changeset 522:1334711928b6

image arithmetic functions: more matlab compatibility, test cases, better help text, bug fixes
author carandraug
date Sat, 10 Dec 2011 23:06:18 +0000
parents ea4f9b00355e
children 19957ee273c5
files inst/imadd.m inst/imdivide.m inst/immultiply.m inst/imsubtract.m inst/private/imarithmetics.m
diffstat 5 files changed, 73 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/inst/imadd.m
+++ b/inst/imadd.m
@@ -23,7 +23,7 @@
 ## to the image @var{a}.
 ##
 ## The class of @var{out} will be the same as @var{a} unless @var{a} is logical
-## in which case @var{out} will be double. Alternatively, the class can be
+## in which case @var{out} will be double. Alternatively, it can be
 ## specified with @var{class}.
 ##
 ## @emph{Note 1}: you can force output class to be logical by specifying
@@ -64,3 +64,10 @@
   endif
 
 endfunction
+
+%!assert (imadd (uint8   ([23 250]), uint8   ([23 250])),            uint8   ([46 255])); # default to first class and truncate
+%!assert (imadd (uint8   ([23 250]), 10),                            uint8   ([33 255])); # works adding a scalar
+%!assert (imadd (uint8   ([23 250]), uint8   ([23 250]), "uint16"),  uint16  ([46 500])); # defining output class works
+%!assert (imadd (logical ([ 1   0]), logical ([ 1   1])),            double  ([ 2   1])); # return double for two logical images
+%!assert (imadd (logical ([ 1   0]), logical ([ 1   1]), "logical"), logical ([ 1   1])); # this is matlab incompatible on purpose
+%!fail  ("imadd (uint8   ([23 250]), uint16  ([23 250]))")                                # input need to have same class
--- a/inst/imdivide.m
+++ b/inst/imdivide.m
@@ -23,7 +23,7 @@
 ## by it.
 ##
 ## The class of @var{out} will be the same as @var{a} unless @var{a} is logical
-## in which case @var{out} will be double. Alternatively, the class can be
+## in which case @var{out} will be double. Alternatively, it can be
 ## specified with @var{class}.
 ##
 ## @emph{Note}: the values are truncated to the mininum value of the output
@@ -52,3 +52,10 @@
   end_unwind_protect
 
 endfunction
+
+%!assert (imdivide (uint8   ([23 250]), uint8   ([ 2  50])),            uint8   ([ 12   5])); # default to first class
+%!assert (imdivide (uint8   ([56 255]), uint8   ([ 0   0])),            uint8   ([255 255])); # dividing by zero works (tested in matlab)
+%!assert (imdivide (uint8   ([23 250]), 2),                             uint8   ([ 12 125])); # works subtracting a scalar
+%!assert (imdivide (uint8   ([23 250]), uint8   ([ 2  50]), "uint16"),  uint16  ([ 12   5])); # defining output class works (not in matlab)
+%!assert (imdivide (logical ([1 1 0 0]), logical ([1 0 1 0])),          double  ([1 Inf 0 NaN])); # dividing logical matrix  (tested in matlab)
+%!fail  ("imdivide (uint8   ([23 250]), uint16  ([23 250]))")                                 # input needs to have same class
--- a/inst/immultiply.m
+++ b/inst/immultiply.m
@@ -23,7 +23,7 @@
 ## multiplie by it.
 ##
 ## The class of @var{out} will be the same as @var{a} unless @var{a} is logical
-## in which case @var{out} will be double. Alternatively, the class can be
+## in which case @var{out} will be double. Alternatively, it can be
 ## specified with @var{class}.
 ##
 ## @emph{Note}: the values are truncated to the mininum value of the output
@@ -36,10 +36,11 @@
   if (nargin < 2 || nargin > 3)
     print_usage;
   endif
-  [img, val] = imarithmetics ("immultiply", img, val, out_class);
+  [img, val] = imarithmetics ("immultiply", img, val, out_class, nargin);
 
-  ## matlab doesn't even gives a warning in this situation, it simply returns
-  ## a double precision float
+  ## have to check how matlab behaves in this situtation. Their documentation
+  ## does not say anything but add and subtract silently ignore it and return
+  ## double anyway. This may be done in the call to imarithmetics
   if (nargin > 2 && strcmpi (out_class, "logical"))
     warning ("Ignoring request to return logical as output of multiplication.");
   endif
@@ -47,3 +48,9 @@
   img = img .* val;
 
 endfunction
+
+%!assert (immultiply (uint8   ([255 50]), uint16  ([300 50])),           uint8  ([255 255]));  # default to first class and truncate
+%!assert (immultiply (uint8   ([250 50]), uint16  ([  3  4]), "uint32"), uint32 ([750 400]));  # defining output class works (not in matlab?)
+%!assert (immultiply (uint8   ([255 50]),                  4),           uint8  ([255 200]));  # works multiplying by a scalar
+%!assert (immultiply (logical ([  1  0]), uint16  ([300 50])),           uint16 ([300   0]));  # output class defaults to whatever input is not logical
+%!assert (immultiply (logical ([  1  0]), logical ([  1  1])),           double ([  1   0]));  # tested on matlab for compatibility
--- a/inst/imsubtract.m
+++ b/inst/imsubtract.m
@@ -23,7 +23,7 @@
 ## to the image @var{a}.
 ##
 ## The class of @var{out} will be the same as @var{a} unless @var{a} is logical
