changeset 865:a17b71b7e407 stable

Rewrite imcrop to add several alternative API's. * imcrop.m: return rect (the position vector defining the bounding box), accept indexed and ND images, use of current figure or specific figure handle, accept specific rect argument for non-interactive usage, display cropped image if nargout is 0, don't loop forever until valid bounding box is selected. * NEWS: mention imcrop news for the next patch release.
author Carnë Draug <carandraug@octave.org>
date Wed, 15 Jan 2014 20:52:48 +0000
parents fa9074c5cfe9
children 2d08c21bbec7
files NEWS inst/imcrop.m
diffstat 2 files changed, 210 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,14 @@
+ Summary of important user-visible changes for image 2.2.1 (yyyy/mm/dd):
+-------------------------------------------------------------------------
+
+ ** imcrop had many alternative interfaces added for more flexibility.
+    Added support in the input for indexed images, figures handles,
+    N-dimensional images, and specific bounding box vector for a
+    non-interactive usage.  Output can now also return the bounding box used
+    for the cropping in addition to the cropped image.  It will no longer
+    loop forever until it gets two valid coordinates for the bounding box.
+
+
  Summary of important user-visible changes for image 2.2.0 (2014/01/08):
 -------------------------------------------------------------------------
 
--- a/inst/imcrop.m
+++ b/inst/imcrop.m
@@ -1,5 +1,4 @@
-## Copyright (C) 2012 Pablo Rossi <prossi@ing.unrc.edu.ar>
-## Copyright (C) 2012 Carnë Draug <carandraug+dev@gmail.com>
+## Copyright (C) 2014 Carnë Draug <carandraug+dev@gmail.com>
 ##
 ## This program 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
@@ -15,36 +14,211 @@
 ## this program; if not, see <http://www.gnu.org/licenses/>.
 
 ## -*- texinfo -*-
-## @deftypefn {Function File} @var{cropped} = imcrop (@var{Img})
+## @deftypefn  {Function File} {} imcrop ()
+## @deftypefnx {Function File} {} imcrop (@var{img})
+## @deftypefnx {Function File} {} imcrop (@var{ind}, @var{cmap})
+## @deftypefnx {Function File} {} imcrop (@var{h})
+## @deftypefnx {Function File} {} imcrop (@dots{}, @var{rect})
+## @deftypefnx {Function File} {[@var{cropped}] =} imcrop (@dots{})
+## @deftypefnx {Function File} {[@var{cropped}, @var{rect}] =} imcrop (@dots{})
+## @deftypefnx {Function File} {[@var{x}, @var{y}, @var{cropped}, @var{rect}] =} imcrop (@dots{})
 ## Crop image.
 ##
-## Displays the image @var{Img} in a figure window and waits for user to select
-## two points to define the bounding box.  First click on the top left and then
-## on the bottom right corner of the region.  The function will not return until
-## two valid points in the correct order are selected.
+## Displays the image @var{img} in a figure window and waits for the user to
+## select two points defining a bounding box.  First click on the top left
+## and then on the bottom right corner of the region.  For an indexed image, a
+## corresponding colormap can be specified in @var{cmap}.  For multi-dimensional
+## images (each 2D image is concatenated in the 4th dimension), only the
+## first image is displayed.
+##
+## if no image data is given, uses the current figure or figure from graphics
+## handle @var{h}.
+##
+## Non-interactive usage is supported if the last input argument is 4 element
+## vector @var{rect} defining the region of interest.  The first two elements
+## specify the initial @var{x_ini} and @var{y_ini} coordinates, and the last
+## two the @var{width} and @var{height}, i.e.,
+## @code{@var{rect} = [@var{x_ini} @var{y_ini} @var{width} @var{height}]}.
+## Note how this the opposite of the majority of Octave indexing rules where
+## rows come before columns.
+##
+## Returns the @var{cropped} image and a vector @var{rect} with the
+## coordinates and size for @var{cropped}.  If more than 3 output arguments
+## are requested, also returns the @var{x} and @var{y} data that define
+## the coordinate system.
 ##
