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