1## Copyright (C) 2013 Carnë Draug <carandraug@octave.org> 2## 3## This program is free software; you can redistribute it and/or modify it under 4## the terms of the GNU General Public License as published by the Free Software 5## Foundation; either version 3 of the License, or (at your option) any later 6## version. 7## 8## This program is distributed in the hope that it will be useful, but WITHOUT 9## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 11## details. 12## 13## You should have received a copy of the GNU General Public License along with 14## this program; if not, see <http://www.gnu.org/licenses/>. 15 16## -*- texinfo -*- 17## @deftypefn {Function File} {} bwperim (@var{bw}) 18## @deftypefnx {Function File} {} bwperim (@var{bw}, @var{conn}) 19## Find perimeter of objects in binary images. 20## 21## Values from the matrix @var{bw} are considered part of an object perimeter 22## if their value is non-zero and is connected to at least one zero-valued 23## element, or to the outside of @var{bw}. 24## 25## Element connectivity @var{conn}, to define the size of objects, can be 26## specified with a numeric scalar (number of elements in the neighborhood): 27## 28## @table @samp 29## @item 4 or 8 30## for 2 dimensional matrices; 31## @item 6, 18 or 26 32## for 3 dimensional matrices; 33## @end table 34## 35## or with a binary matrix representing a connectivity array. Defaults to 36## @code{conndef (ndims (@var{bw}), "minimal")} which is equivalent to 37## @var{conn} of 4 and 6 for 2 and 3 dimensional matrices respectively. 38## 39## @seealso{bwarea, bwboundaries, imerode, mmgrad} 40## @end deftypefn 41 42function varargout = bwperim (bw, conn) 43 44 if (nargin < 1 || nargin > 2) 45 print_usage (); 46 endif 47 48 if (! isnumeric (bw) && ! islogical (bw)) 49 error("bwperim: BW must be a numeric or logical matrix"); 50 endif 51 bw = logical (bw); 52 53 if (nargin < 2) 54 conn = conndef (ndims (bw), "minimal"); 55 else 56 conn = conndef (conn); 57 endif 58 59 ## Recover the elements that would get removed by erosion 60 perim = (! imerode (bw, conn)) & bw; 61 62 ## Get the borders back which are removed during erosion 63 ## FIXME this is a bit too convoluted and not elegant at all. I am also 64 ## unsure if it is correct for N dimensional stuff and unusual 65 ## connectivities. We should probably be using the output from 66 ## bwboundaries() but bwboundaries() seems buggy in the case of 67 ## holes. 68 tmp_idx = repmat ({":"}, [1 ndims(perim)]); 69 tmp_conn_idx = repmat ({":"}, [1 ndims(conn)]); 70 p_size = size (perim); 71 for dim = 1:min (ndims (perim), ndims (conn)) 72 conn_idx = tmp_conn_idx; 73 conn_idx{dim} = [1 3]; 74 if (! any (conn(conn_idx{:})(:))) 75 continue 76 endif 77 78 idx = tmp_idx; 79 idx{dim} = [1 p_size(dim)]; 80 perim(idx{:}) = bw(idx{:}); 81 endfor 82 83 if (nargout > 0) 84 varargout{1} = perim; 85 else 86 imshow (perim); 87 endif 88endfunction 89 90%!test 91%! in = [ 1 1 1 1 0 1 1 0 1 1 92%! 1 1 0 1 1 1 1 1 1 0 93%! 1 1 1 0 1 1 1 1 1 1 94%! 1 1 1 1 0 1 1 1 0 1 95%! 1 1 1 0 1 1 1 1 1 0 96%! 1 1 1 1 1 1 0 1 0 1 97%! 1 1 1 1 1 1 1 1 1 0 98%! 1 1 1 1 1 1 1 1 1 1 99%! 1 1 1 1 1 1 0 0 1 1 100%! 1 1 1 1 0 1 0 1 1 0]; 101%! 102%! out = [1 1 1 1 0 1 1 0 1 1 103%! 1 1 0 1 1 0 0 1 1 0 104%! 1 0 1 0 1 0 0 0 1 1 105%! 1 0 0 1 0 1 0 1 0 1 106%! 1 0 1 0 1 0 1 0 1 0 107%! 1 0 0 1 0 1 0 1 0 1 108%! 1 0 0 0 0 0 1 0 1 0 109%! 1 0 0 0 0 0 1 1 0 1 110%! 1 0 0 0 1 1 0 0 1 1 111%! 1 1 1 1 0 1 0 1 1 0]; 112%! assert (bwperim (in), logical (out)) 113%! assert (bwperim (in, 4), logical (out)) 114%! 115%! out = [1 1 1 1 0 1 1 0 1 1 116%! 1 1 0 1 1 1 1 1 1 0 117%! 1 1 1 0 1 1 0 1 1 1 118%! 1 0 1 1 0 1 0 1 0 1 119%! 1 0 1 0 1 1 1 1 1 0 120%! 1 0 1 1 1 1 0 1 0 1 121%! 1 0 0 0 0 1 1 1 1 0 122%! 1 0 0 0 0 1 1 1 1 1 123%! 1 0 0 1 1 1 0 0 1 1 124%! 1 1 1 1 0 1 0 1 1 0]; 125%! assert (bwperim (in, 8), logical (out)) 126%! 127%! out = [1 1 1 1 0 1 1 0 1 1 128%! 1 0 0 0 0 1 0 0 1 0 129%! 1 0 0 0 0 0 0 1 0 1 130%! 1 0 1 0 0 0 0 0 0 1 131%! 1 0 0 0 0 1 0 1 0 0 132%! 1 0 0 0 1 0 0 0 0 1 133%! 1 0 0 0 0 0 0 1 0 0 134%! 1 0 0 0 0 1 1 0 0 1 135%! 1 0 0 1 0 1 0 0 1 1 136%! 1 1 1 1 0 1 0 1 1 0]; 137%! assert (bwperim (in, [1 0 0; 0 1 0; 0 0 1]), logical (out)) 138 139## test that any non-zero value is valid (even i and Inf) 140%!test 141%! in = [ 0 0 0 0 0 0 0 142%! 0 0 5 0 0 1 9 143%! 0 Inf 9 7 0 0 0 144%! 0 1.5 5 7 1 0 0 145%! 0 0.5 -1 89 i 0 0 146%! 0 4 10 15 1 0 0 147%! 0 0 0 0 0 0 0]; 148%! out = [0 0 0 0 0 0 0 149%! 0 0 1 0 0 1 1 150%! 0 1 0 1 0 0 0 151%! 0 1 0 0 1 0 0 152%! 0 1 0 0 1 0 0 153%! 0 1 1 1 1 0 0 154%! 0 0 0 0 0 0 0]; 155%! assert (bwperim (in), logical (out)) 156 157## test for 3D 158%!test 159%! in = reshape (magic(16), [8 8 4]) > 50; 160%! out(:,:,1) = [ 161%! 1 1 0 1 0 1 1 1 162%! 0 1 1 1 1 1 0 1 163%! 0 1 1 1 1 1 0 1 164%! 1 1 0 1 1 1 1 1 165%! 1 1 1 1 1 1 1 1 166%! 1 1 1 0 1 0 1 1 167%! 1 1 1 0 1 0 1 1 168%! 1 0 1 1 1 1 1 0]; 169%! out(:,:,2) = [ 170%! 1 1 0 1 0 1 1 1 171%! 0 1 1 0 1 1 0 1 172%! 0 1 0 0 0 1 0 1 173%! 1 0 1 0 0 0 1 1 174%! 1 0 0 1 0 1 0 1 175%! 1 0 1 0 1 0 1 1 176%! 1 1 1 0 1 0 1 1 177%! 1 0 1 1 1 1 1 0]; 178%! out(:,:,3) = [ 179%! 1 1 0 1 0 1 1 1 180%! 0 1 1 0 1 1 0 1 181%! 0 1 0 0 0 1 0 1 182%! 1 0 0 0 0 0 1 1 183%! 1 0 0 1 0 1 0 1 184%! 1 0 1 0 1 0 1 1 185%! 1 1 1 0 1 0 1 1 186%! 1 0 1 1 1 1 1 0]; 187%! out(:,:,4) = [ 188%! 1 1 0 1 0 1 1 1 189%! 0 1 1 1 1 1 0 1 190%! 0 1 1 1 1 1 0 1 191%! 1 1 1 1 1 1 1 1 192%! 1 1 1 1 1 1 1 0 193%! 1 1 1 0 1 0 1 1 194%! 1 1 1 0 1 0 1 1 195%! 1 0 1 1 1 1 1 0]; 196%! assert (bwperim (in), logical (out)) 197%! 198%! out(:,:,1) = [ 199%! 1 1 0 1 0 1 1 1 200%! 0 1 1 1 1 1 0 1 201%! 0 1 1 1 1 1 0 1 202%! 1 1 0 1 1 1 1 1 203%! 1 1 1 1 1 1 1 1 204%! 1 1 1 0 1 0 1 1 205%! 1 1 1 0 1 0 1 1 206%! 1 0 1 1 1 1 1 0]; 207%! out(:,:,2) = [ 208%! 1 1 0 1 0 1 1 1 209%! 0 1 1 1 1 1 0 1 210%! 0 1 1 0 0 1 0 1 211%! 1 1 1 1 0 1 1 1 212%! 1 0 1 1 1 1 1 1 213%! 1 0 1 0 1 0 1 1 214%! 1 1 1 0 1 0 1 1 215%! 1 0 1 1 1 1 1 0]; 216%! out(:,:,3) = [ 217%! 1 1 0 1 0 1 1 1 218%! 0 1 1 1 1 1 0 1 219%! 0 1 0 0 0 1 0 1 220%! 1 1 0 0 0 1 1 1 221%! 1 0 1 1 1 1 1 1 222%! 1 0 1 0 1 0 1 1 223%! 1 1 1 0 1 0 1 1 224%! 1 0 1 1 1 1 1 0]; 225%! out(:,:,4) = [ 226%! 1 1 0 1 0 1 1 1 227%! 0 1 1 1 1 1 0 1 228%! 0 1 1 1 1 1 0 1 229%! 1 1 1 1 1 1 1 1 230%! 1 1 1 1 1 1 1 0 231%! 1 1 1 0 1 0 1 1 232%! 1 1 1 0 1 0 1 1 233%! 1 0 1 1 1 1 1 0]; 234%! assert (bwperim (in, 18), logical (out)) 235 236%!error bwperim ("text") 237%!error bwperim (rand (10), 5) 238%!error bwperim (rand (10), "text") 239 240%!test 241%! a = false (5); 242%! a(1:4,2:4) = true; 243%! 244%! p = false (5); 245%! p(1:4,[2 4]) = true; 246%! assert (bwperim (a, [0 0 0; 1 1 1; 0 0 0]), p) 247 248## This is not a bug. Since connectivity defaults to maximum for the 249## number of dimensions, a single slice will be displayed completely 250## (this is obvious but very easy to forget) 251%!test 252%! a = false (8, 8, 5); 253%! a(4:5,4:5,2:4) = true; 254%! a(2:7,2:7,3) = true; 255%! assert (bwperim (a, 26), a) 256%! 257%! ## It is easy to forget that is correct 258%! b = a; 259%! b(4:5, 4:5, 3) = false; 260%! assert (bwperim (a), b) 261%! 262%! c = a; 263%! c(3:6,3:6,3) = false; 264%! assert (bwperim (a, 4), c) 265 266## test dimensions of length 1 (1x1, Nx1, etc) (bug #50153) 267%!test 268%! conn_self = logical ([0 0 0; 0 1 0; 0 0 0]); 269%! assert (bwperim (true), true) 270%! assert (bwperim (true, conn_self), false) 271%! assert (bwperim (true (1, 6)), true (1, 6)) 272%! assert (bwperim (true (1, 6), conn_self), false (1, 6)) 273%! assert (bwperim (true (6, 1)), true (6, 1)) 274%! 275%! bw_3d = true (1, 1, 6); 276%! assert (bwperim (bw_3d), bw_3d) 277%! assert (bwperim (bw_3d, conn_self), false (1, 1, 6)) 278%! assert (bwperim (bw_3d, true (3)), bw_3d) 279%! 280%! perim_3d = bw_3d; 281%! perim_3d(1, 1, 2:end-1) = false; 282%! conn_3d = false (3, 3, 3); 283%! conn_3d(2, 2, :) = true; 284%! assert (bwperim (true (1, 1, 6), conn_3d), perim_3d) 285