-## Returns the @var{cropped} image.
+## @emph{Note}: the values in @var{rect} are not necessarily integer values
+## and can't always be used directly as index values for other images.  To
+## crop the same region from a multiple images of the same size, either using
+## a multi-dimensional image:
 ##
-## @seealso{imshow}
+## @example
+## @group
+## nd_img = cat (4, img1, img2, img3, img4);
+## croped = imcrop (nd_img);
+## cropped_1 = cropped(:,:,:,1);
+## cropped_2 = cropped(:,:,:,2);
+## cropped_3 = cropped(:,:,:,3);
+## cropped_4 = cropped(:,:,:,4);
+## @end group
+## @end example
+##
+## or multiple calls to @code{imcrop}:
+##
+## @example
+## @group
+## [croped_1, rect] = imcrop (img1);
+## cropped_2        = imcrop (img2, rect);
+## cropped_3        = imcrop (img3, rect);
+## cropped_4        = imcrop (img4, rect);
+## @end group
+## @end example
+##
+## @seealso{impixel, imshow}
 ## @end deftypefn
 
-function col = imcrop (Img)
+## TODO not yet implemented
+## @deftypefnx {Function File} {} imcrop (@var{xData}, @var{yData}, @dots{})
+
+function varargout = imcrop (varargin)
+
+  ## Screw Matlab and their over complicated API's! How can we properly
+  ## parse all the possible alternative calls? See
+  ## http://www.youtube.com/watch?v=1oZWacjmYm8 to understand how such
+  ## API's develop.
+
+  ## There is no check for this things, anything is valid. We (Octave)
+  ## are at least checking the number of elements otherwise the input
+  ## parsing would be horrible.
+  valid_rect   = @(x) numel (x) == 4;
+  valid_system = @(x) numel (x) == 2;
+
+  rect = [];
+  interactive = true;   # is interactive usage
+  alt_system  = false;  # was an alternative coordinate system requested?
+  from_fig    = false;  # do we have the image data or need to fetch from figure?
+
+  if (nargin > 5)
+    print_usage ();
+  endif
+
+  rect = [];
+  if (numel (varargin) > 1 && valid_rect (varargin{end}))
+    interactive = false;
+    rect = varargin{end};
+    varargin(end) = [];
+  endif
 
-  handle = imshow (Img);
-  [a, b] = size (Img);
+  xdata = [];
+  ydata = [];
+  if (numel (varargin) > 2 && valid_system (varargin{1}) && valid_system (varargin{2}))
+    ## requested messy stuff
+    ## we should probably reuse part of what impixel does
+    alt_system = true;
+    xdata = varargin{1};
+    ydata = varargin{2};
+    varargin([1 2]) = [];
+    error ("imcrop: messing around with coordinate system is not implemented");
+  endif
+
+  ## After we remove all that extra stuff, we are left with the image
+  fnargin = numel (varargin);
+  if (fnargin > 2)
+    print_usage ();
+  elseif (fnargin == 0)
+    ## use current figure
+    from_fig = true;
+    h = gcf ();
+  elseif (fnargin == 1 && ishandle (varargin{1}))
+    ## use specified figure
+    from_fig = true;
+    h = varargin{1};
+  elseif (interactive)
+    ## leave input check to imshow
+    h = nd_imshow (varargin{:});
+  elseif (isimage (varargin{1}))
+    ## we have the image data and it's not interactive, so there is
+    ## nothing to do. We only check the minimum in the image.
+  else
+    print_usage ();
+  endif
 
