changeset 20404:131ce8cfaa80

Relax input in functions that convert between colorspaces (bug #45456) * scripts/image/hsv2rgb.m, scripts/image/ntsc2rgb.m, scripts/image/rgb2hsv.m, scripts/image/rgb2ntsc.m: remove all input check and leave it up to new private functions handled by all. This adds support for ND images, drops check for values in the [0 1] range, allows integer images, and allows sparse matrices. Also adjust tests and add extra ones. * scripts/image/private/colorspace_conversion_input_check.m, scripts/image/private/colorspace_conversion_revert.m: all of this functions handle input check in the same way. In the same way, they need to prepare the output in the same way based on what preparation was done during input check (transforming image into colormap). So we create two new private functions to avoid repeated code. All code was adapted from hsv2rgb.
author Carnë Draug <carandraug@octave.org>
date Sun, 19 Jul 2015 17:41:21 +0100
parents 2f9119bb3fe5
children f74ab65ee1bf
files scripts/image/hsv2rgb.m scripts/image/ntsc2rgb.m scripts/image/private/colorspace_conversion_input_check.m scripts/image/private/colorspace_conversion_revert.m scripts/image/rgb2hsv.m scripts/image/rgb2ntsc.m
diffstat 6 files changed, 179 insertions(+), 151 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/image/hsv2rgb.m	Sat Jul 18 08:56:51 2015 -0700
+++ b/scripts/image/hsv2rgb.m	Sun Jul 19 17:41:21 2015 +0100
@@ -63,42 +63,8 @@
     print_usage ();
   endif
 
-  cls = class (hsv);
-  ## If we have an image convert it into a color map.
-  if (! iscolormap (hsv))
-    if (! any (strcmp (cls, {"uint8", "uint16", "single", "double"})))
-      error ("hsv2rgb: HSV of invalid data type '%s'", cls);
-    elseif (size (hsv, 3) != 3)
-      error ("hsv2rgb: HSV must be a colormap or HSV image");
-    elseif (! isreal (hsv) || ! isnumeric (hsv))
-      error ("hsv2rgb: HSV must be numeric and real");
-    endif
-    is_image = true;
-
-    ## Allow for ND images, i.e., multiple images on the 4th dimension.
-    sz = size (hsv);
-    nd = ndims (hsv);
-    if (nd == 3)
-      is_ndimage = false;
-    elseif (nd == 4)
-      is_ndimage = true;
-      hsv = permute (hsv, [1 2 4 3]);
-    elseif (nd > 4)
-      error ("hsv2rgb: invalid HSV with more than 4 dimensions");
-    endif
-    hsv = reshape (hsv, [numel(hsv)/3 3]);
-  else
-    is_image = false;
-    is_ndimage = false;
-  endif
-
-  ## Convert to floating point (remember to leave class single alone)
-  if (isinteger (hsv))
-    hsv = double (hsv) / double (intmin (cls));
-    is_uint = true;
-  else
-    is_uint = false;
-  endif
+  [hsv, cls, sz, is_im, is_nd, is_int] ...
+    = colorspace_conversion_input_check ("hsv2rgb", "HSV", hsv);
 
   h = hsv(:,1);
   s = hsv(:,2);
@@ -126,18 +92,7 @@
                + (hue >= 1/6 & hue < 1/2)
                + (hue >= 1/2 & hue < 2/3) .* (4 - 6 * hue));
 
-  if (is_image)
-    if (is_ndimage)
-      rgb = reshape (rgb, [sz(1:2) sz(4) sz(3)]);
-      rgb = permute (rgb, [1 2 4 3]);
-    else
-      rgb = reshape (rgb, sz);
-    endif
-  endif
-
-  if (is_uint)
-    rgb *= intmax (cls);
-  endif
+  rgb = colorspace_conversion_revert (rgb, cls, sz, is_im, is_nd, is_int);
 
 endfunction
 
@@ -179,3 +134,12 @@
 %!error hsv2rgb (1,2)
 %!error <invalid data type> hsv2rgb ({1})
 %!error <HSV must be a colormap or HSV image> hsv2rgb (ones (2,2))
