view inst/@strel/strel.m @ 690:519e8cbcfe35

strel: implement height for arbitrary shapes
author carandraug
date Wed, 19 Dec 2012 10:45:19 +0000
parents ed7b091a5eb2
children 199c5cf48f51
line wrap: on
line source

## Copyright (C) 2012 Roberto Metere <roberto@metere.it>
## Copyright (C) 2012 Carnë Draug <carandraug@octave.org>
##
## 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
## Foundation; either version 3 of the License, or (at your option) any later
## version.
##
## This program 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
## this program; if not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {Function File} {} strel (@var{shape}, @var{parameters})
## Generate a morphological structuring element.
##
## @code{strel} creates a logical matrix/array with the shape @var{shape} tuned
## with parameters @var{parameters}.
## Available shapes at the moment are: 'square', 'rectangle', 'diamond', 'pair',
## 'disk' (partially)
##
## Argument @var{shape} can be = 'diamond', 'octagon', 'pair', 'rectangle', 'square', 'line',
## 'periodicline', 'ball', 'arbitrary', 'disk'
## Argument @var{parameters} may vary for number, type and meaning, depending on @var{shape}.
##           'diamond' - RADIUS = integer radius greater than 0
##           'octagon' - NYI
##           'pair' - OFFSET = 2-element positive integer vector [X Y] or [X; Y]
##           'rectangle' - DIMENSIONS = 2-element positive integer vector [X Y] or [X; Y]
##           'square' - EDGE = integer edge greater than 0
##           'line' - NYI
##           'periodicline' - NYI
##           'ball' - NYI
##           'arbitrary' - NYI
##           'disk' - RADIUS = integer radius greater than 0
##                  - N = use 0, 4, 6 or 8 periodic lines (default 0) (NYI)
##                        (default will be 4, for now 0: it works 'slower')
##
## @seealso{imdilate, imerode}
## @end deftypefn

function SE = strel (shape, varargin)

  if (nargin < 1 || nargin > 4 || (! ischar (shape) && ! ismatrix (shape)))
    print_usage;
  endif

  if (! ischar (shape))
    varargin(2:end+1) = varargin(:);
    varargin(1) = shape;
    shape = "arbitrary";
  endif

  ## because the order that these are created matters, we make them all here
  SE        = struct;
  SE.shape  = tolower (shape);
  SE.nhood  = false;
  SE.flat   = true;
  SE.height = [];

  switch (SE.shape)
    case "arbitrary"
      if (numel (varargin) == 1)
        nhood   = varargin{1};
        SE.flat = true;
      elseif (numel (varargin) == 2)
        nhood     = varargin{1};
        SE.height = varargin{2};
        SE.flat   = false;
      else
        error ("strel: an arbitrary shape takes 1 or 2 arguments");
      endif
      if (! isbw (nhood, "non-logical"))
        error ("strel: NHOOD must be a matrix with only 0 and 1 values")
      endif

      SE.nhood = logical (nhood); # we need this as logical for the height tests

      if (! SE.flat && ! (isnumeric (SE.height) && isreal (SE.height) &&
                          ndims (SE.height) == ndims (nhood)          &&
                          all (size (SE.height) == size (nhood))      &&
                          all (isfinite (SE.height(:)))))
        error ("strel: HEIGHT must be a finite real matrix of the same size as NHOOD");
      elseif (! SE.flat && ! (all (SE.height( SE.nhood)(:) > 0) &&
                              all (SE.height(!SE.nhood)(:) == 0)))
        error ("strel: HEIGHT must a matrix with values for each non-zero value in NHOOD");
      endif

#    case "ball"
      ## TODO implement ball shape

    case "diamond"
      if (numel (varargin) == 1)
        radius = varargin{1};
      else
        error ("strel: no RADIUS specified for diamond shape");
      endif
      if (! is_positive_integer (radius))
        error ("strel: RADIUS must be a positive integer");
      endif

      [xx, yy]  = meshgrid (-radius:radius);
      SE.nhood  = (abs (xx) + abs (yy)) <= radius;
      SE.flat   = true;

    case "disk"
      if (numel (varargin) == 1)
        radius = varargin{1};
      else
        ## TODO implement second option for number of periodic lines
        error ("strel: no RADIUS specified for disk shape");
      endif
      if (! is_positive_integer (radius))
        error ("strel: RADIUS must be a positive integer");
      endif

      SE.nhood = fspecial ("disk", radius) > 0;
      SE.flat  = true;

#    case "line"
      ## TODO implement line shape

