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