+
+## Test ND input
+%!test
+%! hsv = rand (16, 16, 3, 5);
+%! rgb = zeros (size (hsv));
+%! for i = 1:5
+%!   rgb(:,:,:,i) = hsv2rgb (hsv(:,:,:,i));
+%! endfor
+%! assert (hsv2rgb (hsv), rgb)
--- a/scripts/image/ntsc2rgb.m	Sat Jul 18 08:56:51 2015 -0700
+++ b/scripts/image/ntsc2rgb.m	Sun Jul 19 17:41:21 2015 +0100
@@ -45,22 +45,8 @@
     print_usage ();
   endif
 
-  if (! isa (yiq, "double"))
-    error ("ntsc2rgb: YIQ must be of type double");
-  endif
-
-  ## If we have an image convert it into a color map.
-  if (isnumeric (yiq) && ndims (yiq) == 3)
-    is_image = true;
-    sz = size (yiq);
-    yiq = [yiq(:,:,1)(:), yiq(:,:,2)(:), yiq(:,:,3)(:)];
-  else
-    is_image = false;
-  endif
-
-  if (! isreal (yiq) || columns (yiq) != 3 || issparse (yiq))
-    error ("ntsc2rgb: input must be a matrix of size Nx3 or NxMx3");
-  endif
+  [yiq, cls, sz, is_im, is_nd, is_int] ...
+    = colorspace_conversion_input_check ("ntsc2rgb", "YIQ", yiq);
 
   ## Conversion matrix constructed from 'inv (rgb2ntsc matrix)'.
   ## See programming notes in rgb2ntsc.m.  Note: Matlab matrix for inverse
@@ -70,17 +56,11 @@
   trans = [ 1.0,      1.0,      1.0;
             0.95617, -0.27269, -1.10374;
             0.62143, -0.64681,  1.70062 ];
-
   rgb = yiq * trans;
 
-  ## If input was an image, convert it back into one.
-  if (is_image)
-    rgb = reshape (rgb, sz);
-  endif
-
+  rgb = colorspace_conversion_revert (rgb, cls, sz, is_im, is_nd, is_int);
 endfunction
 
-
 ## Test pure R, G, B colors
 %!assert (ntsc2rgb ([.299  .596  .211]), [1 0 0], 1e-5)
 %!assert (ntsc2rgb ([.587 -.274 -.523]), [0 1 0], 1e-5)
@@ -97,6 +77,14 @@
 ## Test input validation
 %!error ntsc2rgb ()
 %!error ntsc2rgb (1,2)
-%!error <YIQ must be of type double> ntsc2rgb (uint8 (1))
-%!error <must be a matrix of size Nx3 or NxMx3> ntsc2rgb (ones (2,2))
+%!error <YIQ must be a colormap or YIQ image> ntsc2rgb (uint8 (1))
+%!error <YIQ must be a colormap or YIQ image> ntsc2rgb (ones (2,2))
 
