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 #include <string>
17 #include <typeinfo> // for an optimization using logical matrices
18 
19 #include <octave/Array.h>
20 #include <octave/Array-util.h>  // to get ind2sub
21 #include <octave/boolNDArray.h>
22 #include <octave/dim-vector.h>
23 #include <octave/lo-ieee.h>  // octave_Inf
24 
25 #include <octave/defun-dld.h>
26 #include <octave/defun-int.h>
27 #include <octave/error.h>
28 #include <octave/ovl.h>
29 
30 #include "strel.h"
31 
32 #define WANTS_OCTAVE_IMAGE_VALUE 1
33 #include "octave-wrappers.h"
34 
35 using namespace octave::image;
36 
37 // How this works:
38 //
39 // Erosion and dilation are simply minimum and maximum filters (in case of
40 // dilation, the filter needs to be reflected). We have a binary matrix as
41 // Structuring Element (SE) which slides through the image, and using the
42 // maximum or minimum value of those elements for the output matrix. The
43 // border of the matrix is considered to be +Inf or -Inf for the minimum
44 // (erosion) and maximum (dilation) respectively.
45 //
46 // We start by padding the input matrix accordingly to the requested shape
47 // (maybe this step could be avoided by doing the filtering in some different
48 // method around the borders of the matrix0.
49 //
50 // For performance (so we can use a pointer to access the data) while
51 // supporting ND matrices, we calculate the offset for all the points in the
52 // input matrix that affects a single point in the output. Note that as we
53 // slide through this offset values will always be the same.
54 //
55 // We could implement something more close to convn which is quite efficient
56 // but that requires to go through every element of the SE which would be a
57 // waste because not all elements in the SE will be true. Anyway, at least for
58 // binary images (and convn can only be used to do erosion and dilation of
59 // binary images), we already perform faster.
60 
61 // Pads the matrix MT with PADVAL, so it has the correct size to perform a
62 // spatial filtering with SE, for the requested SHAPE. The SHAPE argument
63 // is the same as in convn().
64 // FIXME: apparently it is not the same as convn(). Matlab seems to have
65 //        changed how this is done and will trim the SE, effectively changing
66 //        what its origin is. For example, requesting full erosion with the
67 //        following SE's will return the same
68 //
69 //              0 0 0
70 //              0 0 1         0 1
71 //              0 1 1         1 1
72 //
73 //        because in the first case, the first column and row are ignored. This
74 //        means that the size of output for full erosion will differ depending
75 //        on the SE.
76 template <class T>
77 static T
pad_matrix(const T & mt,const strel & se,const double & padval,const std::string & shape)78 pad_matrix (const T& mt, const strel& se,
79             const double& padval, const std::string& shape)
80 {
81   // If the shape is valid, we can return the input matrix.
82   if (shape == "valid")
83     return mt;
84 
85   const octave_idx_type ndims = mt.ndims ();
86   const Array<octave_idx_type> pre_pad  = se.pre_pad  (ndims, shape);
87   const Array<octave_idx_type> post_pad = se.post_pad (ndims, shape);
88 
89   dim_vector padded_size (mt.dims ());
90   for (octave_idx_type dim = 0; dim < ndims; dim++)
91     padded_size(dim) += pre_pad(dim) + post_pad(dim);
92   T padded (padded_size, padval);
93 
94   // Ammount of pre_pad is also how much the original must be shifted
95   // when inserting into the new padded matrix.
96   padded.insert (mt, pre_pad);
97 
98   return padded;
99 }
100 
101 // The general idea about the following is to look at each point for the
102 // output, one at a time, and evaluate all the points from the input. This
103 // at least allows us to skip many points in the case of binary images. For
104 // each output point we consider the one with same index in the input as
105 // "under" the SE element with index 0, and shift from that point to all the
106 // others. Then we move to the next point of output.
107 //
108 // SE:
109 //    0 1 1
110 //
111 // Input in:
112 //  0 1 0 0 1 0 0 1 0 1 1 0 0 1 1 1 1 1 0 0 1 0
113 //
114 // Input out:
115 //    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
116 //
117 // Output:
118 //    0 0 0 0 0 0 0 1 0 0 0 1 1 1 1 0 0 0 0 0
119 //
120 // Note that the output is shorter in size since we have already padded the
121 // input as appropriate for the requested shape. When we slide the SE over
122 // the input, its center (origin) shows what value will be on the output. But
123 // we won't actually use the center of the SE, only the first element and the
124 // distance from it. This means that in this example, the NNZ elements of the
125 // SE will have an offset of 1 and 2.
126 // We match the first element of output with the first from output, and shift
127 // their offset values to move to all other points in the input and assign
128 // them to the output.
129 //
130 // To deal with N dimensional images we have the cumulative dimensions of the
131 // input matrix, e.g. for 10x20x4x5 matrix, this would be the array
132 // [10 200 800 4000]. This is how much we must shift the pointer in the input
133 // matrix to get to the next value in a specific dimension. For example, to get
134 // to the second column we would add 10*(2-1) to the input matrix. To get to
135 // element 4 of the 3rd dimension we would add 200*(4-3). So we use this with
136 // recursion, and adding to the pointer of the input matrix until we only
137 // have a column to erode).
138 
139 // The values of erosion and isflat come as template since they are checked
140 // at the deepest of the loop. By using a template instead of function
141 // argument, it's all done at compile time so we get better performance.
142 // If erosion is false, we perform dilation instead
143 template<class P, bool erosion, bool flat>
144 inline static void
erode_line(const P * in,P * out,const octave_idx_type * offsets,const P * height,const octave_idx_type & nnz,const octave_idx_type & line_length)145 erode_line (const P* in, P* out, const octave_idx_type* offsets, const P* height,
146             const octave_idx_type& nnz, const octave_idx_type& line_length)
147 {
148   for (octave_idx_type line_idx = 0; line_idx < line_length; line_idx++)
149     {
150       for (octave_idx_type nnz_idx = 0; nnz_idx < nnz; nnz_idx++)
151         {
152           if (flat)
153             {
154               if (erosion)
155                 {
156                   if (in[offsets[nnz_idx]] < out[line_idx])
157                     out[line_idx] = in[offsets[nnz_idx]];
158                 }
159               else
160                 {
161                   if (in[offsets[nnz_idx]] > out[line_idx])
162                     out[line_idx] = in[offsets[nnz_idx]];
163                 }
164             }
165           else
166             {
167               // If non-flat, there is no need to check if typeid is boolean
168               // since non-flat makes no sense for binary images.
169               if (erosion)
170                 {
171                   P val = in[offsets[nnz_idx]] - height[nnz_idx];
172                   if (val < out[line_idx])
173                     out[line_idx] = val;
174                 }
175               else
176                 {
177                   P val = in[offsets[nnz_idx]] + height[nnz_idx];
178                   if (val > out[line_idx])
179                     out[line_idx] = val;
180                 }
181             }
182         }
183       in++;
184     }
185 }
186 
187 // For the specific case of boolean dilation/erosion, we may be able to
188 // break from the loop sooner.  Also, there is non-flat binary erosion
189 // and dilation.
190 template<class P, bool erosion, bool flat>
191 inline static void
erode_line(const bool * in,bool * out,const octave_idx_type * offsets,const bool * height,const octave_idx_type & nnz,const octave_idx_type & line_length)192 erode_line (const bool* in, bool* out, const octave_idx_type* offsets, const bool* height,
193             const octave_idx_type& nnz, const octave_idx_type& line_length)
194 {
195   for (octave_idx_type line_idx = 0; line_idx < line_length; line_idx++)
196     {
197       for (octave_idx_type nnz_idx = 0; nnz_idx < nnz; nnz_idx++)
198         {
199           if (erosion)
200             {
201               if (! in[offsets[nnz_idx]])
202                 {
203                   out[line_idx] = false;
204                   break;
205                 }
206             }
207           else
208             {
209               if (in[offsets[nnz_idx]])
210                 {
211                   out[line_idx] = true;
212                   break;
213                 }
214             }
215         }
216       in++;
217     }
218 }
219 
220 template<class P, bool erosion, bool flat>
221 static void
erode_nd(const P * in,const dim_vector & in_cd,P * out,const dim_vector & out_cd,const dim_vector & out_d,const octave_idx_type * offsets,const P * height,const octave_idx_type & nnz,const octave_idx_type & dim)222 erode_nd (const P* in, const dim_vector& in_cd,
223           P* out, const dim_vector& out_cd, const dim_vector& out_d,
224           const octave_idx_type* offsets, const P* height,
225           const octave_idx_type& nnz, const octave_idx_type& dim)
226 {
227   if (dim == 0)
228     erode_line<P, erosion, flat> (in, out, offsets, height, nnz, out_d(0));
229   else
230     for (octave_idx_type elem = 0; elem < out_d(dim); elem++)
231       erode_nd<P, erosion, flat> (in  + in_cd(dim-1) * elem, in_cd,
232                                   out + out_cd(dim-1)* elem, out_cd, out_d,
233                                   offsets, height, nnz, dim -1);
234   OCTAVE_QUIT;
235 }
236 
237 template<class T>
238 static octave_value
erode(const T & im,const strel & se,const std::string & shape,const bool & erosion)239 erode (const T& im, const strel& se, const std::string& shape, const bool& erosion)
240 {
241   typedef typename T::element_type P;
242 
243   // If image is empty, return empty of the same class.
244   if (octave_image::value (im).isempty ())
245     return octave_value (im);
246 
247   // In the case of floating point, complex and integers numbers, both
248   // octave_Inf and -octave_Inf actually become that type max and min value.
249   // However, for boolean, both of them are converted to "true" so for
250   // dilation, where we want false, we check the type.
251   T padded;
252   if (erosion)
253     padded = pad_matrix<T> (im, se, octave_Inf, shape);
254   else
255     {
256       if (typeid (P) == typeid (bool))
257         padded = pad_matrix<T> (im, se, false, shape);
258       else
259         padded = pad_matrix<T> (im, se, -octave_Inf, shape);
260     }
261 
262   const boolNDArray nhood = se.get_nhood ();
263 
264   const octave_idx_type ndims   = padded.ndims ();
265   const dim_vector nhood_size   = nhood.dims ().redim (ndims);
266   const dim_vector padded_size  = padded.dims ();
267 
268   const dim_vector cum_size = padded_size.cumulative ();
269   const Array<octave_idx_type> offsets = se.offsets (cum_size);
270 
271   const Array<P> heights = se.true_heights<P> ();
272 
273   const bool flat = se.flat ();
274 
275   if (typeid (P) == typeid (bool) && ! flat)
276     error ("only non flat structuring elements for binary images");
277 
278   dim_vector out_size (padded_size);
279   for (octave_idx_type i = 0; i < ndims; i++)
280     out_size(i) -= nhood_size(i) - 1;
281 
282   T out;
283 
284   // When there's only a single neighbor on the SE, then we will only shift
285   // the matrix by its distance to the origin of the SE.
286   if (se.get_nnz () == 1)
287     {
288       octave_idx_type ind = nhood.find (1)(0);
289       Array<idx_vector> sub = ind2sub (nhood_size, idx_vector (ind));
290 
291       Array<idx_vector> ranges (dim_vector (ndims, 1));
292       for (octave_idx_type dim = 0; dim < ndims; dim++)
293         {
294           octave_idx_type start (sub(dim)(0));
295           octave_idx_type limit (start + out_size(dim));
296           ranges(dim) = idx_vector (start, limit);
297         }
298       out = padded.index (ranges);
299     }
300   else
301     {
302       if (erosion)
303         out = T (out_size, octave_Inf);
304       else
305         if (typeid (P) == typeid (bool))
306           out = T (out_size, false);
307         else
308           out = T (out_size, -octave_Inf);
309 
310       if (flat)
311         if (erosion)
312           erode_nd<P, true, true> (padded.data (), cum_size, out.fortran_vec (),
313             out_size.cumulative (), out_size, offsets.data (), heights.data (),
314             offsets.numel (), ndims -1);
315         else
316           erode_nd<P, false, true> (padded.data (), cum_size, out.fortran_vec (),
317             out_size.cumulative (), out_size, offsets.data (), heights.data (),
318             offsets.numel (), ndims -1);
319 
320       else
321         if (erosion)
322           erode_nd<P, true, false> (padded.data (), cum_size, out.fortran_vec (),
323             out_size.cumulative (), out_size, offsets.data (), heights.data (),
324             offsets.numel (), ndims -1);
325         else
326           erode_nd<P, false, false> (padded.data (), cum_size, out.fortran_vec (),
327             out_size.cumulative (), out_size, offsets.data (), heights.data (),
328             offsets.numel (), ndims -1);
329     }
330   return octave_value (out);
331 }
332 
333 static octave_value
base_action(const std::string & func,const bool & erosion,const octave_value_list & args)334 base_action (const std::string& func, const bool& erosion, const octave_value_list& args)
335 {
336   octave_value retval;
337   const octave_idx_type nargin = args.length ();
338   if (nargin < 2 || nargin > 4)
339     print_usage (func);
340 
341   // Default shape is "same"
342   const std::string shape = nargin > 2? args(2).string_value () : "same";
343 
344   strel se (args(1));
345   if (! erosion) // must be dilation, then get the se reflection
346     se = se.reflect ();
347 
348   octave_image::value im (args(0));
349   for (octave_idx_type idx = 0; idx < se.numel (); idx++)
350     {
351       const strel se_elem = se(idx);
352       if (im.islogical ())
353         im = erode<boolNDArray> (im.bool_array_value (), se_elem, shape, erosion);
354       else if (im.is_int8_type ())
355         im = erode<int8NDArray> (im.int8_array_value (), se_elem, shape, erosion);
356       else if (im.is_int16_type ())
357         im = erode<int16NDArray> (im.int16_array_value (), se_elem, shape, erosion);
358       else if (im.is_int32_type ())
359         im = erode<int32NDArray> (im.int32_array_value (), se_elem, shape, erosion);
360       else if (im.is_int64_type ())
361         im = erode<int64NDArray> (im.int64_array_value (), se_elem, shape, erosion);
362       else if (im.is_uint8_type ())
363         im = erode<uint8NDArray> (im.uint8_array_value (), se_elem, shape, erosion);
364       else if (im.is_uint16_type ())
365         im = erode<uint16NDArray> (im.uint16_array_value (), se_elem, shape, erosion);
366       else if (im.is_uint32_type ())
367         im = erode<uint32NDArray> (im.uint32_array_value (), se_elem, shape, erosion);
368       else if (im.is_uint64_type ())
369         im = erode<uint64NDArray> (im.uint64_array_value (), se_elem, shape, erosion);
370       else if (im.isreal ())
371         if (im.is_single_type ())
372           im = erode<FloatNDArray> (im.float_array_value (), se_elem, shape, erosion);
373         else // must be double
374           im = erode<NDArray> (im.array_value (), se_elem, shape, erosion);
375       else if (im.iscomplex ())
376         if (im.is_single_type ())
377           im = erode<FloatComplexNDArray> (im.float_complex_array_value (), se_elem, shape, erosion);
378         else // must be double
379           im = erode<ComplexNDArray> (im.complex_array_value (), se_elem, shape, erosion);
380       else
381         im = octave_value ();
382     }
383 
384   return im;
385 }
386 
387 DEFUN_DLD(imerode, args, , "\
388 -*- texinfo -*-\n\
389 @deftypefn  {Loadable Function} {} imerode (@var{im}, @var{SE})\n\
390 @deftypefnx {Loadable Function} {} imerode (@var{im}, @var{SE}, @var{shape})\n\
391 Perform morphological erosion.\n\
392 \n\
393 The image @var{im} must be a numeric matrix with any number of dimensions.\n\
394 The erosion is performed with the structuring element @var{se} which can\n\
395 be a:\n\
396 \n\
397 @itemize @bullet\n\
398 @item strel object;\n\
399 @item array of strel objects as returned by @code{@@strel/getsequence};\n\
400 @item matrix of 0's and 1's.\n\
401 @end itemize\n\
402 \n\
403 To perform a non-flat erosion, @var{SE} must be a strel object.\n\
404 \n\
405 The size of the result is determined by the optional @var{shape} argument\n\
406 which takes the following values:\n\
407 \n\
408 @table @asis\n\
409 @item @qcode{\"same\"} (default)\n\
410 Return image of the same size as input @var{im}.\n\
411 \n\
412 @item @qcode{\"full\"}\n\
413 Return the full erosion (image is padded to accommodate @var{se} near the\n\
414 borders).\n\
415 \n\
416 @item @qcode{\"valid\"}\n\
417 Return only the parts which do not include the padded edges.\n\
418 @end table\n\
419 \n\
420 In case of a @var{SE} with a size of even length, the center is considered\n\
421 at indices @code{floor ([size(@var{SE})/2] + 1)}.\n\
422 \n\
423 @seealso{imdilate, imopen, imclose, strel}\n\
424 @end deftypefn")
425 {
426   return base_action ("imerode", true, args);
427 }
428 
429 /*
430 ## using [1] as mask returns the same value
431 %!assert (imerode (eye (3), [1]), eye (3));
432 ## and an empty SE returns all Inf
433 %!assert (imerode (eye (3), []), Inf (3, 3));
434 
435 ## test normal usage with non-symmetric SE
436 %!test
437 %! im = [0 1 0
438 %!       1 1 1
439 %!       0 1 0];
440 %! se = [1 0 0
441 %!       0 1 0
442 %!       0 1 1];
443 %! assert (imerode (im,          se),          [0 1 0; 0 0 0; 0 1 0]);
444 %! assert (imerode (logical(im), se), logical ([0 1 0; 0 0 0; 0 1 0]));
445 %! assert (imerode (im, se, "full"),
446 %!                 [  0    0    0    0  Inf
447 %!                    1    0    1    0  Inf
448 %!                    0    0    0    0    0
449 %!                  Inf    0    1    0    1
450 %!                  Inf  Inf    0    1    0]);
451 %! assert (imerode (logical(im), se, "full"),
452 %!                 logical([0     0     0     0     1
453 %!                          1     0     1     0     1
454 %!                          0     0     0     0     0
455 %!                          1     0     1     0     1
456 %!                          1     1     0     1     0]));
457 
458 %!test
459 %! a = rand ([10 40 15 6 8 5]) > 0.2;
460 %! se = ones ([5 3 7]);
461 %!
462 %! ## the image is not really indexed but this way it is padded with 1s
463 %! assert (imerode (a, se), colfilt (a, "indexed", size (se), "sliding", @all))
464 %!
465 %! assert (imerode (a, se, "valid"), convn (a, se, "valid") == nnz (se))
466 %! ## again, we need to pad it ourselves because convn pads with zeros
467 %! b = true (size (a) + [4 2 6 0 0 0]);
468 %! b(3:12, 2:41, 4:18,:,:,:) = a;
469 %! assert (imdilate (b, se, "same"), convn (b, se, "same") > 0)
470 %! b = true (size (a) + [8 4 12 0 0 0]);
471 %! b(5:14, 3:42, 7:21,:,:,:) = a;
472 %! assert (imdilate (b, se, "full"), convn (b, se, "full") > 0)
473 
474 %!test
475 %! im = [0 0 0 0 0 0 0
476 %!       0 0 1 0 1 0 0
477 %!       0 0 1 1 0 1 0
478 %!       0 0 1 1 1 0 0
479 %!       0 0 0 0 0 0 0];
480 %! se = [0 0 0
481 %!       0 1 0
482 %!       0 1 1];
483 %! out = [0 0 0 0 0 0 0
484 %!        0 0 1 0 0 0 0
485 %!        0 0 1 1 0 0 0
486 %!        0 0 0 0 0 0 0
487 %!        0 0 0 0 0 0 0];
488 %! assert (imerode (im, se), out);
489 %! assert (imerode (logical (im), se), logical (out));
490 %! assert (imerode (im, logical (se)), out);
491 %! assert (imerode (logical (im), logical (se)), logical (out));
492 %!
493 %! # with an even-size SE
494 %! se =  [0 0 0 1
495 %!        0 1 0 0
496 %!        0 1 1 1];
497 %! out = [0 0 0 0 0 0 0
498 %!        0 0 0 0 0 0 0
499 %!        0 0 1 0 0 0 0
500 %!        0 0 0 0 0 0 0
501 %!        0 0 0 0 0 0 0];
502 %! assert (imerode (im, se), out);
503 %! out = [ 0 0 0 0 1 0 1
504 %!        0 0 1 0 1 1 0
505 %!        0 0 1 1 1 1 1
506 %!        0 0 1 1 1 1 1
507 %!        0 0 1 1 1 1 1];
508 %! assert (imdilate (im, se), out);
509 
510 ## normal usage for grayscale images
511 %!test
512 %! a = [ 82    2   97   43   79   43   41   65   51   11
513 %!       60   65   21   56   94   77   36   38   75   39
514 %!       32   68   78    1   16   75   76   90   81   56
515 %!       43   90   82   41   36    1   87   19   18   63
516 %!       63   64    2   48   18   43   38   25   22   99
517 %!       12   46   90   79    3   92   39   79   10   22
518 %!       38   98   11   10   40   90   88   38    4   76
519 %!       54   37    9    4   33   98   36   47   53   57
520 %!       38   76   82   50   14   74   64   99    7   33
521 %!       88   96   41   62   84   89   97   23   41    3];
522 %!
523 %! domain = ones (3);
524 %! out = [  2    1    1    1   16   36   36   11
525 %!         21    1    1    1    1    1   18   18
526 %!          2    1    1    1    1    1   18   18
527 %!          2    2    2    1    1    1   10   10
528 %!          2    2    2    3    3   25    4    4
529 %!          9    4    3    3    3   36    4    4
530 %!          9    4    4    4   14   36    4    4
531 %!          9    4    4    4   14   23    7    3];
532 %! assert (imerode (a, domain, "valid"), out);
533 %! assert (imerode (uint8 (a), domain, "valid"), uint8 (out));
534 %! assert (imerode (uint8 (a), strel ("arbitrary", domain), "valid"), uint8 (out));
535 %! assert (imerode (uint8 (a), strel ("square", 3), "valid"), uint8 (out));
536 %!
537 %!## Test for non-flat strel
538 %! assert (imerode (a, strel ("arbitrary", domain, ones (3)), "valid"), out -1);
539 %!
540 %! out = [ 97   97   97   94   94   90   90   90
541 %!         90   90   94   94   94   90   90   90
542 %!         90   90   82   75   87   90   90   99
543 %!         90   90   90   92   92   92   87   99
544 %!         98   98   90   92   92   92   88   99
545 %!         98   98   90   98   98   98   88   79
546 %!         98   98   82   98   98   99   99   99
547 %!         96   96   84   98   98   99   99   99];
548 %! assert (imdilate (a, domain, "valid"), out);
549 %! assert (imdilate (uint8 (a), domain, "valid"), uint8 (out));
550 %!
551 %!## Test for non-flat strel
552 %! assert (imdilate (a, strel ("arbitrary", domain, ones (3)), "valid"), out +1);
553 %!
554 %! ## test while using SE that can be decomposed and an actual sequence
555 %! domain = ones (5);
556 %! out = [   2   1   1   1   1   1  16  11  11  11
557 %!           2   1   1   1   1   1   1   1  11  11
558 %!           2   1   1   1   1   1   1   1  11  11
559 %!           2   1   1   1   1   1   1   1  10  10
560 %!           2   1   1   1   1   1   1   1   4   4
561 %!           2   2   2   1   1   1   1   1   4   4
562 %!           2   2   2   2   2   3   3   4   4   4
563 %!           9   4   3   3   3   3   3   3   3   3
564 %!           9   4   4   4   4   4   4   3   3   3
565 %!           9   4   4   4   4   4   7   3   3   3];
566 %! assert (imerode (a, domain), out);
567 %! assert (imerode (a, strel ("square", 5)), out);
568 %! assert (imerode (a, getsequence (strel ("square", 5))), out);
569 %!
570 %! ## using a non-symmetric SE
571 %! domain = [ 1 1 0
572 %!            0 1 1
573 %!            0 1 0];
574 %!
575 %! out = [  2    2    1   16   36   36   38   39
576 %!         60    1    1   16    1   36   19   18
577 %!         32    2    1    1    1   19   18   18
578 %!          2    2   18    3    1    1   19   10
579 %!         46    2    2    3   18   38   10    4
580 %!         11    9    4    3    3   36    4    4
581 %!          9    4    4   10   36   36   38    4
582 %!         37    9    4    4   33   36    7    7];
583 %! assert (imerode (a, domain, "valid"), out);
584 %! assert (imerode (a, strel ("arbitrary", domain, ones (3)), "valid"), out -1);
585 %!
586 %! out = [ 78   97   56   94   94   90   90   81
587 %!         90   82   78   94   87   87   90   90
588 %!         90   90   82   43   75   87   90   99
589 %!         90   90   79   92   92   87   79   25
590 %!         98   90   90   90   92   92   79   79
591 %!         98   98   79   98   98   90   88   57
592 %!         98   82   50   74   98   99   99   53
593 %!         96   82   84   89   98   97   99   99];
594 %! assert (imdilate (a, domain, "valid"), out);
595 %! assert (imdilate (a, strel ("arbitrary", domain, ones (3)), "valid"), out +1);
596 
597 // Tests for N-dimensions
598 %!test
599 %! im = reshape (magic(16), [4 8 4 2]);
600 %! se = true (3, 3, 3);
601 %! out = zeros (4, 8, 4, 2);
602 %! out(:,:,1,1) = [
603 %!     3   3  46   2   2   2  47  47
604 %!     3   3  30   2   2   2  31  31
605 %!    17  17  16  16  16  20  13  13
606 %!    33  33  16  16  16  36  13  13];
607 %! out(:,:,2,1) = [
608 %!     3   3  46   2   2   2  43  43
609 %!     3   3  30   2   2   2  27  27
610 %!    17  17  12  12  12  20  13  13
611 %!    33  33  12  12  12  36  13  13];
612 %! out(:,:,3,1) = [
613 %!     3   3  42   6   6   6  43  43
614 %!     3   3  26   6   6   6  27  27
615 %!    21  21  12  12  12  20   9   9
616 %!    37  37  12  12  12  36   9   9];
617 %! out(:,:,4,1) = [
618 %!     7   7  42   6   6   6  43  43
619 %!     7   7  26   6   6   6  27  27
620 %!    21  21  12  12  12  24   9   9
621 %!    37  37  12  12  12  40   9   9];
622 %! out(:,:,1,2) = [
623 %!    11  11  38  10  10  10  39  39
624 %!    11  11  22  10  10  10  23  23
625 %!    25  25   8   8   8  28   5   5
626 %!    41  41   8   8   8  44   5   5];
627 %! out(:,:,2,2) = [
628 %!    11  11  38  10  10  10  35  35
629 %!    11  11  22  10  10  10  19  19
630 %!    25  25   4   4   4  28   5   5
631 %!    41  41   4   4   4  44   5   5];
632 %! out(:,:,3,2) = [
633 %!    11  11  34  14  14  14  35  35
634 %!    11  11  18  14  14  14  19  19
635 %!    29  29   4   4   4  28   1   1
636 %!    45  45   4   4   4  44   1   1];
637 %! out(:,:,4,2) = [
638 %!    15  15  34  14  14  14  35  35
639 %!    15  15  18  14  14  14  19  19
640 %!    29  29   4   4   4  32   1   1
641 %!    45  45   4   4   4  48   1   1];
642 %! assert (imerode (im, se), out);
643 %! assert (imerode (uint16 (im), se), uint16 (out));
644 %!
645 %! ## trying a more weird SE
646 %! se(:,:,1) = [1 0 1; 0 1 1; 0 0 0];
647 %! se(:,:,3) = [1 0 1; 0 1 1; 0 0 1];
648 %! out(:,:,1,1) = [
649 %!    3  17  46   2   2   2  47  47
650 %!   17   3  30   2   2   2  31  31
651 %!   17  17  16  16  16  20  13  31
652 %!   33  33  16  16  16  36  13  13];
653 %! out(:,:,2,1) = [
654 %!    3   3  46   2   2  20  43  61
655 %!    3   3  30   2  20   2  27  43
656 %!   33  17  12  20  20  20  13  13
657 %!   51  33  12  12  30  36  13  13];
658 %! out(:,:,3,1) = [
659 %!    3  21  42   6   6   6  43  43
660 %!   21   3  26   6   6   6  27  27
661 %!   21  21  12  12  12  20   9  27
662 %!   37  37  12  12  12  36   9   9];
663 %! out(:,:,4,1) = [
664 %!    7   7  42   6   6  24  57  57
665 %!    7   7  26   6  24   6  43  43
666 %!   37  21  26  24  24  24   9   9
667 %!   55  37  12  12  26  40   9   9];
668 %! out(:,:,1,2) = [
669 %!   11  25  38  10  10  10  39  39
670 %!   25  11  22  10  10  10  23  23
671 %!   25  25   8   8   8  28   5  23
672 %!   41  41   8   8   8  44   5   5];
673 %! out(:,:,2,2) = [
674 %!   11  11  38  10  10  28  35  53
675 %!   11  11  22  10  22  10  19  35
676 %!   41  25   4  22  22  28   5   5
677 %!   59  41   4   4  22  44   5   5];
678 %! out(:,:,3,2) = [
679 %!   11  29  34  14  14  14  35  35
680 %!   29  11  18  14  14  14  19  19
681 %!   29  29   4   4   4  28   1  19
682 %!   45  45   4   4   4  44   1   1];
683 %! out(:,:,4,2) = [
684 %!   15  15  34  14  14  32  49  49
685 %!   15  15  18  14  18  14  35  35
686 %!   45  29  18  18  18  32   1   1
687 %!   63  45   4   4  18  48   1   1];
688 %! assert (imerode (im, se), out);
689 %! assert (imerode (uint16 (im), se), uint16 (out));
690 
691 ## Test input check
692 %!error imerode (ones (10), 45)
693 %!error imerode (ones (10), "some text")
694 %!error imerode (ones (10), {23, 45})
695 
696 ## No binary erosion for non-flat strel
697 %!error imerode (rand (10) > 10 , strel ("arbitrary", true (3), ones (3)))
698 */
699 
700 // PKG_ADD: autoload ("imdilate", which ("imerode"));
701 // PKG_DEL: autoload ("imdilate", which ("imerode"), "remove");
702 DEFUN_DLD(imdilate, args, , "\
703 -*- texinfo -*-\n\
704 @deftypefn  {Loadable Function} {} imdilate (@var{im}, @var{SE})\n\
705 @deftypefnx {Loadable Function} {} imdilate (@var{im}, @var{SE}, @var{shape})\n\
706 Perform morphological dilation.\n\
707 \n\
708 The image @var{im} must be a numeric matrix with any number of dimensions.\n\
709 The dilation is performed with the structuring element @var{se} which can\n\
710 be a:\n\
711 \n\
712 @itemize @bullet\n\
713 @item strel object;\n\
714 @item array of strel objects as returned by @code{@@strel/getsequence};\n\
715 @item matrix of 0's and 1's.\n\
716 @end itemize\n\
717 \n\
718 To perform a non-flat dilation, @var{SE} must be a strel object.\n\
719 \n\
720 The size of the result is determined by the optional @var{shape} argument\n\
721 which takes the following values:\n\
722 \n\
723 @table @asis\n\
724 @item @qcode{\"same\"} (default)\n\
725 Return image of the same size as input @var{im}.\n\
726 \n\
727 @item @qcode{\"full\"}\n\
728 Return the full dilation (matrix is padded to accommodate @var{se} near the\n\
729 borders).\n\
730 \n\
731 @item @qcode{\"valid\"}\n\
732 Return only the parts which do not include the padded edges.\n\
733 @end table\n\
734 \n\
735 In case of a @var{SE} with a size of even length, the center is considered\n\
736 at indices @code{floor ([size(@var{SE})/2] + 1)}.\n\
737 \n\
738 @seealso{imerode, imopen, imclose}\n\
739 @end deftypefn")
740 {
741   return base_action ("imdilate", false, args);
742 }
743 
744 /*
745 // Tests for N-dimensions
746 
747 %!test
748 %! a = rand ([10 40 15 6 8 5]) > 0.8;
749 %! se = ones ([5 3 7]);
750 %! assert (imdilate (a, se), convn (a, se, "same") > 0)
751 %! assert (imdilate (a, se, "full"), convn (a, se, "full") > 0)
752 %! assert (imdilate (a, se, "valid"), convn (a, se, "valid") > 0)
753 %! assert (imdilate (a, se), colfilt (a, size (se), "sliding", @any))
754 
755 %!test
756 %! im = reshape (magic(16), [4 8 4 2]);
757 %! se = true (3, 3, 3);
758 %! out = zeros (4, 8, 4, 2);
759 %!
760 %! out(:,:,1,1) = [
761 %!   256   256   209   253   253   253   212   212
762 %!   256   256   225   253   253   253   228   228
763 %!   238   238   243   243   243   239   242   242
764 %!   222   222   243   243   243   223   242   242];
765 %! out(:,:,2,1) = [
766 %!   256   256   213   253   253   253   212   212
767 %!   256   256   229   253   253   253   228   228
768 %!   238   238   243   243   243   239   246   246
769 %!   222   222   243   243   243   223   246   246];
770 %! out(:,:,3,1) = [
771 %!   252   252   213   253   253   253   216   216
772 %!   252   252   229   253   253   253   232   232
773 %!   238   238   247   247   247   235   246   246
774 %!   222   222   247   247   247   219   246   246];
775 %! out(:,:,4,1) = [
776 %!   252   252   213   249   249   249   216   216
777 %!   252   252   229   249   249   249   232   232
778 %!   234   234   247   247   247   235   246   246
779 %!   218   218   247   247   247   219   246   246];
780 %! out(:,:,1,2) = [
781 %!   248   248   217   245   245   245   220   220
782 %!   248   248   233   245   245   245   236   236
783 %!   230   230   251   251   251   231   250   250
784 %!   214   214   251   251   251   215   250   250];
785 %! out(:,:,2,2) = [
786 %!   248   248   221   245   245   245   220   220
787 %!   248   248   237   245   245   245   236   236
788 %!   230   230   251   251   251   231   254   254
789 %!   214   214   251   251   251   215   254   254];
790 %! out(:,:,3,2) = [
791 %!   244   244   221   245   245   245   224   224
792 %!   244   244   237   245   245   245   240   240
793 %!   230   230   255   255   255   227   254   254
794 %!   214   214   255   255   255   211   254   254];
795 %! out(:,:,4,2) = [
796 %!   244   244   221   241   241   241   224   224
797 %!   244   244   237   241   241   241   240   240
798 %!   226   226   255   255   255   227   254   254
799 %!   210   210   255   255   255   211   254   254];
800 %! assert (imdilate (im, se), out);
801 %! assert (imdilate (uint16 (im), se), uint16 (out));
802 %!
803 %! ## trying a more weird SE
804 %! se(:,:,1) = [1 0 1; 0 1 1; 0 0 0];
805 %! se(:,:,3) = [1 0 1; 0 1 1; 0 0 1];
806 %! out(:,:,1,1) = [
807 %!  256   256   209   239   253   253   212   194
808 %!  256   256   225   239   239   239   228   212
809 %!  222   222   243   239   243   239   242   242
810 %!  208   208   225   243   243   223   242   242];
811 %! out(:,:,2,1) = [
812 %!  256   256   213   253   253   253   212   212
813 %!  238   256   229   253   253   253   228   228
814 %!  238   238   243   243   243   239   246   228
815 %!  222   222   243   243   243   223   228   246];
816 %! out(:,:,3,1) = [
817 %!  252   252   213   235   253   253   216   198
818 %!  252   252   229   235   235   253   232   216
819 %!  222   238   247   235   247   235   246   246
820 %!  204   222   229   247   247   219   246   246];
821 %! out(:,:,4,1) = [
822 %!  252   252   213   249   249   249   216   216
823 %!  234   252   229   249   249   249   232   232
824 %!  234   234   247   247   247   235   246   232
825 %!  218   218   247   247   247   219   232   246];
826 %! out(:,:,1,2) = [
827 %!  248   248   217   231   245   245   220   202
828 %!  248   248   233   233   233   231   236   220
829 %!  214   214   251   233   251   231   250   250
830 %!  200   200   233   251   251   215   250   250];
831 %! out(:,:,2,2) = [
832 %!  248   248   221   245   245   245   220   220
833 %!  230   248   237   245   245   245   236   236
834 %!  230   230   251   251   251   231   254   236
835 %!  214   214   251   251   251   215   236   254];
836 %! out(:,:,3,2) = [
837 %!  244   244   221   227   245   245   224   206
838 %!  244   244   237   237   237   245   240   224
839 %!  214   230   255   237   255   227   254   254
840 %!  196   214   237   255   255   211   254   254];
841 %! out(:,:,4,2) = [
842 %!  244   244   221   241   241   241   224   224
843 %!  226   244   237   241   241   241   240   240
844 %!  226   226   255   255   255   227   254   240
845 %!  210   210   255   255   255   211   240   254];
846 %! assert (imdilate (im, se), out);
847 %! assert (imdilate (uint16 (im), se), uint16 (out));
848 */
849 
850 /*
851 ## bug #47879 (invalid but mathematically interesting corner-case)
852 ## This is all about empty sets, either by using a blank/empty/zeros SE
853 ## or by picking a SE that "picks" elements only from the borders.  These
854 ## are two completely different issues that may look the same.  See a more
855 ## detailed explanation at http://stackoverflow.com/a/37117842/1609556
856 
857 %!test    # scalar blank SE
858 %! se = 0;
859 %! assert (imerode (5, se), Inf)
860 %! assert (imerode (true, se), true)
861 %! assert (imerode (false, se), true)
862 %! assert (imerode (uint8 (3), se), uint8 (255))
863 %!
864 %! assert (imdilate (5, se), -Inf)
865 %! assert (imdilate (true, se), false)
866 %! assert (imdilate (false, se), false)
867 %! assert (imdilate (uint8 (3), se), uint8 (0))
868 
869 %!test    # empty SE
870 %! se = [];
871 %! assert (imerode (5, se), Inf)
872 %! assert (imerode (true, se), true)
873 %! assert (imerode (false, se), true)
874 %! assert (imerode (uint8 (3), se), uint8 (255))
875 %!
876 %! assert (imdilate (5, se), -Inf)
877 %! assert (imdilate (true, se), false)
878 %! assert (imdilate (false, se), false)
879 %! assert (imdilate (uint8 (3), se), uint8 (0))
880 
881 %!test    # non-scalar blank SE
882 %! se = zeros (3, 3);
883 %! assert (imerode (5, se), Inf)
884 %! assert (imerode (true, se), true)
885 %! assert (imerode (false, se), true)
886 %! assert (imerode (uint8 (3), se), uint8 (255))
887 %!
888 %! assert (imdilate (5, se), -Inf)
889 %! assert (imdilate(true, se), false)
890 %! assert (imdilate (false, se), false)
891 %! assert (imdilate (uint8 (3), se), uint8 (0))
892 
893 %!test    # erode only with out-of-border elements
894 %! se = [1 1 1; 1 0 1; 1 1 1];
895 %! assert (imerode (5, se), Inf)
896 %! assert (imerode (true, se), true)
897 %!
898 %! assert (imdilate (5, se), -Inf)
899 %! assert (imdilate (true, se), false)
900 
901 %!test    # only true elements of SE are out-of-border
902 %! se = [0 0 0; 1 0 0; 1 1 0];
903 %! assert (imerode (zeros (3), se), [0 0 0; 0 0 0; Inf 0 0])
904 %! assert (imerode (false (3), se), logical ([0 0 0; 0 0 0; 1 0 0]))
905 %! assert (imdilate (zeros (3), se), [0 0 -Inf; 0 0 0; 0 0 0])
906 %! assert (imdilate (false (3), se), false (3, 3))
907 %!
908 %! se = [0 0 0; 0 0 0; 1 1 1];
909 %! assert (imerode (zeros (3, 3), se), [0 0 0; 0 0 0; Inf Inf Inf])
910 %! assert (imerode (false (3, 3), se), logical ([0 0 0; 0 0 0; 1 1 1]))
911 %! assert (imdilate (zeros (3, 3), se), [-Inf -Inf -Inf; 0 0 0; 0 0 0])
912 %! assert (imdilate (false (3, 3), se), false (3, 3))
913 
914 %!test  # only true elements of even-sized SE are out-of-border
915 %! se = logical ([0 1; 1 1]);
916 %! assert (imerode (false (3, 3), se), logical ([0 0 0; 0 0 0; 0 0 1]))
917 %! assert (imerode (zeros (3, 3), se), [0 0 0; 0 0 0; 0 0 Inf])
918 %!
919 %! assert (imdilate (false (3, 3), se), false (3, 3))
920 %! assert (imdilate (zeros (3, 3), se), [-Inf 0 0; 0 0 0; 0 0 0])
921 */
922