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