+## Test ND input
+%!test
+%! yiq = rand (16, 16, 3, 5);
+%! rgb = zeros (size (yiq));
+%! for i = 1:5
+%!   rgb(:,:,:,i) = ntsc2rgb (yiq(:,:,:,i));
+%! endfor
+%! assert (ntsc2rgb (yiq), rgb)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/image/private/colorspace_conversion_input_check.m	Sun Jul 19 17:41:21 2015 +0100
@@ -0,0 +1,71 @@
+## Copyright (C) 2015 Carnë Draug <carandraug+dev@gmail.com>
+##
+## This file is part of Octave.
+##
+## Octave is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## Private function the functions that convert between color spaces, i.e.,
+## rgb2ntsc, rgb2hsv, hsv2rgb, and ntsc2rgb.  All of these functions need to
+## handle input in the same way.  The returned flags are meant to be handled
+## by the complementary private function colorspace_conversion_revert()
+
+function [in_arg, cls, sz, is_im, is_nd, is_int] ...
+            = colorspace_conversion_input_check (func, arg_name, in_arg)
+
+  cls = class (in_arg);
+  sz = size (in_arg);
+
+  ## If we have an image convert it into a color map.
+  if (! iscolormap (in_arg))
+    if (! any (strcmp (cls, {"uint8", "uint16", "single", "double"})))
+      error ("%s: %s of invalid data type '%s'", func, arg_name, cls);
+    elseif (size (in_arg, 3) != 3)
+      error ("%s: %s must be a colormap or %s image", func, arg_name, arg_name);
+    elseif (! isreal (in_arg) || ! isnumeric (in_arg))
+      error ("%s: %s must be numeric and real", func, arg_name);
+    endif
+    is_im = true;
+
+    ## For floating point values, R, G and B should be in the [0 1] range,
+    ## otherwise they don't make any sense. We accept those values
+    ## anyways because we must return something for Matlab compatibility.
+    ## User case is when a function returns an RGB image just slightly outside
+    ## the range due to floating point rounding errors.
+
+    ## Allow for ND images, i.e., multiple images on the 4th dimension.
+    nd = ndims (in_arg);
+    if (nd == 3)
+      is_nd = false;
+    elseif (nd == 4)
+      is_nd = true;
+      in_arg = permute (in_arg, [1 2 4 3]);
+    elseif (nd > 4)
+      error ("%s: invalid %s with more than 4 dimensions", func, arg_name);
+    endif
+    in_arg = reshape (in_arg, [numel(in_arg)/3 3]);
+  else
+    is_im = false;
+    is_nd = false;
+  endif
+
+  ## Convert to floating point (remember to leave class single alone)
+  if (isinteger (in_arg))
+    in_arg = double (in_arg) / double (intmin (cls));
+    is_int = true;
+  else
+    is_int = false;
+  endif
+
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/image/private/colorspace_conversion_revert.m	Sun Jul 19 17:41:21 2015 +0100
@@ -0,0 +1,37 @@
+## Copyright (C) 2015 Carnë Draug <carandraug+dev@gmail.com>
+##
+## This file is part of Octave.
+##
+## Octave is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## Private function the functions that convert between color spaces, i.e.,
+## rgb2ntsc, rgb2hsv, hsv2rgb, and ntsc2rgb.  This reverts a colormap type
+## into the same shape and class as it was in the input.  The flags are meant
+## to come from  complementary private function
+## colorspace_conversion_input_check()
+
+function rv = colorspace_conversion_revert (rv, cls, sz, is_im, is_nd, is_int)
+  if (is_im)
+    if (is_nd)
+      rv = reshape (rv, [sz(1:2) sz(4) sz(3)]);
+      rv = permute (rv, [1 2 4 3]);
+    else
+      rv = reshape (rv, sz);
+    endif
+  endif
+  if (is_int)
+    rv *= intmax (cls);
+  endif
+endfunction
--- a/scripts/image/rgb2hsv.m	Sat Jul 18 08:56:51 2015 -0700
+++ b/scripts/image/rgb2hsv.m	Sun Jul 19 17:41:21 2015 +0100
@@ -19,52 +19,32 @@
 ## -*- texinfo -*-
 ## @deftypefn  {Function File} {@var{hsv_map} =} rgb2hsv (@var{rgb})
 ## @deftypefnx {Function File} {@var{hsv_map} =} rgb2hsv (@var{rgb})
-## Transform a colormap or image from red-green-blue (RGB) space to
-## hue-saturation-value (HSV) space.
+## Transform a colormap or image from RGB to HSV color space.
 ##
 ## A color in the RGB space consists of red, green, and blue intensities.
 ##
