Mercurial > hg > octave-image
changeset 350:f8c6b6fa1217
Support a new set of spatial filters
author | hauberg |
---|---|
date | Sun, 05 Oct 2008 19:18:25 +0000 |
parents | 327358a87fdc |
children | 9b16a35cf8d1 |
files | INDEX inst/cordflt2.m inst/entropy.m inst/entropyfilt.m inst/ordfilt2.m inst/ordfiltn.m inst/rangefilt.m inst/stdfilt.m src/Makefile src/__cordfltn__.cc src/__spatial_filtering__.cc |
diffstat | 11 files changed, 901 insertions(+), 302 deletions(-) [+] |
line wrap: on
line diff
--- a/INDEX +++ b/INDEX @@ -28,6 +28,7 @@ imhist mean2 std2 + entropy qtdecomp qtgetblk qtsetblk @@ -118,3 +119,6 @@ nlfilter im2col col2im + rangefilt + stdfilt + entropyfilt
--- a/inst/cordflt2.m +++ b/inst/cordflt2.m @@ -20,8 +20,9 @@ ## @seealso{ordfilt2} ## @end deftypefn -function varargout = cordflt2(varargin) +function retval = cordflt2(A, nth, domain, S) warning(["cordflt2: this function is deprecated and will be removed in upcoming " "releases. Use 'ordfilt2' instead."]); - [varargout{1:nargout}] = __cordfltn__(varargin{:}); + + retval = __spatial_filtering__ (A, domain, "ordered", S, nth); endfunction
new file mode 100644 --- /dev/null +++ b/inst/entropy.m @@ -0,0 +1,67 @@ +## Copyright (C) 2008 Søren Hauberg +## +## 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, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +## -*- texinfo -*- +## @deftypefn {Function File} {@var{E} =} entropy (@var{im}) +## @deftypefnx{Function File} {@var{E} =} entropy (@var{im}, @var{nbins}) +## Computes the entropy of an image. +## +## The entropy of the elements of the image @var{im} is computed as +## +## @example +## @var{E} = -sum (@var{P} .* log2 (@var{P}) +## @end example +## +## where @var{P} is the distribution of the elements of @var{im}. The distribution +## is approximated using a histogram with @var{nbins} cells. If @var{im} is +## @code{logical} then two cells are used by default. For other classes 256 cells +## are used by default. +## +## When the entropy is computed, zero-valued cells of the histogram are ignored. +## +## @seealso{entropyfilt} +## @end deftypefn + +function retval = entropy (I, nbins = 0) + ## Check input + if (nargin == 0) + error ("entropy: not enough input arguments"); + endif + + if (!ismatrix (I)) + error ("entropy: first input must be a matrix"); + endif + + if (!isscalar (nbins)) + error ("entropy: second input argument must be a scalar"); + endif + + ## Get number of histogram bins + if (nbins <= 0) + if (islogical (I)) + nbins = 2; + else + nbins = 256; + endif + endif + + ## Compute histogram + P = hist (I (:), nbins, true); + + ## Compute entropy (ignoring zero-entries of the histogram) + P += (P == 0); + retval = -sum (P .* log2 (P)); +endfunction
new file mode 100644 --- /dev/null +++ b/inst/entropyfilt.m @@ -0,0 +1,100 @@ +## Copyright (C) 2008 Søren Hauberg +## +## 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, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +## -*- texinfo -*- +## @deftypefn {Function File} {@var{E} =} entropyfilt (@var{im}) +## @deftypefnx{Function File} {@var{E} =} entropyfilt (@var{im}, @var{domain}) +## @deftypefnx{Function File} {@var{E} =} entropyfilt (@var{im}, @var{domain}, @var{padding}, ...) +## Computes the local entropy in a neighbourhood around each pixel in an image. +## +## The entropy of the elements of the neighbourhood is computed as +## +## @example +## @var{E} = -sum (@var{P} .* log2 (@var{P}) +## @end example +## +## where @var{P} is the distribution of the elements of @var{im}. The distribution +## is approximated using a histogram with @var{nbins} cells. If @var{im} is +## @code{logical} then two cells are used. For other classes 256 cells +## are used. +## +## When the entropy is computed, zero-valued cells of the histogram are ignored. +## +## The neighbourhood is defined by the @var{domain} binary mask. Elements of the +## mask with a non-zero value are considered part of the neighbourhood. By default +## a 9 by 9 matrix containing only non-zero values is used. +## +## At the border of the image, extrapolation is used. By default symmetric +## extrapolation is used, but any method supported by the @code{padarray} function +## can be used. Since extrapolation is used, one can expect a lower entropy near +## the image border. +## +## @seealso{entropy, paddarray, stdfilt} +## @end deftypefn + +function retval = entropyfilt (I, domain = true (9), padding = "symmetric", varargin) + ## Check input + if (nargin == 0) + error ("entropyfilt: not enough input arguments"); + endif + + if (!ismatrix (I)) + error ("entropyfilt: first input must be a matrix"); + endif + + if (!ismatrix (domain)) + error ("entropyfilt: second input argument must be a logical matrix"); + endif + domain = (domain > 0); + + ## Get number of histogram bins + if (islogical (I)) + nbins = 2; + else + nbins = 256; + endif + + ## Convert to 8 or 16 bit integers if needed + switch (class (I)) + case {"double", "single", "int16", "int32", "int64", "uint16", "uint32", "uint64"} + min_val = double (min (I (:))); + max_val = double (max (I (:))); + if (min_val == max_val) + retval = zeros (size (I)); + return; + endif + I = (double (I) - min_val)./(max_val - min_val); + I = uint8 (255 * I); + case {"logical", "int8", "uint8"} + ## Do nothing + otherwise + error ("entropyfilt: cannot handle images of class '%s'", class (I)); + endswitch + size (I) + ## Pad image + pad = floor (size (domain)/2); + I = padarray (I, pad, padding, varargin {:}); + even = (round (size (domain)/2) == size (domain)/2); + idx = cell (1, ndims (I)); + for k = 1:ndims (I) + idx {k} = (even (k)+1):size (I, k); + endfor + I = I (idx {:}); + size (I) + ## Perform filtering + retval = __spatial_filtering__ (I, domain, "entropy", I, nbins); + +endfunction
--- a/inst/ordfilt2.m +++ b/inst/ordfilt2.m @@ -63,6 +63,6 @@ endif; A = impad(A, xpad, ypad, padding); -retval = __cordfltn__(A, nth, domain, S); +retval = __spatial_filtering__ (A, domain, "ordered", S, nth); endfunction
--- a/inst/ordfiltn.m +++ b/inst/ordfiltn.m @@ -95,5 +95,5 @@ A = A(idx{:}); ## Perform the filtering - retval = __cordfltn__(A, nth, domain, S); + retval = __spatial_filtering__ (A, domain, "ordered", S, nth); endfunction
new file mode 100644 --- /dev/null +++ b/inst/rangefilt.m @@ -0,0 +1,71 @@ +## Copyright (C) 2008 Søren Hauberg +## +## 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, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +## -*- texinfo -*- +## @deftypefn {Function File} {@var{R} =} rangefilt (@var{im}) +## @deftypefnx{Function File} {@var{R} =} rangefilt (@var{im}, @var{domain}) +## @deftypefnx{Function File} {@var{R} =} rangefilt (@var{im}, @var{domain}, @var{padding}, ...) +## Computes the local intensity range in a neighbourhood around each pixel in +## an image. +## +## The intensity range of the pixels of a neighbourhood is computed as +## +## @example +## @var{R} = max (@var{x}) - min (@var{x}) +## @end example +## +## where @var{x} is the value of the pixels in the neighbourhood, +## +## The neighbourhood is defined by the @var{domain} binary mask. Elements of the +## mask with a non-zero value are considered part of the neighbourhood. By default +## a 3 by 3 matrix containing only non-zero values is used. +## +## At the border of the image, extrapolation is used. By default symmetric +## extrapolation is used, but any method supported by the @code{padarray} function +## can be used. +## +## @seealso{paddarray, entropyfilt, stdfilt} +## @end deftypefn + +function retval = rangefilt (I, domain = true (3), padding = "symmetric", varargin) + ## Check input + if (nargin == 0) + error ("rangefilt: not enough input arguments"); + endif + + if (!ismatrix (I)) + error ("rangefilt: first input must be a matrix"); + endif + + if (!ismatrix (domain)) + error ("rangefilt: second input argument must be a logical matrix"); + endif + domain = (domain > 0); + + ## Pad image + pad = floor (size (domain)/2); + I = padarray (I, pad, padding, varargin {:}); + even = (round (size (domain)/2) == size (domain)/2); + idx = cell (1, ndims (I)); + for k = 1:ndims (I) + idx {k} = (even (k)+1):size (I, k); + endfor + I = I (idx {:}); + + ## Perform filtering + retval = __spatial_filtering__ (I, domain, "range", I, 0); + +endfunction
new file mode 100644 --- /dev/null +++ b/inst/stdfilt.m @@ -0,0 +1,74 @@ +## Copyright (C) 2008 Søren Hauberg +## +## 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, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +## -*- texinfo -*- +## @deftypefn {Function File} {@var{S} =} stdfilt (@var{im}) +## @deftypefnx{Function File} {@var{S} =} stdfilt (@var{im}, @var{domain}) +## @deftypefnx{Function File} {@var{S} =} stdfilt (@var{im}, @var{domain}, @var{padding}, ...) +## Computes the local standard deviation in a neighbourhood around each pixel in +## an image. +## +## The standard deviation of the pixels of a neighbourhood is computed as +## +## @example +## @var{S} = sqrt ((sum (@var{x} - @var{mu}).^2)/(@var{N}-1)) +## @end example +## +## where @var{mu} is the mean value of the pixels in the neighbourhood, +## @var{N} is the number of pixels in the neighbourhood. So, an unbiased estimator +## is used. +## +## The neighbourhood is defined by the @var{domain} binary mask. Elements of the +## mask with a non-zero value are considered part of the neighbourhood. By default +## a 3 by 3 matrix containing only non-zero values is used. +## +## At the border of the image, extrapolation is used. By default symmetric +## extrapolation is used, but any method supported by the @code{padarray} function +## can be used. Since extrapolation is used, one can expect a lower deviation near +## the image border. +## +## @seealso{std2, paddarray, entropyfilt} +## @end deftypefn + +function retval = stdfilt (I, domain = true (3), padding = "symmetric", varargin) + ## Check input + if (nargin == 0) + error ("stdfilt: not enough input arguments"); + endif + + if (!ismatrix (I)) + error ("stdfilt: first input must be a matrix"); + endif + + if (!ismatrix (domain)) + error ("stdfilt: second input argument must be a logical matrix"); + endif + domain = (domain > 0); + + ## Pad image + pad = floor (size (domain)/2); + I = padarray (I, pad, padding, varargin {:}); + even = (round (size (domain)/2) == size (domain)/2); + idx = cell (1, ndims (I)); + for k = 1:ndims (I) + idx {k} = (even (k)+1):size (I, k); + endfor + I = I (idx {:}); + + ## Perform filtering + retval = __spatial_filtering__ (I, domain, "std", I, 0); + +endfunction
--- a/src/Makefile +++ b/src/Makefile @@ -8,14 +8,10 @@ PNG=pngread.oct pngwrite.oct endif -ifdef HAVE_MAGICKXX - IMAGEMAGICK=__magick_read__.oct -endif - -all: __cordfltn__.oct __bilateral__.oct __custom_gaussian_smoothing__.oct \ +all: __spatial_filtering__.oct __bilateral__.oct __custom_gaussian_smoothing__.oct \ bwlabel.oct bwfill.oct rotate_scale.oct hough_line.oct \ graycomatrix.oct deriche.oct __bwdist.oct nonmax_supress.oct \ - $(JPEG) $(PNG) $(IMAGEMAGICK) + $(JPEG) $(PNG) jpgread.oct: jpgread.cc $(MKOCTFILE) $< -ljpeg @@ -29,7 +25,4 @@ pngwrite.oct: pngwrite.cc $(MKOCTFILE) $< -lpng -__magick_read__.oct: __magick_read__.cc - $(MKOCTFILE) $< `Magick++-config --cppflags` `Magick++-config --ldflags` - clean: ; -$(RM) *.o octave-core core *.oct *~
deleted file mode 100644 --- a/src/__cordfltn__.cc +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright (C) 2008 Soren Hauberg -// -// 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/>. - -// The 'compare' and 'selnth' functions are copied from the 'cordflt2' function -// developed by Teemu Ikonen. This work was released under GPLv2 or later. -// -- Soren Hauberg, March 21st, 2008 - -#include <octave/oct.h> - -// Template function for comparison -// ET is the type of the matrix element -template <class ET> -inline bool compare (const ET a, const ET b) -{ - if (a > b) - return 1; - else - return 0; -} - -// Explicit template function for complex compare -template <> inline bool compare<Complex> (const Complex a, const Complex b) -{ - const double anorm2 = a.real () * a.real () + a.imag () * a.imag (); - const double bnorm2 = b.real () * b.real () + b.imag () * b.imag (); - - if (anorm2 > bnorm2) - return 1; - else - return 0; -} - -// select nth largest member from the array values -// Partitioning algorithm, see Numerical recipes chap. 8.5 -template <class ET> -ET selnth (ET *vals, int len, int nth) -{ - ET hinge; - int l, r, mid, i, j; - - l = 0; - r = len - 1; - for (;;) - { - // if partition size is 1 or two, then sort and return - if (r <= l+1) - { - if (r == l+1 && compare<ET> (vals [l], vals [r])) - std::swap (vals [l], vals [r]); - - return vals [nth]; - } - else - { - mid = (l+r) >> 1; - std::swap (vals [mid], vals [l+1]); - - // choose median of l, mid, r to be the hinge element - // and set up sentinels in the borders (order l, l+1 and r) - if (compare<ET> (vals [l], vals [r])) - std::swap (vals [l], vals [r]); - - if (compare<ET> (vals [l+1], vals [r])) - std::swap (vals [l+1], vals [r]); - - if (compare<ET> (vals [l], vals [l+1])) - std::swap (vals [l], vals [l+1]); - - i = l + 1; - j = r; - hinge = vals [l+1]; - for (;;) - { - do i++; while (compare<ET> (hinge, vals [i])); - do j--; while (compare<ET> (vals [j], hinge)); - if (i > j) - break; - std::swap (vals [i], vals [j]); - } - vals [l+1] = vals [j]; - vals [j] = hinge; - if (j >= nth) - r = j - 1; - if (j <= nth) - l = i; - } - } -} - -// Template function for doing the actual filtering -// MT is the type of the matrix to be filtered (Matrix or ComplexMatrix) -// ET is the type of the element of the matrix (double or Complex) -template <class MT, class ET> -octave_value_list do_filtering (MT A, int nth, const boolNDArray dom, MT S) -{ - const int ndims = dom.ndims (); - const octave_idx_type dom_numel = dom.numel (); - const dim_vector dom_size = dom.dims (); - const dim_vector A_size = A.dims (); - - octave_idx_type len = 0; - for (octave_idx_type i = 0; i < dom_numel; i++) - len += dom (i); - if (nth > len - 1) - { - warning("__cordfltn__: nth should be less than number of non-zero values " - "in domain setting nth to largest possible value"); - nth = len - 1; - } - if (nth < 0) - { - warning("__cordfltn__: nth should be non-negative, setting to 1"); - nth = 0; // nth is a c-index - } - - dim_vector dim_offset (dom_size); - for (int i = 0; i < ndims; i++) - dim_offset (i) = (dom_size (i)+1) / 2 - 1; - - // Allocate output - octave_value_list retval; - dim_vector out_size (dom_size); - for (int i = 0; i < ndims; i++) - out_size (i) = A_size (i) - dom_size (i) + 1; - - MT out = MT (out_size); - const octave_idx_type out_numel = out.numel (); - - // Iterate over every element of 'out'. - dim_vector idx_dim (ndims); - Array<octave_idx_type> dom_idx (idx_dim); - Array<octave_idx_type> A_idx (idx_dim); - Array<octave_idx_type> out_idx (idx_dim, 0); - for (octave_idx_type i = 0; i < out_numel; i++) - { - // For each neighbour - OCTAVE_LOCAL_BUFFER (ET, values, len); - int l = 0; - for (int n = 0; n < ndims; n++) - dom_idx (n) = 0; - - for (octave_idx_type j = 0; j < dom_numel; j++) - { - for (int n = 0; n < ndims; n++) - A_idx (n) = out_idx (n) + dom_idx (n); - - if (dom (dom_idx)) - values [l++] = A (A_idx) + S (dom_idx); - - dom.increment_index (dom_idx, dom_size); - } - - // Compute filter result - out (out_idx) = selnth (values, len, nth); - - // Prepare for next iteration - out.increment_index (out_idx, out_size); - - OCTAVE_QUIT; - } - - retval (0) = octave_value (out); - - return retval; -} - -// instantiate template functions -//SH template bool compare<double>(const double, const double); -//SH template double selnth(double *, int, int); -//SH template Complex selnth(Complex *, int, int); -//SH template octave_value_list do_filtering<NDArray, double>(NDArray, int, const boolNDArray, NDArray); -// g++ is broken, explicit instantiation of specialized template function -// confuses the compiler. -//template int compare<Complex>(const Complex, const Complex); -//SH template octave_value_list do_filtering<ComplexNDArray, Complex>(ComplexNDArray, int, const boolNDArray, ComplexNDArray); - -DEFUN_DLD(__cordfltn__, args, , "\ --*- texinfo -*-\n\ -@deftypefn {Loadable Function} __cordfltn__(@var{A}, @var{nth}, @var{domain}, @var{S})\n\ -Implementation of two-dimensional ordered filtering. In general this function\n\ -should NOT be used. Instead use @code{ordfilt2}.\n\ -@seealso{cordflt2, ordfilt2}\n\ -@end deftypefn\n\ -") -{ - octave_value_list retval; - if (args.length () != 4) - { - print_usage (); - return retval; - } - - // nth is an index to an array, thus - 1 - const int nth = (int)args (1).scalar_value () - 1; - const boolNDArray dom = args (2).bool_array_value (); - if (error_state) - { - error ("__cordfltn__: invalid input"); - return retval; - } - - const int ndims = dom.ndims (); - const int args0_ndims = args (0).ndims (); - if (args0_ndims != ndims || args (3).ndims () != ndims) - { - error ("__cordfltn__: input must be of the same dimension"); - return retval; - } - - // Take action depending on input type - if (args (0).is_real_matrix ()) - { - const NDArray A = args (0).array_value (); - const NDArray S = args (3).array_value (); - retval = do_filtering<NDArray, double> (A, nth, dom, S); - } - else if (args (0).is_complex_matrix ()) - { - const ComplexNDArray A = args (0).complex_matrix_value (); - const ComplexNDArray S = args (3).complex_matrix_value (); - retval = do_filtering<ComplexNDArray, Complex> (A, nth, dom, S); - } - else if (args (0).is_int8_type ()) - { - const int8NDArray A = args (0).int8_array_value (); - const int8NDArray S = args (3).int8_array_value (); - retval = do_filtering<int8NDArray, octave_int8> (A, nth, dom, S); - } - else if (args (0).is_int16_type ()) - { - const int16NDArray A = args (0).int16_array_value (); - const int16NDArray S = args (3).int16_array_value (); - retval = do_filtering<int16NDArray, octave_int16> (A, nth, dom, S); - } - else if (args (0).is_int32_type ()) - { - const int32NDArray A = args (0).int32_array_value (); - const int32NDArray S = args (3).int32_array_value (); - retval = do_filtering<int32NDArray, octave_int32> (A, nth, dom, S); - } - else if (args (0).is_int64_type ()) - { - const int64NDArray A = args (0).int64_array_value (); - const int64NDArray S = args (3).int64_array_value (); - retval = do_filtering<int64NDArray, octave_int64> (A, nth, dom, S); - } - else if (args (0).is_uint8_type ()) - { - const uint8NDArray A = args (0).uint8_array_value (); - const uint8NDArray S = args (3).uint8_array_value (); - retval = do_filtering<uint8NDArray, octave_uint8> (A, nth, dom, S); - } - else if (args (0).is_uint16_type ()) - { - const uint16NDArray A = args (0).uint16_array_value (); - const uint16NDArray S = args (3).uint16_array_value (); - retval = do_filtering<uint16NDArray, octave_uint16> (A, nth, dom, S); - } - else if (args (0).is_uint32_type ()) - { - const uint32NDArray A = args (0).uint32_array_value (); - const uint32NDArray S = args (3).uint32_array_value (); - retval = do_filtering<uint32NDArray, octave_uint32> (A, nth, dom, S); - } - else if (args (0).is_uint64_type ()) - { - const uint64NDArray A = args (0).uint64_array_value (); - const uint64NDArray S = args (3).uint64_array_value (); - retval = do_filtering<uint64NDArray, octave_uint64> (A, nth, dom, S); - } - else - { - error ("__cordfltn__: first input should be a real, complex, or integer array"); - } - - return retval; -}
new file mode 100644 --- /dev/null +++ b/src/__spatial_filtering__.cc @@ -0,0 +1,578 @@ +// Copyright (C) 2008 Soren Hauberg +// +// 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/>. + +// The 'compare' and 'selnth' functions are copied from the 'cordflt2' function +// developed by Teemu Ikonen. This work was released under GPLv2 or later. +// -- Soren Hauberg, March 21st, 2008 + +#include <octave/oct.h> + +/** + * Filter functions for ordered filtering. + */ + +template <class ET> +inline bool compare (const ET a, const ET b) +{ + if (a > b) + return 1; + else + return 0; +} + +template <> inline bool compare<Complex> (const Complex a, const Complex b) +{ + const double anorm2 = a.real () * a.real () + a.imag () * a.imag (); + const double bnorm2 = b.real () * b.real () + b.imag () * b.imag (); + + if (anorm2 > bnorm2) + return 1; + else + return 0; +} + +// select nth largest member from the array values +// Partitioning algorithm, see Numerical recipes chap. 8.5 +template <class ET, class MT, class ET_OUT> +ET_OUT selnth (MT &vals, octave_idx_type len, int nth) +{ + ET hinge; + int l, r, mid, i, j; + + l = 0; + r = len - 1; + for (;;) + { + // if partition size is 1 or two, then sort and return + if (r <= l+1) + { + if (r == l+1 && compare<ET> (vals (l), vals (r))) + std::swap (vals (l), vals (r)); + + return vals (nth); + } + else + { + mid = (l+r) >> 1; + std::swap (vals (mid), vals (l+1)); + + // choose median of l, mid, r to be the hinge element + // and set up sentinels in the borders (order l, l+1 and r) + if (compare<ET> (vals (l), vals (r))) + std::swap (vals (l), vals (r)); + + if (compare<ET> (vals (l+1), vals (r))) + std::swap (vals (l+1), vals (r)); + + if (compare<ET> (vals (l), vals (l+1))) + std::swap (vals (l), vals (l+1)); + + i = l + 1; + j = r; + hinge = vals (l+1); + for (;;) + { + do i++; while (compare<ET> (hinge, vals (i))); + do j--; while (compare<ET> (vals (j), hinge)); + if (i > j) + break; + std::swap (vals (i), vals (j)); + } + vals (l+1) = vals (j); + vals (j) = hinge; + if (j >= nth) + r = j - 1; + if (j <= nth) + l = i; + } + } +} + +template <class ET, class MT, class ET_OUT> +ET_OUT min_filt (MT &vals, octave_idx_type len, int not_used) +{ + ET_OUT min_val = vals (0); + for (octave_idx_type i = 1; i < len; i++) + min_val = compare (min_val, vals (i)) ? vals (i) : min_val; + + return min_val; +} + +template <class ET, class MT, class ET_OUT> +ET_OUT max_filt (MT &vals, octave_idx_type len, int not_used) +{ + ET_OUT max_val = vals (0); + for (octave_idx_type i = 1; i < len; i++) + max_val = compare (max_val, vals (i)) ? max_val : vals (i); + + return max_val; +} + +/** + * Filter functions for standard deviation filters + */ + +template <class ET> inline +ET square (const ET a) +{ + return a * a; +} + +template <class ET, class MT, class ET_OUT> +ET_OUT std_filt (MT &vals, octave_idx_type len, int norm) +{ + // Compute mean + ET_OUT mean = 0; + for (octave_idx_type i = 0; i < len; i++) + mean += (ET_OUT)vals (i); + mean /= (ET_OUT)len; + + // Compute sum of square differences from the mean + ET_OUT var = 0; + for (octave_idx_type i = 0; i < len; i++) + var += square ((ET_OUT)vals (i) - mean); + + // Normalise to produce variance + var /= (ET_OUT)norm; + + // Compute std. deviation + return sqrt (var); +} + +/** + * Functions for the entropy filter. + */ + +/* We only need the explicit typed versions */ +template <class ET> +void get_entropy_info (ET &add, int &nbins) +{ +} + +#define ENTROPY_INFO(TYPE, ADD, NBINS) \ + template <> \ + void get_entropy_info<TYPE> (TYPE &add, int &nbins) \ + { \ + add = ADD; \ + if (nbins <= 0) \ + nbins = NBINS; \ + } + +ENTROPY_INFO (bool, 0, 2) +ENTROPY_INFO (octave_int8, 128, 256) +//ENTROPY_INFO (octave_int16, 32768, 65536) +ENTROPY_INFO (octave_uint8, 0, 256) +//ENTROPY_INFO (octave_uint16, 0, 65536) + +#undef ENTROPY_INFO + +template <class ET, class MT, class ET_OUT> +ET_OUT entropy_filt (MT &vals, octave_idx_type len, int nbins) +{ + ET add; + get_entropy_info<ET> (add, nbins); + + // Compute histogram from values + ColumnVector hist (nbins, 0); + for (octave_idx_type i = 0; i < len; i++) + hist (vals (i) + add) ++; + for (octave_idx_type i = 0; i < len; i++) + hist (vals (i) + add) /= (double)len; + + // Compute entropy + double entropy = 0; + for (octave_idx_type i = 0; i < nbins; i++) + { + const double p = hist (i); + if (p > 0) + entropy -= p * log2 (p); + } + + return entropy; +} + +/** + * The function for the range filter + */ +template <class ET, class MT, class ET_OUT> +ET_OUT range_filt (MT &vals, octave_idx_type len, int not_used) +{ + const ET_OUT min_val = min_filt<ET, MT, ET_OUT> (vals, len, not_used); + const ET_OUT max_val = max_filt<ET, MT, ET_OUT> (vals, len, not_used); + + return max_val - min_val; +} + +/** + * The general function for doing the filtering. + */ + +template <class MT, class ET, class MTout, class ETout> +octave_value_list do_filtering (const MT &A, const boolNDArray &dom, + ETout (*filter_function) (MT&, octave_idx_type, int), const MT &S, int arg4) +{ + octave_value_list retval; + + const int ndims = dom.ndims (); + const octave_idx_type dom_numel = dom.numel (); + const dim_vector dom_size = dom.dims (); + const dim_vector A_size = A.dims (); + + octave_idx_type len = 0; + for (octave_idx_type i = 0; i < dom_numel; i++) + len += dom (i); + + dim_vector dim_offset (dom_size); + for (int i = 0; i < ndims; i++) + dim_offset (i) = (dom_size (i)+1) / 2 - 1; + + // Allocate output + dim_vector out_size (dom_size); + for (int i = 0; i < ndims; i++) + out_size (i) = A_size (i) - dom_size (i) + 1; + + MTout out = MTout (out_size); + const octave_idx_type out_numel = out.numel (); + + // Iterate over every element of 'out'. + dim_vector idx_dim (ndims); + Array<octave_idx_type> dom_idx (idx_dim); + Array<octave_idx_type> A_idx (idx_dim); + Array<octave_idx_type> out_idx (idx_dim, 0); + + dim_vector values_size (2); + values_size (0) = 1; + values_size (1) = len; + MT values (values_size); + + for (octave_idx_type i = 0; i < out_numel; i++) + { + // For each neighbour + int l = 0; + for (int n = 0; n < ndims; n++) + dom_idx (n) = 0; + + for (octave_idx_type j = 0; j < dom_numel; j++) + { + for (int n = 0; n < ndims; n++) + A_idx (n) = out_idx (n) + dom_idx (n); + + if (dom (dom_idx)) + values (l++) = A (A_idx) + S (dom_idx); + + dom.increment_index (dom_idx, dom_size); + } + + // Compute filter result + out (out_idx) = filter_function (values, len, arg4); + + // Prepare for next iteration + out.increment_index (out_idx, out_size); + + OCTAVE_QUIT; + } + + retval (0) = octave_value (out); + + return retval; +} + +/** + * The Octave function + */ + +DEFUN_DLD(__spatial_filtering__, args, , "\ +-*- texinfo -*-\n\ +@deftypefn {Loadable Function} __spatial_filtering__(@var{A}, @var{domain},\ +@var{method}, @var{S}, @var{arg})\n\ +Implementation of two-dimensional spatial filtering. In general this function\n\ +should NOT be used -- user interfaces are available in other functions.\n\ +The function computes local characteristics of the image @var{A} in the domain\n\ +@var{domain}. The following values of @var{method} are supported.\n\ +\n\ +@table @asis\n\ +@item \"ordered\"\n\ +Perform ordered filtering. The output in a pixel is the @math{n}th value of a\n\ +sorted list containing the elements of the neighbourhood. The value of @math{n}\n\ +is given in the @var{arg} argument. The corresponding user interface is available\n\ +in @code{ordfilt2} and @code{ordfiltn}.\n\ +@item \"std\"\n\ +Compute the local standard deviation. The correponding user interface is available\n\ +in @code{stdfilt}.\n\ +@item \"entropy\"\n\ +Compute the local entropy. The correponding user interface is available\n\ +in @code{entropyfilt}.\n\ +@item \"range\"\n\ +Compute the local range of the data. The corresponding user interface is\n\ +available in @code{rangefilt}.\n\ +@item \"min\"\n\ +Computes the smallest value in a local neighbourheed.\n\ +@item \"max\"\n\ +Computes the largest value in a local neighbourheed.\n\ +@item \"encoded sign of difference\"\n\ +NOT IMPLEMENTED (local binary patterns style)\n\ +@end table\n\ +@seealso{cordflt2, ordfilt2}\n\ +@end deftypefn\n\ +") +{ + octave_value_list retval; + const int nargin = args.length (); + if (nargin < 4) + { + print_usage (); + return retval; + } + + const boolNDArray dom = args (1).bool_array_value (); + if (error_state) + { + error ("__spatial_filtering__: invalid input"); + return retval; + } + + octave_idx_type len = 0; + for (octave_idx_type i = 0; i < dom.numel (); i++) + len += dom (i); + + const int ndims = dom.ndims (); + const int args0_ndims = args (0).ndims (); + if (args0_ndims != ndims || args (3).ndims () != ndims) + { + error ("__spatial_filtering__: input must be of the same dimension"); + return retval; + } + + + int arg4 = (nargin == 4) ? 0 : args (4).int_value (); + + const std::string method = args (2).string_value (); + if (error_state) + { + error ("__spatial_filtering__: method must be a string"); + return retval; + } + + #define GENERAL_ACTION(MT, FUN, ET, MT_OUT, ET_OUT, FILTER_FUN) \ + { \ + const MT A = args (0).FUN (); \ + const MT S = args (3).FUN (); \ + if (error_state) \ + error ("__spatial_filtering__: invalid input"); \ + else \ + retval = do_filtering<MT, ET, MT_OUT, ET_OUT> (A, dom, FILTER_FUN<ET, MT, ET_OUT>, S, arg4); \ + } + + if (method == "ordered") + { + // Handle input + arg4 -= 1; // convert arg to zero-based index + if (arg4 > len - 1) + { + warning ("__spatial_filtering__: nth should be less than number of non-zero " + "values in domain setting nth to largest possible value"); + arg4 = len - 1; + } + if (arg4 < 0) + { + warning ("__spatial_filtering__: nth should be non-negative, setting to 1"); + arg4 = 0; + } + + // Do the real work + #define ACTION(MT, FUN, ET) \ + GENERAL_ACTION(MT, FUN, ET, MT, ET, selnth) + if (args (0).is_real_matrix ()) + ACTION (NDArray, array_value, double) + else if (args (0).is_complex_matrix ()) + ACTION (ComplexNDArray, complex_array_value, Complex) + else if (args (0).is_bool_matrix ()) + ACTION (boolNDArray, bool_array_value, bool) + else if (args (0).is_int8_type ()) + ACTION (int8NDArray, int8_array_value, octave_int8) + else if (args (0).is_int16_type ()) + ACTION (int16NDArray, int16_array_value, octave_int16) + else if (args (0).is_int32_type ()) + ACTION (int32NDArray, int32_array_value, octave_int32) + else if (args (0).is_int64_type ()) + ACTION (int64NDArray, int64_array_value, octave_int64) + else if (args (0).is_uint8_type ()) + ACTION (uint8NDArray, uint8_array_value, octave_uint8) + else if (args (0).is_uint16_type ()) + ACTION (uint16NDArray, uint16_array_value, octave_uint16) + else if (args (0).is_uint32_type ()) + ACTION (uint32NDArray, uint32_array_value, octave_uint32) + else if (args (0).is_uint64_type ()) + ACTION (uint64NDArray, uint64_array_value, octave_uint64) + else + error ("__spatial_filtering__: first input should be a real, complex, or integer array"); + + #undef ACTION + } + else if (method == "min") + { + // Do the real work + #define ACTION(MT, FUN, ET) \ + GENERAL_ACTION(MT, FUN, ET, MT, ET, min_filt) + if (args (0).is_real_matrix ()) + ACTION (NDArray, array_value, double) + else if (args (0).is_complex_matrix ()) + ACTION (ComplexNDArray, complex_array_value, Complex) + else if (args (0).is_bool_matrix ()) + ACTION (boolNDArray, bool_array_value, bool) + else if (args (0).is_int8_type ()) + ACTION (int8NDArray, int8_array_value, octave_int8) + else if (args (0).is_int16_type ()) + ACTION (int16NDArray, int16_array_value, octave_int16) + else if (args (0).is_int32_type ()) + ACTION (int32NDArray, int32_array_value, octave_int32) + else if (args (0).is_int64_type ()) + ACTION (int64NDArray, int64_array_value, octave_int64) + else if (args (0).is_uint8_type ()) + ACTION (uint8NDArray, uint8_array_value, octave_uint8) + else if (args (0).is_uint16_type ()) + ACTION (uint16NDArray, uint16_array_value, octave_uint16) + else if (args (0).is_uint32_type ()) + ACTION (uint32NDArray, uint32_array_value, octave_uint32) + else if (args (0).is_uint64_type ()) + ACTION (uint64NDArray, uint64_array_value, octave_uint64) + else + error ("__spatial_filtering__: first input should be a real, complex, or integer array"); + + #undef ACTION + } + else if (method == "max") + { + // Do the real work + #define ACTION(MT, FUN, ET) \ + GENERAL_ACTION(MT, FUN, ET, MT, ET, max_filt) + if (args (0).is_real_matrix ()) + ACTION (NDArray, array_value, double) + else if (args (0).is_complex_matrix ()) + ACTION (ComplexNDArray, complex_array_value, Complex) + else if (args (0).is_bool_matrix ()) + ACTION (boolNDArray, bool_array_value, bool) + else if (args (0).is_int8_type ()) + ACTION (int8NDArray, int8_array_value, octave_int8) + else if (args (0).is_int16_type ()) + ACTION (int16NDArray, int16_array_value, octave_int16) + else if (args (0).is_int32_type ()) + ACTION (int32NDArray, int32_array_value, octave_int32) + else if (args (0).is_int64_type ()) + ACTION (int64NDArray, int64_array_value, octave_int64) + else if (args (0).is_uint8_type ()) + ACTION (uint8NDArray, uint8_array_value, octave_uint8) + else if (args (0).is_uint16_type ()) + ACTION (uint16NDArray, uint16_array_value, octave_uint16) + else if (args (0).is_uint32_type ()) + ACTION (uint32NDArray, uint32_array_value, octave_uint32) + else if (args (0).is_uint64_type ()) + ACTION (uint64NDArray, uint64_array_value, octave_uint64) + else + error ("__spatial_filtering__: first input should be a real, complex, or integer array"); + + #undef ACTION + } + else if (method == "range") + { + // Do the real work + #define ACTION(MT, FUN, ET) \ + GENERAL_ACTION(MT, FUN, ET, MT, ET, range_filt) + if (args (0).is_real_matrix ()) + ACTION (NDArray, array_value, double) + else if (args (0).is_complex_matrix ()) + ACTION (ComplexNDArray, complex_array_value, Complex) + else if (args (0).is_bool_matrix ()) + ACTION (boolNDArray, bool_array_value, bool) + else if (args (0).is_int8_type ()) + ACTION (int8NDArray, int8_array_value, octave_int8) + else if (args (0).is_int16_type ()) + ACTION (int16NDArray, int16_array_value, octave_int16) + else if (args (0).is_int32_type ()) + ACTION (int32NDArray, int32_array_value, octave_int32) + else if (args (0).is_int64_type ()) + ACTION (int64NDArray, int64_array_value, octave_int64) + else if (args (0).is_uint8_type ()) + ACTION (uint8NDArray, uint8_array_value, octave_uint8) + else if (args (0).is_uint16_type ()) + ACTION (uint16NDArray, uint16_array_value, octave_uint16) + else if (args (0).is_uint32_type ()) + ACTION (uint32NDArray, uint32_array_value, octave_uint32) + else if (args (0).is_uint64_type ()) + ACTION (uint64NDArray, uint64_array_value, octave_uint64) + else + error ("__spatial_filtering__: first input should be a real, complex, or integer array"); + + #undef ACTION + } + else if (method == "std") + { + // Compute normalisation factor + if (arg4 == 0) + arg4 = len - 1; // unbiased + else + arg4 = len; // max. likelihood + + // Do the real work + #define ACTION(MT, FUN, ET) \ + GENERAL_ACTION(MT, FUN, ET, NDArray, double, std_filt) + if (args (0).is_real_matrix ()) + ACTION (NDArray, array_value, double) + else if (args (0).is_bool_matrix ()) + ACTION (boolNDArray, bool_array_value, bool) + else if (args (0).is_int8_type ()) + ACTION (int8NDArray, int8_array_value, octave_int8) + else if (args (0).is_int16_type ()) + ACTION (int16NDArray, int16_array_value, octave_int16) + else if (args (0).is_int32_type ()) + ACTION (int32NDArray, int32_array_value, octave_int32) + else if (args (0).is_int64_type ()) + ACTION (int64NDArray, int64_array_value, octave_int64) + else if (args (0).is_uint8_type ()) + ACTION (uint8NDArray, uint8_array_value, octave_uint8) + else if (args (0).is_uint16_type ()) + ACTION (uint16NDArray, uint16_array_value, octave_uint16) + else if (args (0).is_uint32_type ()) + ACTION (uint32NDArray, uint32_array_value, octave_uint32) + else if (args (0).is_uint64_type ()) + ACTION (uint64NDArray, uint64_array_value, octave_uint64) + else + error ("__spatial_filtering__: first input should be a real, complex, or integer array"); + + #undef ACTION + } + else if (method == "entropy") + { + // Do the real work + #define ACTION(MT, FUN, ET) \ + GENERAL_ACTION(MT, FUN, ET, NDArray, double, entropy_filt) + if (args (0).is_bool_matrix ()) + ACTION (boolNDArray, bool_array_value, bool) + else if (args (0).is_int8_type ()) + ACTION (int8NDArray, int8_array_value, octave_int8) + else if (args (0).is_uint8_type ()) + ACTION (uint8NDArray, uint8_array_value, octave_uint8) + else + error ("__spatial_filtering__: first input should be a real, complex, or integer array"); + + #undef ACTION + } + else + { + error ("__spatial_filtering__: unknown method '%s'.", method.c_str ()); + } + + return retval; +}