Mercurial > hg > octave-image
changeset 828:0ac3df9562b0
bwconncomp: return indices for object elements and not its boundaries.
* bwconncomp: PixelIdxList only gives indices for the borders of each
object in the input image. Fix this and give indices for each element
in the object. Also, by making use of bwlabeln, expand support for matrices
with arbitrary number of dimensions (it was only accepting connectivity
of 4). The default connectivity was changed to 8 for Matlab compatibility.
Add tests.
* private/make_conn.m: return second output argument giving connectivity
for the user (not used internally since for that is only the logical matrix).
* NEWS: make note of this big change. Also added fucntion to list of
functions supporting ND images.
author | Carnë Draug <carandraug@octave.org> |
---|---|
date | Thu, 07 Nov 2013 20:09:04 +0000 |
parents | b413eb683df3 |
children | 2927361d7b00 |
files | NEWS inst/bwconncomp.m inst/private/make_conn.m |
diffstat | 3 files changed, 130 insertions(+), 56 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS +++ b/NEWS @@ -112,6 +112,10 @@ ** The transform option of imtophat has been removed (it was deprecated in version 2.0.0) in favour of using imbothat. + ** The function bwconncomp now returns the indices for each element in each + object, no longer the indices for the elements in the object boundaries + only. The connectivity default was changed to 8. + ** Other functions that have been changed for smaller bugfixes, increased Matlab compatibility, or performance: @@ -123,6 +127,7 @@ number of dimensions: bestblk + bwconncomp col2im colfilt im2col
--- a/inst/bwconncomp.m +++ b/inst/bwconncomp.m @@ -1,4 +1,5 @@ ## Copyright (C) 2010 Søren Hauberg <soren@hauberg.org> +## Copyright (C) 2013 Carnë Draug <carandraug@octave.org> ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software @@ -14,56 +15,110 @@ ## this program; if not, see <http://www.gnu.org/licenses/>. ## -*- texinfo -*- -## @deftypefn {Function File} {@var{cc} = } bwconncomp (@var{BW}) -## @deftypefnx {Function File} {@var{cc} = } bwconncomp (@var{BW}, @var{connectivity}) -## Trace the boundaries of objects in a binary image. +## @deftypefn {Function File} {@var{cc} =} bwconncomp (@var{bw}) +## @deftypefnx {Function File} {@var{cc} =} bwconncomp (@var{bw}, @var{conn}) +## Find connected objects. ## -## @code{bwconncomp} traces the boundaries of objects in a binary image @var{BW} -## and returns information about them in a structure with the following fields. +## Elements from the matrix @var{bw}, belong to an object if they have a +## non-zero value. The output @var{cc} is a structure with information about +## each object; ## -## @table @t -## @item Connectivity -## The connectivity used in the boundary tracing. -## @item ImageSize -## The size of the image @var{BW}. -## @item NumObjects -## The number of objects in the image @var{BW}. -## @item PixelIdxList +## @table @qcode +## @item "Connectivity" +## The connectivity used in the boundary tracing. This may be different from +## the input argument, e.g., if @var{conn} is defined as a matrix of 1s and +## size 3x3, the @qcode{"Connectivity"} value will still be 8. +## @item "ImageSize" +## The size of the matrix @var{bw}. +## @item "NumObjects" +## The number of objects in the image @var{bw}. +## @item "PixelIdxList" +## A cell array with linear indices for each element of each object in @var{bw} ## A cell array containing where each element corresponds to an object in @var{BW}. ## Each element is represented as a vector of linear indices of the boundary of ## the given object. ## @end table ## -## The connectivity used in the tracing is by default 4, but can be changed -## by setting the @var{connectivity} input parameter to 8. Sadly, this is not -## yet implemented. -## @seealso{bwlabel, bwboundaries, ind2sub} +## Element connectivity @var{conn}, to define the size of objects, can be +## specified with a numeric scalar (number of elements in the neighborhood): +## +## @table @samp +## @item 4 or 8 +## for 2 dimensional matrices; +## @item 6, 18 or 26 +## for 3 dimensional matrices; +## @end table +## +## or with a binary matrix representing a connectivity array. Defaults to +## @code{conndef (ndims (@var{bw}), "maximal")} which is equivalent to +## @var{conn} of 8 and 26 for 2 and 3 dimensional matrices respectively. +## +## @seealso{bwlabel, bwlabeln, bwboundaries, ind2sub, regionprops} ## @end deftypefn -function CC = bwconncomp (bw, N = 4) - ## Check input - if (nargin < 1) - error ("bwconncomp: not enough input arguments"); - endif - if (!ismatrix (bw) || ndims (bw) != 2) - error ("bwconncomp: first input argument must be a NxM matrix"); +function CC = bwconncomp (bw, N) + + if (nargin < 1 || nargin > 2) + print_usage (); + elseif (! ismatrix (bw) || ! (isnumeric (bw) || islogical (bw))) + error ("bwconncomp: BW must be an a numeric matrix"); endif - if (!isscalar (N) || !any (N == [4])) #, 8])) - error ("bwconncomp: second input argument must be 4"); + if (nargin < 2) + ## Defining default connectivity here because it's dependent + ## on the first argument + N = conndef (ndims (bw), "maximal"); endif - - ## Trace boundaries - B = bwboundaries (bw, N); + [conn, N] = make_conn ("bwconncomp", 2, ndims (bw), N); + - ## Convert from (x, y) index to linear indexing - P = cell (1, numel (B)); - for k = 1:numel (B) - P{k} = sub2ind (size (bw), B{k}(:, 1), B{k}(:, 2)); - endfor + [bw, n_obj] = bwlabeln (logical (bw), conn); + ## We should probably implement this as the first part of bwlabeln + ## as getting the indices is the first part of its code. Here we are + ## just reverting the work already done. + P = arrayfun (@(x) find (bw == x), 1:n_obj, "UniformOutput", false); ## Return result CC = struct ("Connectivity", N, "ImageSize", size (bw), - "NumObjects", numel (B), + "NumObjects", n_obj, "PixelIdxList", {P}); endfunction + +%!test +%! a = rand (10) > 0.5; +%! cc = bwconncomp (a, 4); +%! assert (cc.Connectivity, 4) +%! assert (cc.ImageSize, [10 10]) +%! +%! b = false (10); +%! for i = 1:numel (cc.PixelIdxList) +%! b(cc.PixelIdxList{i}) = true; +%! endfor +%! assert (a, b) + +%!test +%! a = rand (10, 13) > 0.5; +%! cc = bwconncomp (a, 4); +%! assert (cc.ImageSize, [10 13]) +%! +%! b = false (10, 13); +%! for i = 1:numel (cc.PixelIdxList) +%! b(cc.PixelIdxList{i}) = true; +%! endfor +%! assert (a, b) + +%!test +%! a = rand (15) > 0.5; +%! conn_8 = bwconncomp (a, 8); +%! assert (conn_8, bwconncomp (a)) +%! assert (conn_8, bwconncomp (a, ones (3))) +%! assert (conn_8.Connectivity, 8) +%! assert (bwconncomp (a, ones (3)).Connectivity, 8) +%! assert (bwconncomp (a, [0 1 0; 1 1 1; 0 1 0]).Connectivity, 4) + +## test that PixelIdxList is a row vector +%!test +%! a = rand (40, 40) > 0.2; +%! cc = bwconncomp (a, 4); +%! assert (rows (cc.PixelIdxList), 1) +%! assert (columns (cc.PixelIdxList) > 1)
--- a/inst/private/make_conn.m +++ b/inst/private/make_conn.m @@ -32,37 +32,29 @@ ## @code{conndef (ndims (@var{bw}), "maximal")} which is equivalent to ## @var{conn} of 8 and 26 for 2 and 3 dimensional matrices respectively. -function conn = make_conn (func, arg_pos, n_dims, conn) +function [conn, N] = make_conn (func, arg_pos, n_dims, conn) + + persistent conn_4 = logical ([0 1 0; 1 1 1; 0 1 0]); + persistent conn_8 = true (3); + persistent conn_6 = get_conn_6 (); + persistent conn_18 = get_conn_18 (); + persistent conn_26 = true (3, 3, 3); iptcheckconn (conn, func, "CONN", arg_pos); if (isscalar (conn)) + N = conn; if (n_dims == 2) - if (conn == 4) - conn = [0 1 0 - 1 1 1 - 0 1 0]; - elseif (conn == 8) - conn = [1 1 1 - 1 1 1 - 1 1 1]; + if (conn == 4), conn = conn_4; + elseif (conn == 8), conn = conn_8; else error ("%s: CONN must have a value of 4 or 8 for 2 dimensional matrices", func); endif elseif (n_dims == 3) - if (conn == 6) - conn = false (3, 3, 3); - conn(:,2,2) = true; - conn(2,:,2) = true; - conn(2,2,:) = true; - elseif (conn == 18) - conn = false (3, 3, 3); - conn(2,:,:) = true; - conn(:,2,:) = true; - conn(:,:,2) = true; - elseif (conn == 26) - conn = true (3, 3, 3); + if (conn == 6), conn = conn_6; + elseif (conn == 18), conn = conn_18; + elseif (conn == 26), conn = conn_26; else error (["%s: CONN must have a value of 6, 18, or 26 for 3 " ... "dimensional matrices"], func); @@ -71,5 +63,27 @@ error (["%s: CONN must be defined as a binary matrix for matrices " ... "with more than 3 dimensions"], func); endif + elseif (nargout > 1) + if (isequal (conn, conn_4)), N = 4; + elseif (isequal (conn, conn_8)), N = 8; + elseif (isequal (conn, conn_6)), N = 6; + elseif (isequal (conn, conn_18)), N = 18; + elseif (isequal (conn, conn_26)), N = 26; + else, N = conn; + endif endif endfunction + +function conn = get_conn_6 () + conn = false (3, 3, 3); + conn(:,2,2) = true; + conn(2,:,2) = true; + conn(2,2,:) = true; +endfunction + +function conn = get_conn_18 () + conn = false (3, 3, 3); + conn(2,:,:) = true; + conn(:,2,:) = true; + conn(:,:,2) = true; +endfunction