-## A color in HSV space is represented by hue, saturation, and value
-## (brightness) levels.  Value gives the amount of light in the color.  Hue
-## describes the dominant wavelength.  Saturation is the amount of hue mixed
-## into the color.
+## A color in HSV space is represented by hue, saturation and value
+## (brightness) levels in a cylindrical coordinate system.  Hue is the
+## azimuth and describes the dominant color.  Saturation is the radial
+## distance and gives the amount of hue mixed into the color.  Value is
+## the height and is the amount of light in the color.
+##
+## Output class and size will be the same as input.
+##
 ## @seealso{hsv2rgb, rgb2ind, rgb2ntsc}
 ## @end deftypefn
 
 ## Author: Kai Habel <kai.habel@gmx.de>
 ## Adapted-by: jwe
 
-function hsv_map = rgb2hsv (rgb)
+function hsv = rgb2hsv (rgb)
 
   if (nargin != 1)
     print_usage ();
   endif
 
-  cls = class (rgb);
-  if (! any (strcmp (cls, {"uint8", "uint16", "single", "double"})))
-    error ("rgb2hsv: invalid data type '%s'", cls);
-  elseif (isfloat (rgb) && (any (rgb(:) < 0) || any (rgb(:) > 1)))
-    error ("rgb2hsv: floating point images may only contain values between 0 and 1");
-  endif
-
-  ## If we have an image convert it into a color map.
-  if (isreal (rgb) && ndims (rgb) == 3)
-    is_image = true;
-    sz = size (rgb);
-    rgb = [rgb(:,:,1)(:), rgb(:,:,2)(:), rgb(:,:,3)(:)];
-    ## Convert to a double image.
-    if (isinteger (rgb))
-      low = double (intmin (cls));
-      high = double (intmax (cls));
-      rgb = (double (rgb) - low) / (high - low);
-    endif
-  else
-    is_image = false;
-  endif
-
-  if (! ismatrix (rgb) || columns (rgb) != 3 || issparse (rgb))
-    error ("rgb2hsv: input must be a matrix of size Nx3 or MxNx3");
-  endif
+  [rgb, cls, sz, is_im, is_nd, is_int] ...
+    = colorspace_conversion_input_check ("rgb2hsv", "RGB", rgb);
 
   ## get the max and min for each row
   s = min (rgb, [], 2);
@@ -97,24 +77,23 @@
   s(! notgray) = 0;
   s(notgray) = 1 - s(notgray) ./ v(notgray);
 
-  hsv_map = [h, s, v];
-
-  ## FIXME: rgb2hsv does not preserve class of image.
-  ##        Should it also convert back to uint8, uint16 for integer images?
-  ## If input was an image, convert it back into one.
-  if (is_image)
-    hsv_map = reshape (hsv_map, sz);
-  endif
+  hsv = [h, s, v];
+  hsv = colorspace_conversion_revert (hsv, cls, sz, is_im, is_nd, is_int);
 
 endfunction
 
-
 ## Test pure colors and gray
 %!assert (rgb2hsv ([1 0 0]), [0 1 1])
 %!assert (rgb2hsv ([0 1 0]), [1/3 1 1])
 %!assert (rgb2hsv ([0 0 1]), [2/3 1 1])
+%!assert (rgb2hsv ([1 1 0]), [1/6 1 1])
+%!assert (rgb2hsv ([0 1 1]), [1/2 1 1])
+%!assert (rgb2hsv ([1 0 1]), [5/6 1 1])
 %!assert (rgb2hsv ([0.5 0.5 0.5]), [0 0 0.5])
 
+## Test tolarant input checking on floats
+%!assert (rgb2hsv ([1.5 1 1]), [0 1/3 1.5], eps)
+
 %!test
 %! rgb_map = rand (64, 3);
 %! assert (hsv2rgb (rgb2hsv (rgb_map)), rgb_map, 1e-6);
@@ -123,9 +102,22 @@
 %! rgb_img = rand (64, 64, 3);
 %! assert (hsv2rgb (rgb2hsv (rgb_img)), rgb_img, 1e-6);
 
+## support sparse input
+%!assert (rgb2hsv (sparse ([0 0 1])), sparse ([2/3 1 1]))
+%!assert (rgb2hsv (sparse ([0 1 1])), sparse ([1/2 1 1]))
+%!assert (rgb2hsv (sparse ([1 1 1])), sparse ([0 0 1]))
+
 ## Test input validation
 %!error rgb2hsv ()
 %!error rgb2hsv (1,2)
 %!error <invalid data type 'cell'> rgb2hsv ({1})
