Mercurial > hg > octave-image
changeset 807:a50b1bcbe3d9
bwlabeln: use implementation of bwlabel which is faster for 2D matrices.
* bwlabeln.cc: merged in code from bwlabel(). If input matrix is 2
dimensional, and the connectivity mask is equivalent to 4 or 8,
use bwlabel_2d() which is faster (though the labels number may
differ). Also fix bug when connectivity mask was passed as a
matrix rather than a scalar connectivity value.
* bwlabel.cc: file removed and merged into bwlabeln.cc.
* Makefile: remove bwlabel.oct as target
author | Carnë Draug <carandraug@octave.org> |
---|---|
date | Thu, 17 Oct 2013 03:33:34 +0100 |
parents | 275202657ccb |
children | 71c15a64a05c |
files | src/Makefile src/bwlabel.cc src/bwlabeln.cc |
diffstat | 3 files changed, 453 insertions(+), 453 deletions(-) [+] |
line wrap: on
line diff
--- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,7 @@ MKOCTFILE ?= mkoctfile -Wall all: __spatial_filtering__.oct __bilateral__.oct __custom_gaussian_smoothing__.oct \ - __boundary__.oct bwlabel.oct bwfill.oct rotate_scale.oct hough_line.oct \ + __boundary__.oct bwfill.oct rotate_scale.oct hough_line.oct \ graycomatrix.oct bwdist.oct nonmax_supress.oct bwlabeln.oct imerode.oct %.oct: %.cc
deleted file mode 100644 --- a/src/bwlabel.cc +++ /dev/null @@ -1,363 +0,0 @@ -//Copyright (C) 2002 Jeffrey E. Boyd <boyd@cpsc.ucalgary.ca> -//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 -// Foundation; either version 3 of the License, or (at your option) any later -// version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -// details. -// -// You should have received a copy of the GNU General Public License along with -// this program; if not, see <http://www.gnu.org/licenses/>. - -#include <octave/oct.h> -#include <vector> -#include <algorithm> - -static octave_idx_type -find (std::vector<octave_idx_type>& lset, octave_idx_type x) -{ - // Follow lset until we find a value that points to itself - while (lset[x] != x) - x = lset[x]; - return x; -} - -static octave_value_list -bwlabel_2d (const boolMatrix& BW, const octave_idx_type& n) -{ - // This algorithm was derived from BKP Horn, Robot Vision, MIT Press, - // 1986, p 65 - 89 by Jeffrey E. Boyd in 2002. Some smaller changes - // were then introduced by Carnë Draug in 2013 to speed up by iterating - // down a column, and what values to use when connecting two labels - // to increase chance of getting them in the right order in the end. - - const octave_idx_type nr = BW.rows (); - const octave_idx_type nc = BW.columns (); - - // The labelled image - Matrix L (nr, nc); - - std::vector<octave_idx_type> lset (nc*nr); // label table/tree - - octave_idx_type ntable = 0; // number of elements in the component table/tree - octave_idx_type ind = 0; // linear index - - bool n4, n6, n8; - n4 = n6 = n8 = false; - if (n == 4) - n4 = true; - else if (n == 6) - n6 = true; - else if (n == 8) - n8 = true; - - const bool* BW_vec = BW.data (); - double* L_vec = L.fortran_vec (); - - for (octave_idx_type c = 0; c < nc; c++) - { - for (octave_idx_type r = 0; r < nr; r++, ind++) - { - if (BW_vec[ind]) // if A is an object - { - octave_idx_type stride = ind - nr; - // Get the neighboring pixels B, C, D, and E - // - // D B - // C A <-- ind is linear index to A - // E - // - // C and B will always be needed so we get them here, but - // D is only needed when n is 6 or 8, and E when n is 8. - - octave_idx_type B, C; - if (c == 0) - C = 0; - else - C = find (lset, L_vec[stride]); - - if (r == 0) - B = 0; - else - B = find (lset, L_vec[ind -1]); - - if (n4) - { - // apply 4 connectedness - if (B && C) // B and C are labeled - { - if (B != C) - lset[B] = C; - - L_vec[ind] = C; - } - else if (B) // B is object but C is not - L_vec[ind] = B; - else if (C) // C is object but B is not - L_vec[ind] = C; - else // B, C not object - new object - { - // label and put into table - ntable++; - L_vec[ind] = lset[ntable] = ntable; - } - } - else if (n6) - { - // Apply 6 connectedness. Seem there's more than one - // possible way to do this for 2D images but for some - // reason, the most common seems to be the top left pixel - // and the bottom right - // See http://en.wikipedia.org/wiki/Pixel_connectivity - - octave_idx_type D; - // D is only required for n6 and n8 - if (r == 0 || c == 0) - D = 0; - else - D = find (lset, L_vec[stride -1]); - - if (D) // D object, copy label and move on - L_vec[ind] = D; - else if (B && C) // B and C are labeled - { - if (B == C) - L_vec[ind] = B; - else - { - octave_idx_type tlabel = std::min (B, C); - lset[B] = tlabel; - lset[C] = tlabel; - L_vec[ind] = tlabel; - } - } - else if (B) // B is object but C is not - L_vec[ind] = B; - else if (C) // C is object but B is not - L_vec[ind] = C; - else // B, C, D not object - new object - { - // label and put into table - ntable++; - L_vec[ind] = lset[ntable] = ntable; - } - } - else if (n8) - { - octave_idx_type D, E; - // D is only required for n6 and n8 - if (r == 0 || c == 0) - D = 0; - else - D = find (lset, L_vec[stride -1]); - - // E is only required for n8 - if (c == 0 || r == nr -1) - E = 0; - else - E = find (lset, L_vec[stride +1]); - - // apply 8 connectedness - if (B || C || D || E) - { - octave_idx_type tlabel = D; - if (D) - ; // do nothing (tlabel is already D) - else if (C) - tlabel = C; - else if (E) - tlabel = E; - else if (B) - tlabel = B; - - L_vec[ind] = tlabel; - - if (B && B != tlabel) - lset[B] = tlabel; - if (C && C != tlabel) - lset[C] = tlabel; - if (D) - // we don't check if B != tlabel since if B - // is true, tlabel == B - lset[D] = tlabel; - if (E && E != tlabel) - lset[E] = tlabel; - } - else - { - // label and put into table - ntable++; // run image through the look-up table - L_vec[ind] = lset[ntable] = ntable; - } - } - } - else - L_vec[ind] = 0; // A is not an object so leave it - } - } - - const octave_idx_type numel = BW.numel (); - - // consolidate component table - for (octave_idx_type i = 0; i <= ntable; i++) - lset[i] = find (lset, i); - - // run image through the look-up table - for (octave_idx_type ind = 0; ind < numel; ind++) - L_vec[ind] = lset[L_vec[ind]]; - - // count up the objects in the image - for (octave_idx_type i = 0; i <= ntable; i++) - lset[i] = 0; - - for (octave_idx_type ind = 0; ind < numel; ind++) - lset[L_vec[ind]]++; - - // number the objects from 1 through n objects - octave_idx_type nobj = 0; - lset[0] = 0; - for (octave_idx_type i = 1; i <= ntable; i++) - if (lset[i] > 0) - lset[i] = ++nobj; - - // Run through the look-up table again, so that their numbers - // match the number of labels - for (octave_idx_type ind = 0; ind < numel; ind++) - L_vec[ind] = lset[L_vec[ind]]; - - octave_value_list rval; - rval(0) = L; - rval(1) = double (nobj); - return rval; -} - -DEFUN_DLD(bwlabel, args, , "\ --*- texinfo -*-\n\ -@deftypefn {Loadable Function} {[@var{l}, @var{num}] =} bwlabel(@var{BW})\n\ -@deftypefnx {Loadable Function} {[@var{l}, @var{num}] =} bwlabel(@var{BW}, @var{n})\n\ -Label binary 2 dimensional image.\n\ -\n\ -Labels foreground objects in the binary image @var{bw}.\n\ -The output @var{l} is a matrix where 0 indicates a background pixel,\n\ -1 indicates that the pixel belong to object number 1, 2 that the pixel\n\ -belong to object number 2, etc.\n\ -The total number of objects is @var{num}.\n\ -\n\ -To pixels belong to the same object if the are neighbors. By default\n\ -the algorithm uses 8-connectivity to define a neighborhood, but this\n\ -can be changed through the argument @var{n} that can be either 4, 6, or 8.\n\ -\n\ -@seealso{bwconncomp, bwlabeln, regionprops}\n\ -@end deftypefn\n\ -") -{ - octave_value_list rval; - - const octave_idx_type nargin = args.length (); - if (nargin < 1 || nargin > 2) - { - print_usage (); - return rval; - } - - // We do not check error state after conversion to boolMatrix - // because what we want is to actually get a boolean matrix - // with all non-zero elements as true (Matlab compatibility). - if (! args(0).is_numeric_type () && ! args(0).is_bool_type ()) - { - error ("bwlabeln: first input argument must be an ND array"); - return rval; - } - const boolMatrix BW = args(0).bool_array_value (); - - // N-hood connectivity - const octave_idx_type n = nargin < 2 ? 8 : args(1).idx_type_value (); - if (error_state || (n != 4 && n!= 6 && n != 8)) - { - error ("bwlabel: BW must be a 2 dimensional matrix"); - return rval; - } - return bwlabel_2d (BW, n); -} - -/* -%!assert (bwlabel (logical ([0 1 0; 0 0 0; 1 0 1])), [0 2 0; 0 0 0; 1 0 3]); -%!assert (bwlabel ([0 1 0; 0 0 0; 1 0 1]), [0 2 0; 0 0 0; 1 0 3]); - -## Support any type of real non-zero value -%!assert (bwlabel ([0 -1 0; 0 0 0; 5 0 0.2]), [0 2 0; 0 0 0; 1 0 3]); - -%!shared in, out -%! -%! in = [ 0 1 1 0 0 1 0 0 0 0 -%! 0 0 0 1 0 0 0 0 0 1 -%! 0 1 1 0 0 0 0 0 1 1 -%! 1 0 0 0 0 0 0 1 0 0 -%! 0 0 0 0 0 1 1 0 0 0 -%! 0 0 0 0 0 0 0 0 0 0 -%! 0 0 0 1 0 0 0 0 0 0 -%! 0 0 0 0 1 1 0 1 0 0 -%! 0 0 0 1 0 1 0 1 0 1 -%! 1 1 0 0 0 0 0 1 1 0]; -%! -%! out = [ 0 3 3 0 0 9 0 0 0 0 -%! 0 0 0 5 0 0 0 0 0 13 -%! 0 4 4 0 0 0 0 0 13 13 -%! 1 0 0 0 0 0 0 11 0 0 -%! 0 0 0 0 0 10 10 0 0 0 -%! 0 0 0 0 0 0 0 0 0 0 -%! 0 0 0 6 0 0 0 0 0 0 -%! 0 0 0 0 8 8 0 12 0 0 -%! 0 0 0 7 0 8 0 12 0 14 -%! 2 2 0 0 0 0 0 12 12 0]; -%!assert (nthargout ([1 2], @bwlabel, in, 4), {out, 14}); -%!assert (nthargout ([1 2], @bwlabel, logical (in), 4), {out, 14}); -%! -%! out = [ 0 3 3 0 0 7 0 0 0 0 -%! 0 0 0 3 0 0 0 0 0 11 -%! 0 4 4 0 0 0 0 0 11 11 -%! 1 0 0 0 0 0 0 9 0 0 -%! 0 0 0 0 0 8 8 0 0 0 -%! 0 0 0 0 0 0 0 0 0 0 -%! 0 0 0 5 0 0 0 0 0 0 -%! 0 0 0 0 5 5 0 10 0 0 -%! 0 0 0 6 0 5 0 10 0 12 -%! 2 2 0 0 0 0 0 10 10 0]; -%!assert (nthargout ([1 2], @bwlabel, in, 6), {out, 12}); -%!assert (nthargout ([1 2], @bwlabel, logical (in), 6), {out, 12}); -%! -%! ## The labeled image is not the same as Matlab, but they are -%! ## labeled correctly. Do we really need to get them properly -%! ## ordered? (the algorithm in bwlabeln does it) -%! mout = [0 1 1 0 0 4 0 0 0 0 -%! 0 0 0 1 0 0 0 0 0 5 -%! 0 1 1 0 0 0 0 0 5 5 -%! 1 0 0 0 0 0 0 5 0 0 -%! 0 0 0 0 0 5 5 0 0 0 -%! 0 0 0 0 0 0 0 0 0 0 -%! 0 0 0 3 0 0 0 0 0 0 -%! 0 0 0 0 3 3 0 6 0 0 -%! 0 0 0 3 0 3 0 6 0 6 -%! 2 2 0 0 0 0 0 6 6 0]; -%! -%! out = [ 0 2 2 0 0 4 0 0 0 0 -%! 0 0 0 2 0 0 0 0 0 5 -%! 0 2 2 0 0 0 0 0 5 5 -%! 2 0 0 0 0 0 0 5 0 0 -%! 0 0 0 0 0 5 5 0 0 0 -%! 0 0 0 0 0 0 0 0 0 0 -%! 0 0 0 3 0 0 0 0 0 0 -%! 0 0 0 0 3 3 0 6 0 0 -%! 0 0 0 3 0 3 0 6 0 6 -%! 1 1 0 0 0 0 0 6 6 0]; -%!assert (nthargout ([1 2], @bwlabel, in, 8), {out, 6}); -%!assert (nthargout ([1 2], @bwlabel, logical (in), 8), {out, 6}); -%! -%!error bwlabel (rand (10) > 0.8, "text") -%!error bwlabel ("text", 6) -*/
--- a/src/bwlabeln.cc +++ b/src/bwlabeln.cc @@ -1,4 +1,6 @@ +// Copyright (C) 2002 Jeffrey E. Boyd <boyd@cpsc.ucalgary.ca> // Copyright (C) 2011-2012 Jordi Gutiérrez Hermoso <jordigh@octave.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 @@ -14,11 +16,14 @@ // along with this program; if not, see // <http://www.gnu.org/licenses/>. -// bwlabeln.cc --- - +// Copyright +// Jeffrey E. Boyd and Carnë Draug for bwlabel_2d +// Jordi Gutiérrez Hermoso for bwlabel_nd -#include <oct.h> +#include <octave/oct.h> +#include <vector> #include <set> +#include <algorithm> #include "union-find.h++" #include "config.h" @@ -31,7 +36,6 @@ #error Must have the TR1 or C++11 unordered_map header #endif - typedef Array<octave_idx_type> coord; bool operator== (const coord& a, const coord& b) @@ -278,95 +282,12 @@ return padded; } - - -DEFUN_DLD(bwlabeln, args, , "\ --*- texinfo -*-\n\ -@deftypefn {Loadable Function} {[@var{l}, @var{num}] =} bwlabeln(@var{bw})\n\ -@deftypefnx {Loadable Function} {[@var{l}, @var{num}] =} bwlabeln(@var{bw}, @var{n})\n\ -Label foreground objects in the n-dimensional binary image @var{bw}.\n\ -\n\ -The optional argument @var{n} sets the connectivity and defaults 26,\n\ -for 26-connectivity in 3-D images. Other possible values are 18 and 6\n\ -for 3-D images, 4 and 8 for 2-D images, or an arbitrary N-dimensional\n\ -binary connectivity mask where each dimension is of size 3.\n\ -\n\ -The output @var{l} is an Nd-array where 0 indicates a background\n\ -pixel, 1 indicates that the pixel belong to object number 1, 2 that\n\ -the pixel belong to object number 2, etc. The total number of objects\n\ -is @var{num}.\n\ -\n\ -The algorithm used is a disjoint-set data structure, a.k.a. union-find.\n\ -See, for example, http://en.wikipedia.org/wiki/Union-find\n\ -\n\ -@seealso{bwconncomp, bwlabel, regionprops}\n\ -@end deftypefn\n\ -") +static octave_value_list +bwlabel_nd (const boolNDArray& BW, const boolNDArray& conn_mask) { octave_value_list rval; - octave_idx_type nargin = args.length (); - - if (nargin < 1 || nargin > 2) - { - print_usage (); - return rval; - } - - if (!args(0).is_numeric_type () && !args(0).is_bool_type ()) - { - error ("bwlabeln: first input argument must be an ND array"); - return rval; - } - - boolNDArray BW = args(0).bool_array_value (); dim_vector size_vec = BW.dims (); - - //Connectivity mask - boolNDArray conn_mask; - - if (nargin == 2) - { - if (args(1).is_real_scalar ()) - { - double N = args(1).scalar_value (); - if (size_vec.length () == 2 && N != 4 && N != 8) - error ("bwlabeln: for 2d arrays, scalar N must be 4 or 8"); - else if (size_vec.length () == 3 && N != 6 && N != 18 && N != 26) - error ("bwlabeln: for 3d arrays, scalar N must be 4 or 8"); - else if (size_vec.length () > 3) - error ("bwlabeln: for higher-dimensional arrays, N must be a " - "connectivity mask"); - else - conn_mask = get_mask (N); - } - else if (args(2).is_numeric_type () || args(2).is_bool_type ()) - { - conn_mask = args(2).bool_array_value (); - dim_vector conn_mask_dims = conn_mask.dims (); - if (conn_mask_dims.length () != size_vec.length ()) - error ("bwlabeln: connectivity mask N must have the same " - "dimensions as BW"); - for (octave_idx_type i = 0; i < conn_mask_dims.length (); i++) - { - if (conn_mask_dims(i) != 3) - { - error ("bwlabeln: connectivity mask N must have all " - "dimensions equal to 3"); - } - } - } - else - error ("bwlabeln: second input argument must be a real scalar " - "or a connectivity array"); - } - else - // Get the maximal mask that has same number of dims as BW. - conn_mask = get_mask (BW); - - if (error_state) - return rval; - auto neighbours = populate_neighbours(conn_mask, size_vec); // Use temporary array with borders padded with zeros. Labels will @@ -440,3 +361,445 @@ rval(1) = ids_to_label.size (); return rval; } + +static octave_idx_type +find (std::vector<octave_idx_type>& lset, octave_idx_type x) +{ + // Follow lset until we find a value that points to itself + while (lset[x] != x) + x = lset[x]; + return x; +} + +static octave_value_list +bwlabel_2d (const boolMatrix& BW, const octave_idx_type& n) +{ + // This algorithm was derived from BKP Horn, Robot Vision, MIT Press, + // 1986, p 65 - 89 by Jeffrey E. Boyd in 2002. Some smaller changes + // were then introduced by Carnë Draug in 2013 to speed up by iterating + // down a column, and what values to use when connecting two labels + // to increase chance of getting them in the right order in the end. + + const octave_idx_type nr = BW.rows (); + const octave_idx_type nc = BW.columns (); + + // The labelled image + Matrix L (nr, nc); + + std::vector<octave_idx_type> lset (nc*nr); // label table/tree + + octave_idx_type ntable = 0; // number of elements in the component table/tree + octave_idx_type ind = 0; // linear index + + bool n4, n6, n8; + n4 = n6 = n8 = false; + if (n == 4) + n4 = true; + else if (n == 6) + n6 = true; + else if (n == 8) + n8 = true; + + const bool* BW_vec = BW.data (); + double* L_vec = L.fortran_vec (); + + for (octave_idx_type c = 0; c < nc; c++) + { + for (octave_idx_type r = 0; r < nr; r++, ind++) + { + if (BW_vec[ind]) // if A is an object + { + octave_idx_type stride = ind - nr; + // Get the neighboring pixels B, C, D, and E + // + // D B + // C A <-- ind is linear index to A + // E + // + // C and B will always be needed so we get them here, but + // D is only needed when n is 6 or 8, and E when n is 8. + + octave_idx_type B, C; + if (c == 0) + C = 0; + else + C = find (lset, L_vec[stride]); + + if (r == 0) + B = 0; + else + B = find (lset, L_vec[ind -1]); + + if (n4) + { + // apply 4 connectedness + if (B && C) // B and C are labeled + { + if (B != C) + lset[B] = C; + + L_vec[ind] = C; + } + else if (B) // B is object but C is not + L_vec[ind] = B; + else if (C) // C is object but B is not + L_vec[ind] = C; + else // B, C not object - new object + { + // label and put into table + ntable++; + L_vec[ind] = lset[ntable] = ntable; + } + } + else if (n6) + { + // Apply 6 connectedness. Seem there's more than one + // possible way to do this for 2D images but for some + // reason, the most common seems to be the top left pixel + // and the bottom right + // See http://en.wikipedia.org/wiki/Pixel_connectivity + + octave_idx_type D; + // D is only required for n6 and n8 + if (r == 0 || c == 0) + D = 0; + else + D = find (lset, L_vec[stride -1]); + + if (D) // D object, copy label and move on + L_vec[ind] = D; + else if (B && C) // B and C are labeled + { + if (B == C) + L_vec[ind] = B; + else + { + octave_idx_type tlabel = std::min (B, C); + lset[B] = tlabel; + lset[C] = tlabel; + L_vec[ind] = tlabel; + } + } + else if (B) // B is object but C is not + L_vec[ind] = B; + else if (C) // C is object but B is not + L_vec[ind] = C; + else // B, C, D not object - new object + { + // label and put into table + ntable++; + L_vec[ind] = lset[ntable] = ntable; + } + } + else if (n8) + { + octave_idx_type D, E; + // D is only required for n6 and n8 + if (r == 0 || c == 0) + D = 0; + else + D = find (lset, L_vec[stride -1]); + + // E is only required for n8 + if (c == 0 || r == nr -1) + E = 0; + else + E = find (lset, L_vec[stride +1]); + + // apply 8 connectedness + if (B || C || D || E) + { + octave_idx_type tlabel = D; + if (D) + ; // do nothing (tlabel is already D) + else if (C) + tlabel = C; + else if (E) + tlabel = E; + else if (B) + tlabel = B; + + L_vec[ind] = tlabel; + + if (B && B != tlabel) + lset[B] = tlabel; + if (C && C != tlabel) + lset[C] = tlabel; + if (D) + // we don't check if B != tlabel since if B + // is true, tlabel == B + lset[D] = tlabel; + if (E && E != tlabel) + lset[E] = tlabel; + } + else + { + // label and put into table + ntable++; // run image through the look-up table + L_vec[ind] = lset[ntable] = ntable; + } + } + } + else + L_vec[ind] = 0; // A is not an object so leave it + } + } + + const octave_idx_type numel = BW.numel (); + + // consolidate component table + for (octave_idx_type i = 0; i <= ntable; i++) + lset[i] = find (lset, i); + + // run image through the look-up table + for (octave_idx_type ind = 0; ind < numel; ind++) + L_vec[ind] = lset[L_vec[ind]]; + + // count up the objects in the image + for (octave_idx_type i = 0; i <= ntable; i++) + lset[i] = 0; + + for (octave_idx_type ind = 0; ind < numel; ind++) + lset[L_vec[ind]]++; + + // number the objects from 1 through n objects + octave_idx_type nobj = 0; + lset[0] = 0; + for (octave_idx_type i = 1; i <= ntable; i++) + if (lset[i] > 0) + lset[i] = ++nobj; + + // Run through the look-up table again, so that their numbers + // match the number of labels + for (octave_idx_type ind = 0; ind < numel; ind++) + L_vec[ind] = lset[L_vec[ind]]; + + octave_value_list rval; + rval(0) = L; + rval(1) = double (nobj); + return rval; +} + +DEFUN_DLD(bwlabeln, args, , "\ +-*- texinfo -*-\n\ +@deftypefn {Loadable Function} {[@var{l}, @var{num}] =} bwlabeln (@var{bw})\n\ +@deftypefnx {Loadable Function} {[@var{l}, @var{num}] =} bwlabeln (@var{bw}, @var{n})\n\ +Label foreground objects in the n-dimensional binary image @var{bw}.\n\ +\n\ +The optional argument @var{n} sets the connectivity and defaults 26,\n\ +for 26-connectivity in 3-D images. Other possible values are 18 and 6\n\ +for 3-D images, 4 and 8 for 2-D images, or an arbitrary N-dimensional\n\ +binary connectivity mask where each dimension is of size 3.\n\ +\n\ +The output @var{l} is an Nd-array where 0 indicates a background\n\ +pixel, 1 indicates that the pixel belong to object number 1, 2 that\n\ +the pixel belong to object number 2, etc. The total number of objects\n\ +is @var{num}.\n\ +\n\ +The algorithm used is a disjoint-set data structure, a.k.a. union-find.\n\ +See, for example, http://en.wikipedia.org/wiki/Union-find\n\ +\n\ +@seealso{bwconncomp, bwlabel, regionprops}\n\ +@end deftypefn\n\ +") +{ + octave_value_list rval; + + const octave_idx_type nargin = args.length (); + if (nargin < 1 || nargin > 2) + { + print_usage (); + return rval; + } + + if (!args(0).is_numeric_type () && !args(0).is_bool_type ()) + { + error ("bwlabeln: BW must be a numeric or logical matrix"); + return rval; + } + boolNDArray BW = args(0).bool_array_value (); + dim_vector size_vec = BW.dims (); + + //Connectivity mask + boolNDArray conn_mask; + if (nargin == 2) + { + if (args(1).is_real_scalar ()) + { + double N = args(1).scalar_value (); + if (size_vec.length () == 2 && N != 4 && N != 8) + error ("bwlabeln: for 2d arrays, scalar N must be 4 or 8"); + else if (size_vec.length () == 3 && N != 6 && N != 18 && N != 26) + error ("bwlabeln: for 3d arrays, scalar N must be 4 or 8"); + else if (size_vec.length () > 3) + error ("bwlabeln: for higher-dimensional arrays, N must be a " + "connectivity mask"); + else + conn_mask = get_mask (N); + } + else if (args(1).is_numeric_type () || args(1).is_bool_type ()) + { + conn_mask = args(1).bool_array_value (); + dim_vector conn_mask_dims = conn_mask.dims (); + if (conn_mask_dims.length () != size_vec.length ()) + error ("bwlabeln: connectivity mask N must have the same " + "dimensions as BW"); + for (octave_idx_type i = 0; i < conn_mask_dims.length (); i++) + { + if (conn_mask_dims(i) != 3) + { + error ("bwlabeln: connectivity mask N must have all " + "dimensions equal to 3"); + } + } + } + else + error ("bwlabeln: second input argument must be a real scalar " + "or a connectivity array"); + } + else + // Get the maximal mask that has same number of dims as BW. + conn_mask = get_mask (BW); + + if (error_state) + return rval; + + // The implementation in bwlabel_2d is faster so use it if we can + const octave_idx_type ndims = BW.ndims (); + if (ndims == 2 && boolMatrix (conn_mask) == get_mask (4)) + rval = bwlabel_2d (BW, 4); + else if (ndims == 2 && boolMatrix (conn_mask) == get_mask (8)) + rval = bwlabel_2d (BW, 8); + else + rval = bwlabel_nd (BW, conn_mask); + + return rval; +} + +// PKG_ADD: autoload ("bwlabel", which ("bwlabeln")); +// PKG_DEL: autoload ("bwlabel", which ("bwlabeln"), "remove"); +DEFUN_DLD(bwlabel, args, , "\ +-*- texinfo -*-\n\ +@deftypefn {Loadable Function} {[@var{l}, @var{num}] =} bwlabel(@var{BW})\n\ +@deftypefnx {Loadable Function} {[@var{l}, @var{num}] =} bwlabel(@var{BW}, @var{n})\n\ +Label binary 2 dimensional image.\n\ +\n\ +Labels foreground objects in the binary image @var{bw}.\n\ +The output @var{l} is a matrix where 0 indicates a background pixel,\n\ +1 indicates that the pixel belong to object number 1, 2 that the pixel\n\ +belong to object number 2, etc.\n\ +The total number of objects is @var{num}.\n\ +\n\ +To pixels belong to the same object if the are neighbors. By default\n\ +the algorithm uses 8-connectivity to define a neighborhood, but this\n\ +can be changed through the argument @var{n} that can be either 4, 6, or 8.\n\ +\n\ +@seealso{bwconncomp, bwlabeln, regionprops}\n\ +@end deftypefn\n\ +") +{ + octave_value_list rval; + + const octave_idx_type nargin = args.length (); + if (nargin < 1 || nargin > 2) + { + print_usage (); + return rval; + } + + // We do not check error state after conversion to boolMatrix + // because what we want is to actually get a boolean matrix + // with all non-zero elements as true (Matlab compatibility). + if (! args(0).is_numeric_type () && ! args(0).is_bool_type ()) + { + error ("bwlabeln: first input argument must be an ND array"); + return rval; + } + const boolMatrix BW = args(0).bool_array_value (); + + // N-hood connectivity + const octave_idx_type n = nargin < 2 ? 8 : args(1).idx_type_value (); + if (error_state || (n != 4 && n!= 6 && n != 8)) + { + error ("bwlabel: BW must be a 2 dimensional matrix"); + return rval; + } + return bwlabel_2d (BW, n); +} + +/* +%!assert (bwlabel (logical ([0 1 0; 0 0 0; 1 0 1])), [0 2 0; 0 0 0; 1 0 3]); +%!assert (bwlabel ([0 1 0; 0 0 0; 1 0 1]), [0 2 0; 0 0 0; 1 0 3]); + +## Support any type of real non-zero value +%!assert (bwlabel ([0 -1 0; 0 0 0; 5 0 0.2]), [0 2 0; 0 0 0; 1 0 3]); + +%!shared in, out +%! +%! in = [ 0 1 1 0 0 1 0 0 0 0 +%! 0 0 0 1 0 0 0 0 0 1 +%! 0 1 1 0 0 0 0 0 1 1 +%! 1 0 0 0 0 0 0 1 0 0 +%! 0 0 0 0 0 1 1 0 0 0 +%! 0 0 0 0 0 0 0 0 0 0 +%! 0 0 0 1 0 0 0 0 0 0 +%! 0 0 0 0 1 1 0 1 0 0 +%! 0 0 0 1 0 1 0 1 0 1 +%! 1 1 0 0 0 0 0 1 1 0]; +%! +%! out = [ 0 3 3 0 0 9 0 0 0 0 +%! 0 0 0 5 0 0 0 0 0 13 +%! 0 4 4 0 0 0 0 0 13 13 +%! 1 0 0 0 0 0 0 11 0 0 +%! 0 0 0 0 0 10 10 0 0 0 +%! 0 0 0 0 0 0 0 0 0 0 +%! 0 0 0 6 0 0 0 0 0 0 +%! 0 0 0 0 8 8 0 12 0 0 +%! 0 0 0 7 0 8 0 12 0 14 +%! 2 2 0 0 0 0 0 12 12 0]; +%!assert (nthargout ([1 2], @bwlabel, in, 4), {out, 14}); +%!assert (nthargout ([1 2], @bwlabel, logical (in), 4), {out, 14}); +%! +%! out = [ 0 3 3 0 0 7 0 0 0 0 +%! 0 0 0 3 0 0 0 0 0 11 +%! 0 4 4 0 0 0 0 0 11 11 +%! 1 0 0 0 0 0 0 9 0 0 +%! 0 0 0 0 0 8 8 0 0 0 +%! 0 0 0 0 0 0 0 0 0 0 +%! 0 0 0 5 0 0 0 0 0 0 +%! 0 0 0 0 5 5 0 10 0 0 +%! 0 0 0 6 0 5 0 10 0 12 +%! 2 2 0 0 0 0 0 10 10 0]; +%!assert (nthargout ([1 2], @bwlabel, in, 6), {out, 12}); +%!assert (nthargout ([1 2], @bwlabel, logical (in), 6), {out, 12}); +%! +%! ## The labeled image is not the same as Matlab, but they are +%! ## labeled correctly. Do we really need to get them properly +%! ## ordered? (the algorithm in bwlabeln does it) +%! mout = [0 1 1 0 0 4 0 0 0 0 +%! 0 0 0 1 0 0 0 0 0 5 +%! 0 1 1 0 0 0 0 0 5 5 +%! 1 0 0 0 0 0 0 5 0 0 +%! 0 0 0 0 0 5 5 0 0 0 +%! 0 0 0 0 0 0 0 0 0 0 +%! 0 0 0 3 0 0 0 0 0 0 +%! 0 0 0 0 3 3 0 6 0 0 +%! 0 0 0 3 0 3 0 6 0 6 +%! 2 2 0 0 0 0 0 6 6 0]; +%! +%! out = [ 0 2 2 0 0 4 0 0 0 0 +%! 0 0 0 2 0 0 0 0 0 5 +%! 0 2 2 0 0 0 0 0 5 5 +%! 2 0 0 0 0 0 0 5 0 0 +%! 0 0 0 0 0 5 5 0 0 0 +%! 0 0 0 0 0 0 0 0 0 0 +%! 0 0 0 3 0 0 0 0 0 0 +%! 0 0 0 0 3 3 0 6 0 0 +%! 0 0 0 3 0 3 0 6 0 6 +%! 1 1 0 0 0 0 0 6 6 0]; +%!assert (nthargout ([1 2], @bwlabel, in, 8), {out, 6}); +%!assert (nthargout ([1 2], @bwlabel, logical (in), 8), {out, 6}); +%! +%!error bwlabel (rand (10) > 0.8, "text") +%!error bwlabel ("text", 6) +*/