Mercurial > hg > octave-image
changeset 818:355bd9c476d9
bwperim: rewrite to support N-dimensional images and connectivity.
* bwperim.m: rewrite the function in order to accept images with any number
of dimensions. Treat the connectivity argument as more standard (relying on
the new private make_conn function). Display image instead of returning its
value if nargout less than 1. Add tests.
* private/make_conn.m: new private function to be shared among functions
accepting a connectivity argument.
* NEWS: add bwperim to functions that have been changed.
author | Carnë Draug <carandraug@octave.org> |
---|---|
date | Tue, 29 Oct 2013 02:32:44 +0000 |
parents | 201fd05fbc79 |
children | 83f060d508a7 |
files | NEWS inst/bwperim.m inst/private/make_conn.m |
diffstat | 3 files changed, 280 insertions(+), 45 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS +++ b/NEWS @@ -118,6 +118,7 @@ Matlab compatibility, or performance: bwlabel + bwperim padarray Summary of important user-visible changes for image 2.0.0 (2012/11/08):
--- a/inst/bwperim.m +++ b/inst/bwperim.m @@ -1,4 +1,4 @@ -## Copyright (C) 2006 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,57 +14,216 @@ ## this program; if not, see <http://www.gnu.org/licenses/>. ## -*- texinfo -*- -## @deftypefn {Function File} @var{BW2} = bwperim(@var{BW1}) -## @deftypefnx{Function File} @var{BW2} = bwperim(@var{BW1}, @var{n}) -## Find the perimeter of objects in binary images. +## @deftypefn {Function File} {} bwperim (@var{bw}) +## @deftypefnx {Function File} {} bwperim (@var{bw}, @var{conn}) +## Find perimeter of objects in binary images. +## +## Values from the matrix @var{bw} are considered part of an object perimeter +## if their value is non-zero and is connected to at least one zero-valued +## element. +## +## Element connectivity @var{conn}, to define the size of objects, can be +## specified with a numeric scalar (number of elements in the neighborhood): ## -## A pixel is part of an object perimeter if its value is one and there -## is at least one zero-valued pixel in its neighborhood. +## @table @samp +## @item 4 or 8 +## for 2 dimensional matrices; +## @item 6, 18 or 26 +## for 3 dimensional matrices; +## @end table ## -## By default the neighborhood of a pixel is 4 nearest pixels, but -## if @var{n} is set to 8 the 8 nearest pixels will be considered. +## or with a binary matrix representing a connectivity array. Defaults to +## @code{conndef (ndims (@var{bw}), "minimal")} which is equivalent to +## @var{conn} of 4 and 6 for 2 and 3 dimensional matrices respectively. +## +## @seealso{bwarea, bwboundaries, imerode, mmgrad} ## @end deftypefn -function out = bwperim(bw, n=4) - ## Input checking - if (nargin < 1) - print_usage(); +function varargout = bwperim (bw, conn) + + if (nargin < 1 || nargin > 2) + print_usage (); + elseif (! ismatrix (bw) || ! (isnumeric (bw) || islogical (bw))) + error("bwperim: BW must be a numeric matrix"); endif - if (!isbw(bw) || ndims(bw)!=2) - error("bwperim: first input argument must be a 2-dimensional binary image"); + + nDims = ndims (bw); + if (nargin < 2) + ## Defining default connectivity here because it's dependent + ## on the first argument + conn = conndef (nDims, "minimal"); + else + conn = make_conn ("bwperim", 2, nDims, conn); endif - if (!isscalar(n) || (n!=4 && n!=8)) - error("bwperim: second argument must be 4 or 8"); - endif - + ## Make sure bw is logical; bw = logical (bw); - - ## Translate image by one pixel in all directions - [rows, cols] = size(bw); - north = [bw(2:end, :); zeros(1, cols, "logical")]; - south = [zeros(1, cols, "logical"); bw(1:end-1, :)]; - west = [bw(:, 2:end), zeros(rows, 1, "logical")]; - east = [zeros(rows, 1, "logical"), bw(:, 1:end-1)]; - if (n == 8) - north_east = north_west = south_east = south_west = zeros (rows, cols, "logical"); - north_east (1:end-1, 2:end) = bw (2:end, 1:end-1); - north_west (1:end-1, 1:end-1) = bw (2:end, 2:end); - south_east (2:end, 2:end) = bw (1:end-1, 1:end-1); - south_west (2:end, 1:end-1) = bw (1:end-1, 2:end); - endif - - ## Do the comparing - if (n == 4) - out = bw; - idx = (north == bw) & (south == bw) & (west == bw) & (east == bw); - out(idx) = false; - else # n == 8 - out = bw; - idx = (north == bw) & (north_east == bw) & ... - (east == bw) & (south_east == bw) & ... - (south == bw) & (south_west == bw) & ... - (west == bw) & (north_west == bw); - out (idx) = false; + + ## Recover the elements that would get removed by erosion + perim = (! imerode (bw, conn)) & bw; + + ## Get the borders back (they are removed during erosion + tmp_idx = repmat ({":"}, [1 nDims]); + p_size = size (perim); + for dim = 1:nDims + idx = tmp_idx; + idx{dim} = [1 p_size(dim)]; + perim(idx{:}) = bw(idx{:}); + endfor + + if (nargout > 0) + varargout{1} = perim; + else + imshow (perim); endif endfunction + +%!shared in, out +%! in = [ 1 1 1 1 0 1 1 0 1 1 +%! 1 1 0 1 1 1 1 1 1 0 +%! 1 1 1 0 1 1 1 1 1 1 +%! 1 1 1 1 0 1 1 1 0 1 +%! 1 1 1 0 1 1 1 1 1 0 +%! 1 1 1 1 1 1 0 1 0 1 +%! 1 1 1 1 1 1 1 1 1 0 +%! 1 1 1 1 1 1 1 1 1 1 +%! 1 1 1 1 1 1 0 0 1 1 +%! 1 1 1 1 0 1 0 1 1 0]; +%! +%! out = [1 1 1 1 0 1 1 0 1 1 +%! 1 1 0 1 1 0 0 1 1 0 +%! 1 0 1 0 1 0 0 0 1 1 +%! 1 0 0 1 0 1 0 1 0 1 +%! 1 0 1 0 1 0 1 0 1 0 +%! 1 0 0 1 0 1 0 1 0 1 +%! 1 0 0 0 0 0 1 0 1 0 +%! 1 0 0 0 0 0 1 1 0 1 +%! 1 0 0 0 1 1 0 0 1 1 +%! 1 1 1 1 0 1 0 1 1 0]; +%!assert (bwperim (in), logical (out)); +%!assert (bwperim (in, 4), logical (out)); +%! +%! out = [1 1 1 1 0 1 1 0 1 1 +%! 1 1 0 1 1 1 1 1 1 0 +%! 1 1 1 0 1 1 0 1 1 1 +%! 1 0 1 1 0 1 0 1 0 1 +%! 1 0 1 0 1 1 1 1 1 0 +%! 1 0 1 1 1 1 0 1 0 1 +%! 1 0 0 0 0 1 1 1 1 0 +%! 1 0 0 0 0 1 1 1 1 1 +%! 1 0 0 1 1 1 0 0 1 1 +%! 1 1 1 1 0 1 0 1 1 0]; +%!assert (bwperim (in, 8), logical (out)); +%! +%! out = [1 1 1 1 0 1 1 0 1 1 +%! 1 0 0 0 0 1 0 0 1 0 +%! 1 0 0 0 0 0 0 1 0 1 +%! 1 0 1 0 0 0 0 0 0 1 +%! 1 0 0 0 0 1 0 1 0 0 +%! 1 0 0 0 1 0 0 0 0 1 +%! 1 0 0 0 0 0 0 1 0 0 +%! 1 0 0 0 0 1 1 0 0 1 +%! 1 0 0 1 0 1 0 0 1 1 +%! 1 1 1 1 0 1 0 1 1 0]; +%!assert (bwperim (in, [1 0 0; 0 1 0; 0 0 1]), logical (out)); + +## test that any non-zero value is valid (even i and Inf) +%!shared in, out +%! in = [ 0 0 0 0 0 0 0 +%! 0 0 5 0 0 1 9 +%! 0 Inf 9 7 0 0 0 +%! 0 1.5 5 7 1 0 0 +%! 0 0.5 -1 89 i 0 0 +%! 0 4 10 15 1 0 0 +%! 0 0 0 0 0 0 0]; +%! out = [0 0 0 0 0 0 0 +%! 0 0 1 0 0 1 1 +%! 0 1 0 1 0 0 0 +%! 0 1 0 0 1 0 0 +%! 0 1 0 0 1 0 0 +%! 0 1 1 1 1 0 0 +%! 0 0 0 0 0 0 0]; +%!assert (bwperim (in), logical (out)); + +## test for 3D +%!shared in, out +%! in = reshape (magic(16), [8 8 4]) > 50; +%! out(:,:,1) = [ +%! 1 1 0 1 0 1 1 1 +%! 0 1 1 1 1 1 0 1 +%! 0 1 1 1 1 1 0 1 +%! 1 1 0 1 1 1 1 1 +%! 1 1 1 1 1 1 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 0 1 1 1 1 1 0]; +%! out(:,:,2) = [ +%! 1 1 0 1 0 1 1 1 +%! 0 1 1 0 1 1 0 1 +%! 0 1 0 0 0 1 0 1 +%! 1 0 1 0 0 0 1 1 +%! 1 0 0 1 0 1 0 1 +%! 1 0 1 0 1 0 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 0 1 1 1 1 1 0]; +%! out(:,:,3) = [ +%! 1 1 0 1 0 1 1 1 +%! 0 1 1 0 1 1 0 1 +%! 0 1 0 0 0 1 0 1 +%! 1 0 0 0 0 0 1 1 +%! 1 0 0 1 0 1 0 1 +%! 1 0 1 0 1 0 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 0 1 1 1 1 1 0]; +%! out(:,:,4) = [ +%! 1 1 0 1 0 1 1 1 +%! 0 1 1 1 1 1 0 1 +%! 0 1 1 1 1 1 0 1 +%! 1 1 1 1 1 1 1 1 +%! 1 1 1 1 1 1 1 0 +%! 1 1 1 0 1 0 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 0 1 1 1 1 1 0]; +%!assert (bwperim (in), logical (out)); +%! +%! out(:,:,1) = [ +%! 1 1 0 1 0 1 1 1 +%! 0 1 1 1 1 1 0 1 +%! 0 1 1 1 1 1 0 1 +%! 1 1 0 1 1 1 1 1 +%! 1 1 1 1 1 1 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 0 1 1 1 1 1 0]; +%! out(:,:,2) = [ +%! 1 1 0 1 0 1 1 1 +%! 0 1 1 1 1 1 0 1 +%! 0 1 1 0 0 1 0 1 +%! 1 1 1 1 0 1 1 1 +%! 1 0 1 1 1 1 1 1 +%! 1 0 1 0 1 0 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 0 1 1 1 1 1 0]; +%! out(:,:,3) = [ +%! 1 1 0 1 0 1 1 1 +%! 0 1 1 1 1 1 0 1 +%! 0 1 0 0 0 1 0 1 +%! 1 1 0 0 0 1 1 1 +%! 1 0 1 1 1 1 1 1 +%! 1 0 1 0 1 0 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 0 1 1 1 1 1 0]; +%! out(:,:,4) = [ +%! 1 1 0 1 0 1 1 1 +%! 0 1 1 1 1 1 0 1 +%! 0 1 1 1 1 1 0 1 +%! 1 1 1 1 1 1 1 1 +%! 1 1 1 1 1 1 1 0 +%! 1 1 1 0 1 0 1 1 +%! 1 1 1 0 1 0 1 1 +%! 1 0 1 1 1 1 1 0]; +%!assert (bwperim (in, 18), logical (out)); + +%!error bwperim ("text") +%!error bwperim (rand (10), 5) +%!error bwperim (rand (10), "text")
new file mode 100644 --- /dev/null +++ b/inst/private/make_conn.m @@ -0,0 +1,75 @@ +## Copyright (C) 2013 Carnë Draug <carandraug+dev@gmail.com> +## +## 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/>. + +## Private function to create a connectivity array +## +## Use the following Texinfo on the documentation of functions +## that make use of it (adjust the default) +## +## 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. + +function conn = make_conn (func, arg_pos, n_dims, conn) + + iptcheckconn (conn, func, "CONN", arg_pos); + if (isscalar (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]; + 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); + else + error (["%s: CONN must have a value of 6, 18, or 26 for 3 " ... + "dimensional matrices"], func); + endif + else + error (["%s: CONN must be defined as a binary matrix for matrices " ... + "with more than 3 dimensions"], func); + endif + endif +endfunction