-%!error <must be a matrix of size Nx3> rgb2hsv (ones (2,2))
+%!error <RGB must be a colormap or RGB image> rgb2hsv (ones (2,2))
 
+## Test ND input
+%!test
+%! rgb = rand (16, 16, 3, 5);
+%! hsv = zeros (size (rgb));
+%! for i = 1:5
+%!   hsv(:,:,:,i) = rgb2hsv (rgb(:,:,:,i));
+%! endfor
+%! assert (rgb2hsv (rgb), hsv)
--- a/scripts/image/rgb2ntsc.m	Sat Jul 18 08:56:51 2015 -0700
+++ b/scripts/image/rgb2ntsc.m	Sun Jul 19 17:41:21 2015 +0100
@@ -51,33 +51,8 @@
     print_usage ();
   endif
 
-  cls = class (rgb);
-  if (! any (strcmp (cls, {"uint8", "uint16", "single", "double"})))
-    error ("rgb2ntsc: invalid data type '%s'", cls);
-  elseif (isfloat (rgb) && (any (rgb(:) < 0) || any (rgb(:) > 1)))
-    error ("rgb2ntsc: floating point images may only contain values between 0 and 1");
-  endif
-
-  ## If we have an image convert it into a color map.
-  if (isreal (rgb) && ndims (rgb) == 3)
-    is_image = true;
-    sz = size (rgb);
-    rgb = [rgb(:,:,1)(:), rgb(:,:,2)(:), rgb(:,:,3)(:)];
-    ## Convert to a double image.
-    if (isinteger (rgb))
-      low = double (intmin (cls));
-      high = double (intmax (cls));
-      rgb = (double (rgb) - low) / (high - low);
-    elseif (isa (rgb, "single"))
-      rgb = double (rgb);
-    endif
-  else
-    is_image = false;
-  endif
-
-  if (! isreal (rgb) || columns (rgb) != 3 || issparse (rgb))
-    error ("rgb2ntsc: input must be a matrix of size Nx3 or NxMx3");
-  endif
+  [rgb, cls, sz, is_im, is_nd, is_int] ...
+    = colorspace_conversion_input_check ("rgb2ntsc", "RGB", rgb);
 
   ## Reference matrix for transformation from http://en.wikipedia.org/wiki/YIQ
   ## and truncated to 3 significant figures.  Matlab uses this matrix for their
@@ -85,18 +60,11 @@
   trans = [ 0.299,  0.596,  0.211;
             0.587, -0.274, -0.523;
             0.114, -0.322,  0.312 ];
-
-  ## Convert data.
   yiq = rgb * trans;
 
-  ## If input was an image, convert it back into one.
-  if (is_image)
-    yiq = reshape (yiq, sz);
-  endif
-
+  yiq = colorspace_conversion_revert (yiq, cls, sz, is_im, is_nd, is_int);
 endfunction
 
-
 ## Test pure RED, GREEN, BLUE colors
 %!assert (rgb2ntsc ([1 0 0]), [.299  .596  .211])
 %!assert (rgb2ntsc ([0 1 0]), [.587 -.274 -.523])
@@ -114,5 +82,13 @@
 %!error rgb2ntsc ()
 %!error rgb2ntsc (1,2)
 %!error <invalid data type 'cell'> rgb2ntsc ({1})
-%!error <must be a matrix of size Nx3 or NxMx3> rgb2ntsc (ones (2,2))
+%!error <RGB must be a colormap or RGB image> rgb2ntsc (ones (2,2))
 
+## Test ND input
+%!test
+%! rgb = rand (16, 16, 3, 5);
+%! yiq = zeros (size (rgb));
+%! for i = 1:5
+%!   yiq(:,:,:,i) = rgb2ntsc (rgb(:,:,:,i));
+%! endfor
+%! assert (rgb2ntsc (rgb), yiq)