-## in which case @var{out} will be double. Alternatively, the class can be
+## in which case @var{out} will be double. Alternatively, it can be
 ## specified with @var{class}.
 ##
 ## @emph{Note 1}: you can force output class to be logical by specifying
@@ -32,6 +32,20 @@
 ##
 ## @emph{Note 2}: the values are truncated to the mininum value of the output
 ## class.
+##
+## @emph{NOte 3}: values are truncated before the operation so if input images are
+## unsigned integers and the request output class is a signed integer, it may lead
+## to unexpected results:
+##
+## @example
+## @group
+## imsubtract (uint8 ([23 190]), uint8 ([24 200]), "int8")
+##      @result{} -1  0
+## @end group
+## @end example
+##
+## Because both 190 and 200 were truncated to 127 before subtraction, their difference
+## is zero.
 ## @seealso{imadd, imcomplement, imdivide, immultiply}
 ## @end deftypefn
 
@@ -39,7 +53,15 @@
 
   if (nargin < 2 || nargin > 3)
     print_usage;
+  elseif (any (isa (img, {"uint8", "uint16", "uint32", "uint64"})) && any (strcmpi (out_class, {"int8", "int16", "int32", "int64"})))
+    ## because we convert the images before the subtraction, if input is:
+    ## imsubtract (uint8(150), uint8 (200), "int8");
+    ## rsult will be 0 because both values are truncated to 127 before subtraction.
+    ## There is no matlab compatibility issue because matlab does not have the option
+    ## to specify output class in imsubtract
+    warning ("input images are unsigned integers but requested output is signed integer. This may lead to unexpected results.");
   endif
+
   [img, val] = imarithmetics ("imsubtract", img, val, out_class);
 
   ## The following makes the code imcompatible with matlab on certain cases.
@@ -51,3 +73,11 @@
   endif
 
 endfunction
+
+%!assert (imsubtract (uint8   ([23 250]), uint8   ([24  50])),            uint8   ([ 0 200])); # default to first class and truncate
+%!assert (imsubtract (uint8   ([23 250]), 10),                            uint8   ([13 240])); # works subtracting a scalar
+%!assert (imsubtract (uint8   ([23 250]), uint8   ([24  50]), "uint16"),  uint16  ([ 0 200])); # defining output class works (not in matlab)
+%!assert (imsubtract (uint8   ([23 250]), uint8   ([24 255]), "int8"),    int8    ([-1   0])); # signed integers kinda work (not in matlab)
+%!assert (imsubtract (logical ([ 1   0]), logical ([ 1   1])),            double  ([ 0  -1])); # return double for two logical images
+%!assert (imsubtract (logical ([ 1   0]), logical ([ 1   1]), "logical"), logical ([ 0   0])); # this is matlab incompatible on purpose
+%!fail  ("imsubtract (uint8   ([23 250]), uint16  ([23 250]))")                                # input need to have same class
--- a/inst/private/imarithmetics.m
+++ b/inst/private/imarithmetics.m
@@ -22,7 +22,7 @@
 ## original function
 ## @end deftypefn
 
-function [img, val] = imarithmetics (func, img, val, out_class)
+function [img, val] = imarithmetics (func, img, val, out_class, in_args)
 
   is_valid = @(x) ((!isnumeric (x) && !islogical (x)) || isempty (x) || issparse (x) || !isreal (x));
 
@@ -34,9 +34,20 @@
     error ("%s: third argument must be a string that specifies the output class", func)
   endif
 
-  if (all (size (img) == size (val)) && strcmpi (class (img), class (val)))
+  same_size = all (size (img) == size (val));
+  if (same_size && strcmpi (class (img), class (val)))
     [img, val] = convert (out_class, img, val);
-  elseif (isscalar (val) && isfloat (val))
+
+  ## multiplication doesn't require same input class and output class defaults to
+  ## whatever input class is not logical (first img, then val)
+  elseif (strcmp (func, "immultiply") && same_size)
+    if (in_args > 2)
+      ## user defined, do nothing
+    elseif (islogical (img) && !islogical (val))
+      out_class = class (val);
+    endif
+    [img, val] = convert (out_class, img, val);
+  elseif (isscalar (val) && isfloat (val) && !strcmp (func, "imabsdiff"))
     ## according to matlab's documentation, if val is not an image of same size
     ## and class as img, then it must be a double scalar. But why not also support
     ## a single scalar and use isfloat?
@@ -50,7 +61,7 @@
   ## in the case that we only want to convert one matrix, this subfunction is called
   ## with 2 arguments only. Then, b takes the value of zero so that the call to the
   ## functions that change the class is insignificant
-  if (!strcmpi (class (a), out_class))
+  if ((nargin == 3 && any (!strcmpi ({class(a), class(b)}, out_class))) || (nargin == 2 && !strcmpi (class (a), out_class)))
     switch tolower (out_class)
       case {"logical"}  a = logical (a); b = logical (b);
       case {"uint8"}    a = uint8   (a); b = uint8   (b);