-  do
-    [hl, rd] = ginput(2);
-    if (hl(1) <= 1), hl(1) = 1; endif
-    if (rd(1) <= 1), rd(1) = 1; endif
-    if (hl(2) >= b), hl(2) = b; endif
-    if (rd(2) >= a), rd(2) = a; endif
-  until (hl(1) < hl(2) || rd(1) < rd(2))
-  ## should we close the image after? Anyway, close does not accept the handle
-  ## since the handle from imshow is not a figure handle
-#  close (handle); 
+  if (from_fig)
+    cdata = get (h, "cdata");
+    xdata = get (h, "xdata");
+    ydata = get (h, "ydata");
+  else
+    cdata = varargin{1};
+    if (! alt_system)
+      xdata = [1 columns(cdata)];
+      ydata = [1 rows(cdata)];
+    endif
+  endif
+
+  ## Finally, crop the image
+  if (interactive)
+    [x, y] = ginput (2);
+    rect = [x(1) y(1) x(2)-x(1) y(2)-y(1)];
+  endif
+  i_ini = round ([rect(1) rect(2)]);
+  i_end = round ([rect(1)+rect(3) rect(2)+rect(4)]);
+  img = cdata(i_ini(2):i_end(2), i_ini(1):i_end(1),:,:); # don't forget RGB and ND images
+
+  ## Even the API for the output is complicated
+  if (nargout == 0 && interactive)
+    figure ();
+    ## In case we have a colormap or something like that, use
+    ## it again when displaying the cropped image.
+    nd_imshow (img, varargin{2:end});
+  elseif (nargout < 3)
+    varargout{1} = img;
+    varargout{2} = rect;
+  else
+    varargout{1} = xdata;
+    varargout{2} = ydata;
+    varargout{3} = img;
+    varargout{4} = rect;
+  endif
+
+endfunction
 
-  hl  = floor (hl);
-  rd  = floor (rd);
-  col = Img(rd(1):rd(2), hl(1):hl(2));
+## shadows core function to support ND image.  If we have one, use
+## the first frame only
+function h = nd_imshow (varargin)
+  size (varargin{1})
+  h = imshow (varargin{1}(:,:,:,1), varargin{2:end});
 endfunction
+
+## test typical non-interactive usage, grayscale image
+%!test
+%! a = randi (255, [100 100]);
+%! rect = [20 30 3 5];
+%! assert (nthargout ([1 2], @imcrop, a, rect), {a(30:35, 20:23) rect});
+%! assert (nthargout (2, @imcrop, a, rect), rect);
+%! assert (nthargout ([3 4], 4, @imcrop, a, rect), {a(30:35, 20:23) rect});
+
+## test typical non-interactive usage, RGB image
+%!test
+%! rgb = randi (255, [100 100 3]);
+%! rect = [20 30 3 5];
+%! assert (nthargout ([1 2], @imcrop, rgb, rect), {rgb(30:35, 20:23,:) rect});
+%! assert (nthargout (2, @imcrop, rgb, rect), rect);
+%! assert (nthargout ([3 4], 4, @imcrop, rgb, rect), {rgb(30:35, 20:23,:) rect});
+
+## test typical non-interactive usage, indexed image
+%!test
+%! a = randi (255, [100 100]);
+%! rect = [20 30 3 5];
+%! cmap = jet (255);
+%! assert (nthargout ([1 2], @imcrop, a, cmap, rect), {a(30:35, 20:23) rect});
+%! assert (nthargout (2, @imcrop, a, cmap, rect), rect);
+%! assert (nthargout ([3 4], 4, @imcrop, a, cmap, rect), {a(30:35, 20:23) rect});
+
+## test typical non-interactive usage, logical image
+%!test
+%! a = rand (100) > 0.5;
+%! rect = [20 30 3 5];
+%! assert (nthargout ([1 2], @imcrop, a, rect), {a(30:35, 20:23) rect});
+%! assert (nthargout (2, @imcrop, a, rect), rect);
+%! assert (nthargout ([3 4], 4, @imcrop, a, rect), {a(30:35, 20:23) rect});
+