#    case "octagon"
      ## TODO implement octagon shape

    case "pair"
      if (numel (varargin) == 1)
        offset = varargin{1};
      else
        error ("strel: no OFFSET specified for pair shape");
      endif
      if (! ismatrix (offset) || numel (offset) != 2 || ! isnumeric (offset))
        error ("strel: OFFSET must be a 2 element vector");
      elseif (any (fix (offset) != offset))
        error ("strel: OFFSET values must be integers");
      endif

      lengths  = abs (2*offset) + 1;
      SE.nhood = false (lengths);
      origin   = (lengths + 1)/2;
      SE.nhood(origin(1), origin(2)) = true;
      SE.nhood(origin(1) + offset(1), origin(2) + offset(2)) = true;

      SE.flat = true;

    case "periodicline"
      ## TODO implement periodicline shape

    case "rectangle"
      if (numel (varargin) == 1)
        dimensions = varargin{1};
      else
        error ("strel: no DIMENSIONS specified for rectangle shape");
      endif
      if (! ismatrix (dimensions) || numel (dimensions) != 2 || ! isnumeric (dimensions))
        error ("strel: DIMENSIONS must be a 2 element vector");
      elseif (! is_positive_integer (dimensions(1)) || ! is_positive_integer (dimensions(2)))
        error ("strel: DIMENSIONS values must be positive integers");
      endif

      SE.nhood = true (dimensions);
      SE.flat  = true;

    case "square"
      if (numel (varargin) == 1)
        edge = varargin{1};
      else
        error ("strel: no EDGE specified for square shape");
      endif
      if (! is_positive_integer (edge))
        error ("strel: EDGE value must be positive integers");
      endif

      SE.nhood = true (edge);
      SE.flat  = true;

    otherwise
      error ("strel: unknown SHAPE `%s'", shape);
  endswitch

  SE = class (SE, "strel");
endfunction

function retval = is_positive_integer (val)
  retval = isscalar (val) && isnumeric (val) && val > 0 && fix (val) == val;
endfunction

%!shared shape, height
%! shape  = [0 0 0 1];
%!assert (getnhood (strel (shape)), logical (shape));
%!assert (getnhood (strel ("arbitrary", shape)), logical (shape));
%! height = [0 0 0 3];
%!assert (getnhood (strel ("arbitrary", shape, height)), logical (shape));
%!assert (getheight (strel ("arbitrary", shape, height)), height);
%! shape = [0 0 0 1 0 0 0
%!          0 0 1 1 1 0 0
%!          0 1 1 1 1 1 0
%!          1 1 1 1 1 1 1
%!          0 1 1 1 1 1 0
%!          0 0 1 1 1 0 0
%!          0 0 0 1 0 0 0];
%!assert (getnhood (strel ("diamond", 3)), logical (shape));
%! shape = [0 0 0 1 0 0 0
%!          0 1 1 1 1 1 0
%!          0 1 1 1 1 1 0
%!          1 1 1 1 1 1 1
%!          0 1 1 1 1 1 0
%!          0 1 1 1 1 1 0
%!          0 0 0 1 0 0 0];
%!assert (getnhood (strel ("disk", 3)), logical (shape));
%! shape = [1;1;0];
%!assert (getnhood (strel ("pair", [-1 0])), logical (shape));
%! shape = [1 0 0 0 0 0 0
%!          0 0 0 1 0 0 0
%!          0 0 0 0 0 0 0];
%!assert (getnhood (strel ("pair", [-1 -3])), logical (shape));
%! shape = [0 0 0 0 0 0 0
%!          0 0 0 0 0 0 0
%!          0 0 0 1 0 0 0
%!          0 0 0 0 0 0 0
%!          0 0 0 0 0 0 1];
%!assert (getnhood (strel ("pair", [2 3])), logical (shape));
%!assert (getnhood (strel ("rectangle", [10 5])), true (10, 5));
%!assert (getnhood (strel ("square", 5)), true (5));

## test input validation
%!error strel()
%!error strel("nonmethodthing", 2)
%!error strel("arbitrary", "stuff")
%!error strel("arbitrary", [0 0 1], [2 0 1])
%!error strel("arbitrary", [0 0 1], [2 0 1; 4 5 1])
%!error strel("arbitrary", [0 0 1], "stuff")
%!error strel("diamond", -3)
%!error strel("disk", -3)
%!error strel("pair", [45 67 90])
%!error strel("rectangle", 2)
%!error strel("rectangle", [2 -5])
%!error strel("square", [34 1-2])