1 // Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
2 //
3 // This program is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU General Public License as
5 // published by the Free Software Foundation; either version 3 of the
6 // License, or (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful, but
9 // WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 // General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, see
15 // <http://www.gnu.org/licenses/>.
16 
17 #include <octave/oct.h>
18 
19 #include "config.h"
20 
21 #include "connectivity.h"
22 using namespace octave::image;
23 
24 // The conndef() function is really really simple and could have easily
25 // been a m file (actually it once was, check the hg log if it ever needs
26 // to be recovered) but then it would be awkward to call it from oct
27 // functions so we made a C++ class for it.
28 
29 DEFUN_DLD(conndef, args, , "\
30 -*- texinfo -*-\n\
31 @deftypefn  {Loadable Function} {} conndef (@var{conn})\n\
32 @deftypefnx {Loadable Function} {} conndef (@var{mask})\n\
33 @deftypefnx {Loadable Function} {} conndef (@var{ndims}, @var{type})\n\
34 Create connectivity array.\n\
35 \n\
36 Creates a matrix of for morphological operations, where elements with\n\
37 a value of 1 are considered connected to the center element (a\n\
38 connectivity array).\n\
39 \n\
40 It can be specified by the number of dimensions, @var{ndims}, and\n\
41 @var{type} which must be one of the following strings:\n\
42 \n\
43 @table @asis\n\
44 @item @qcode{\"minimal\"}\n\
45 Neighbours touch the central element on a (@var{ndims}-1)-dimensional\n\
46 surface.\n\
47 \n\
48 @item @qcode{\"maximal\"}\n\
49 Neighbours touch the central element in any way. Equivalent to\n\
50 @code{ones (repmat (3, 1, @var{ndims}))}.\n\
51 \n\
52 @end table\n\
53 \n\
54 the number of connected elements to the center element, @var{conn},\n\
55 in which case the following are valid:\n\
56 \n\
57 @table @asis\n\
58 @item 4\n\
59 Two-dimensional 4-connected neighborhood.\n\
60 \n\
61 @item 8\n\
62 Two-dimensional 8-connected neighborhood.\n\
63 \n\
64 @item 6\n\
65 Three-dimensional 6-connected neighborhood.\n\
66 \n\
67 @item 18\n\
68 Three-dimensional 18-connected neighborhood.\n\
69 \n\
70 @item 26\n\
71 Three-dimensional 26-connected neighborhood.\n\
72 \n\
73 @end table\n\
74 \n\
75 or a connectivity array itself, in which case it checks for its validity\n\
76 and returns itself.  In such case, it is equivalent to @code{iptcheckconn}.\n\
77 \n\
78 @seealso{iptcheckconn, strel}\n\
79 @end deftypefn")
80 {
81   const octave_idx_type nargin = args.length ();
82 
83   if (nargin < 1 || nargin > 2)
84     print_usage ();
85 
86   connectivity conn;
87   if (nargin == 1)
88     conn = conndef (args(0));
89   else
90     {
91       const octave_idx_type ndims = args(0).uint_value (true);
92       if (ndims < 1)
93         error ("conndef: NDIMS must be a positive integer");
94       const std::string type = args(1).string_value ();
95       try
96         {
97           conn = connectivity (ndims, type);
98         }
99       catch (invalid_connectivity& e)
100         {
101           error ("conndef: TYPE %s", e.what ());
102         }
103     }
104 
105   // we must return an array of class double
106   return octave_value (NDArray (conn.mask));
107 }
108 
109 /*
110 
111 %!assert (conndef (1, "minimal"), [1; 1; 1]);
112 %!assert (conndef (2, "minimal"), [0 1 0; 1 1 1; 0 1 0]);
113 
114 %!test
115 %! C = zeros (3, 3, 3);
116 %! C(:,2,2) = 1;
117 %! C(2,:,2) = 1;
118 %! C(2,2,:) = 1;
119 %! assert (conndef (3, "minimal"), C);
120 
121 %!test
122 %! C = zeros (3, 3, 3, 3);
123 %! C(:,:,2,1) = [0   0   0
124 %!               0   1   0
125 %!               0   0   0];
126 %! C(:,:,1,2) = [0   0   0
127 %!               0   1   0
128 %!               0   0   0];
129 %! C(:,:,2,2) = [0   1   0
130 %!               1   1   1
131 %!               0   1   0];
132 %! C(:,:,3,2) = [0   0   0
133 %!               0   1   0
134 %!               0   0   0];
135 %! C(:,:,2,3) = [0   0   0
136 %!               0   1   0
137 %!               0   0   0];
138 %! assert (conndef (4, "minimal"), C);
139 
140 %!assert (conndef (1, "maximal"), ones (3, 1));
141 %!assert (conndef (2, "maximal"), ones (3, 3));
142 %!assert (conndef (3, "maximal"), ones (3, 3, 3));
143 %!assert (conndef (4, "maximal"), ones (3, 3, 3, 3));
144 
145 %!assert (nnz (conndef (3, "minimal")), 7)
146 %!assert (nnz (conndef (4, "minimal")), 9)
147 %!assert (nnz (conndef (5, "minimal")), 11)
148 %!assert (nnz (conndef (6, "minimal")), 13)
149 
150 %!assert (find (conndef (3, "minimal")), [5 11 13 14 15 17 23](:))
151 %!assert (find (conndef (4, "minimal")), [14 32 38 40 41 42 44 50 68](:))
152 %!assert (find (conndef (5, "minimal")),
153 %!        [   41   95  113  119  121  122  123  125  131  149  203](:))
154 %!assert (find (conndef (6, "minimal")),
155 %!        [  122  284  338  356  362  364  365  366  368  374  392  446  608](:))
156 
157 %!error conndef ()
158 %!error <must be a positive integer> conndef (-2, "minimal")
159 %!error conndef (char (2), "minimal")
160 %!error conndef ("minimal", 3)
161 %!error <TYPE must be "maximal" or "minimal"> conndef (3, "invalid")
162 %!error <CONN must be in the set \[4 6 8 18 26\]> conndef (10)
163 
164 %!assert (conndef (2, "minimal"), conndef (4))
165 %!assert (conndef (2, "maximal"), conndef (8))
166 %!assert (conndef (3, "minimal"), conndef (6))
167 %!assert (conndef (3, "maximal"), conndef (26))
168 
169 %!assert (conndef (18), reshape ([0 1 0 1 1 1 0 1 0
170 %!                                1 1 1 1 1 1 1 1 1
171 %!                                0 1 0 1 1 1 0 1 0], [3 3 3]))
172 */
173 
174 // PKG_ADD: autoload ("iptcheckconn", which ("conndef"));
175 // PKG_DEL: autoload ("iptcheckconn", which ("conndef"), "remove");
176 DEFUN_DLD(iptcheckconn, args, , "\
177 -*- texinfo -*-\n\
178 @deftypefn  {Loadable Function} {} iptcheckconn (@var{conn}, @var{func}, @var{var})\n\
179 @deftypefnx {Loadable Function} {} iptcheckconn (@var{conn}, @var{func}, @var{var}, @var{pos})\n\
180 Check if argument is valid connectivity.\n\
181 \n\
182 If @var{conn} is not a valid connectivity argument, gives a properly\n\
183 formatted error message.  @var{func} is the name of the function to be\n\
184 used on the error message, @var{var} the name of the argument being\n\
185 checked (for the error message), and @var{pos} the position of the\n\
186 argument in the input.\n\
187 \n\
188 A valid connectivity argument must be either double or logical.  It must\n\
189 also be either a scalar from set [4 6 8 18 26], or a symmetric matrix\n\
190 with all dimensions of size 3, with only 0 or 1 as values, and 1 at its\n\
191 center.\n\
192 \n\
193 @seealso{conndef}\n\
194 @end deftypefn")
195 {
196   const octave_idx_type nargin = args.length ();
197 
198   if (nargin < 3 || nargin > 4)
199     print_usage ();
200 
201   const std::string func = args(1).string_value ();
202   const std::string var = args(2).string_value ();
203 
204   int pos = 0;
205   if (nargin > 3)
206     {
207       pos = args(3).int_value ();
208       if (pos < 1)
209         error ("iptcheckconn: POS must be a positive integer");
210     }
211 
212   std::string err_msg;
213 #if defined HAVE_OCTAVE_EXCEPTION_MESSAGE
214   try
215     {
216       const connectivity conn = conndef (args(0));
217     }
218   catch (invalid_connectivity& e)
219     {
220       err_msg = e.what ();
221     }
222   catch (octave::execution_exception& e)
223     {
224       err_msg = e.message ();
225     }
226 #else
227   {
228     octave::unwind_protect frame;
229     frame.protect_var (buffer_error_messages);
230     buffer_error_messages++;
231     try
232       {
233         const connectivity conn = conndef (args(0));
234       }
235     catch (invalid_connectivity& e)
236       {
237         err_msg = e.what ();
238       }
239     catch (octave::execution_exception& e)
240       {
241         err_msg = last_error_message ();
242       }
243   }
244 #endif
245 
246   if (! err_msg.empty ())
247     {
248       // We get the error message from conndef and then parse it to
249       // get the issue with the connectivity so we can throw it again
250       // formatted appropriately for iptcheckconn.  This parsing of
251       // the error message is not nice but:1) we don't want to
252       // duplicate the logic of conndef in iptcheckconn; 2) we prefer
253       // to use conndef and only have this function for Matlab
254       // compatibility; 3) this code is only used when conn is invalid
255       // so won't be happening many times (meaning performance here is
256       // not important); 4) we have plenty of tests to ensure that the
257       // commit message "surgery" will continue to work as expected.
258 
259       const std::string token = "CONN ";
260       std::string::size_type n = err_msg.find(token);
261       if (n == std::string::npos)
262         error ("iptcheckconn: CONN is invalid but failed to parse error");
263       err_msg = err_msg.substr (n + token.size ());
264       if (pos == 0)
265         error ("%s: %s %s", func.c_str (), var.c_str (), err_msg.c_str ());
266       else
267         error ("%s: %s, at pos %i, %s",
268                func.c_str (), var.c_str (), pos, err_msg.c_str ());
269     }
270   return octave_value ();
271 }
272 
273 /*
274 // the complete error message should be "expected error <.> but got none",
275 // but how to escape <> within the error message?
276 
277 %!test iptcheckconn ( 4, "func", "var")
278 %!test iptcheckconn ( 6, "func", "var")
279 %!test iptcheckconn ( 8, "func", "var")
280 %!test iptcheckconn (18, "func", "var")
281 %!test iptcheckconn (26, "func", "var")
282 
283 %!test iptcheckconn (1, "func", "var")
284 %!test iptcheckconn (ones (3, 1), "func", "var")
285 %!test iptcheckconn (ones (3, 3), "func", "var")
286 %!test iptcheckconn (ones (3, 3, 3), "func", "var")
287 %!test iptcheckconn (ones (3, 3, 3, 3), "func", "var")
288 
289 %!error <func: VAR must be in the set \[4 6 8 18 26\]>
290 %!      iptcheckconn (3, "func", "VAR");
291 %!error <func: VAR center is not true>
292 %!      iptcheckconn ([1 1 1; 1 0 1; 1 1 1], "func", "VAR");
293 %!error <func: VAR must either be a logical array or a numeric scalar>
294 %!      iptcheckconn ([1 2 1; 1 1 1; 1 1 1], "func", "VAR");
295 %!error <func: VAR is not symmetric relative to its center>
296 %!      iptcheckconn ([0 1 1; 1 1 1; 1 1 1], "func", "VAR");
297 %!error <func: VAR is not 1x1, 3x1, 3x3, or 3x3x...x3>
298 %!      iptcheckconn (ones (3, 3, 3, 4), "func", "VAR");
299 */
300