changeset 26241:2d80a065ce9a

Add functions for calculating value of function handle over windows of data (bug #48774).
author Juan Pablo Carbajal <ajuanpi+dev@gmail.com>
date Sat, 15 Dec 2018 07:29:27 -0800
parents a957e0da8613
children 2730917a7979
files scripts/signal/module.mk scripts/signal/movfun.m scripts/signal/movmin.m scripts/signal/movslice.m scripts/signal/private/parse_movargs.m
diffstat 5 files changed, 632 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/signal/module.mk	Sat Dec 15 13:44:39 2018 +0100
+++ b/scripts/signal/module.mk	Sat Dec 15 07:29:27 2018 -0800
@@ -3,6 +3,7 @@
   %reldir%/private
 
 %canon_reldir%_PRIVATE_FCN_FILES = \
+  %reldir%/private/parse_moveargs.m  \
   %reldir%/private/rectangle_lw.m  \
   %reldir%/private/rectangle_sw.m  \
   %reldir%/private/triangle_lw.m  \
@@ -30,6 +31,9 @@
   %reldir%/hanning.m \
   %reldir%/hurst.m \
   %reldir%/ifftshift.m \
+  %reldir%/movfun.m \
+  %reldir%/movmin.m \
+  %reldir%/movslice.m \
   %reldir%/periodogram.m \
   %reldir%/sinc.m \
   %reldir%/sinetone.m \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/signal/movfun.m	Sat Dec 15 07:29:27 2018 -0800
@@ -0,0 +1,513 @@
+## Copyright (C) 2018 Juan Pablo Carbajal
+##
+## 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/>.
+
+## Author: Juan Pablo Carbajal <ajuanpi+dev@gmail.com>
+## Created: 2018-08-09
+
+## -*- texinfo -*-
+## @defun {@var{y} =} movfun (@var{fun}, @var{x}, @var{wlen})
+## @defunx {@var{y} =} movfun (@dots{}, @var{property}, @var{value})
+## Apply function @var{fun} to a moving window on @var{x}
+##
+## If @var{wlen} is a scalar, the function @var{fun} is applied to a moving 
+## window of length @var{wlen}. In this case @var{wlen} must be an odd number.
+## If @var{wlen} is an array with 2 elements, the function is applied to a 
+## moving window @code{-wlen(1):wlen(2)} which always includes the middle point.
+##
+## The function works along the first non-singleton dimension unless the property
+## @asis{'dim'} is set.
+##
+## The input @var{x} is reshaped into a @var{wlen}-by-n matrix and @var{fun}
+## is called o this matrix. Therefore @var{fun} must take a single array input 
+## argument and apply the computation column-wise.
+##
+## The output of @var{fun} can a 1-dimensional array of length @var{ndim}.
+## In this case, the output of @var{fun} when applied to a column vector must 
+## be a @strong{row} array of length @var{ndim}.
+## When applied to a 2-dimensional array with @var{n} columns, there are two 
+## formats that work:
+## @var{fun} can return an @var{n}-by-@var{ndim} array.
+## See @code{demo ('movfun', 5)} for an example of this case.
+## Alternatively, @var{fun} can return a row array of length
+## @code{@var{ndim} * @var{n}}, where the output of @var{fun} applied to the 
+## i-th column should be in the values @code{i:@var{n}:(@var{ndim}*@var{n})}.
+## This is useful when concatenating functions into arrays, or when using
+## @command{nthargout}.
+## ## See @code{demo ('movfun', 6)} for an example of this case.
+##
+## If the input @var{x} is a @var{N}-by-@var{M} array, and @var{fun} returns
+## @var{K} values per input column, then the output @var{y} is a 
+## @var{N}-by-@var{M}-by-@var{K} array. All singleton dimensions are squeezed.
+## Thus, if @var{M} is 1, then @var{y} is @var{N}-by-@var{K}. If in addition 
+## @var{K} is also 1, @var{y} has the same size as the input @var{x}.
+##
+## The results on the first and last @var{wlen} elements (the boundaries) is
+## controlled by the boundary conditions property @asis{'Endpoints'}. Possible
+## values are:
+##
+## @table @asis
+##
+## @item 'open'
+## (Default) The function is applied to shorter windows at the extrema of the array,
+## e.g. for a window of length 3, @code{@var{x}(1) = @var{fun} (@var{x}(1:2))}, and
+## @code{@var{x}(end) = @var{fun} (@var{x}(end-1:end))}
+##
+## @item 'periodic'
+## The window is wraped around so that
+## @code{@var{x}(1) = @var{fun} ([@var{x}(end-@var{k}:end) @var{x}(1:@var{k})])}, where
+## @var{k} is the radius of the window (for symmetric windows, 
+## @code{@var{k} = (@var{wlen} - 1) / 2)}
+##
+## @item 'zero'
+## The array is pre- and post-padded with zeros to exactly contain the window.
+##
+## @item 'same'
+## The resulting array @var{y} has the same values as @var{x} at the boundaries.
+##
+## @item 'discard'
+## The resulting array @var{y} is NaN at the boundaries.
+##
+## @end table
+##
+## Note that for some of these values, the window size at the boundaries is not
+## the same as in the middle part, and @var{fun} needs to work on these cases.
+##
+## Other optional properties are:
+##
+## @table @asis
+##
+## @item 'nancond'
+## Indicating whether @code{nan} or @code{NA} values should be omitted (value: 
+## @asis{'omitnan'}), or included (value: @asis{'includenan'}) in the arguments 
+## passed to @var{func}. The default is @asis{'omitnan'}.
+##
+## @item 'outdim'
+## A row array that allow for the selection of output dimensions. This is useful
+## only when @var{fun} returns an array dimension of @var{ndim}. The default is 
+## to return all output dimensions.
+##
+## @end table
+##
+## @strong{Programming tip}: the property 'outdim' is implemented to save memory
+## in case the output is highly dimensional or a wrapper to the base function 
+## that selects the desired outputs is too costly. The easiest way to select 
+## output dimension if memory is not an issue is to filter the result of 
+## @command{movfun}. If code complexity is not an issue one can create a 
+## wrapper, e.g. using anonymous functions. For example, if @code{basefunc} is 
+## the function returning a @var{K}-dimensional row output and we wish dimension
+## @var{D}, we could pass the following wrapper to @command{movfun}:
+## @example
+## @code{@var{fun} = @@(x)basefunc (x)(:,size(x,2) * (@var{D}-1) + (1:size(x,2)))}
+## @end example
+##
+## @seealso{movslice, prepad, postpad, permute, reshape, reshapemod}
+## @end defun
+
+function Y = movfun (func, X, wlen, varargin)
+  persistent dispatch;
+
+  valid_bc = {'open', 'periodic', 'same', 'zero', 'discard'};
+  if (isempty (dispatch))
+    dispatch = struct ();
+    for k = valid_bc
+      cmd = sprintf ('dispatch.%s = @%s_bc;', k{1}, k{1});
+      eval (cmd);
+    endfor
+  endif
+
+  # Parse input arguments
+  parser              = inputParser ();
+  parser.FunctionName = 'movfun';
+  parser.addParamValue ('Endpoints', 'open', ...
+    @(x)any (ismember (tolower (x), valid_bc)));
+  parser.addParamValue ('dim', [], ...
+    @(x) isempty(x) || (isscalar (x) && x > 0 && x <= ndims(X)));
+  parser.addParamValue ('nancond', 'omitnan', ...
+    @(x) any (ismember (x, {'omitnan', 'includenan'})));
+  parser.addParamValue ('outdim', [], ...
+    @(x) isempty(x) || (isvector (x) && all (x > 0)));
+
+  parser.parse(varargin{:});
+  bc      = parser.Results.Endpoints;   % boundary condition
+  dim     = parser.Results.dim;         % dimension to be used as input
+  nancond = parser.Results.nancond;     % whether nan are ignored or not
+  outdim  = parser.Results.outdim;      % selected output dimension of func
+  clear parser
+  #### End parse input arguments
+
+  # If dim was not provided search the first non-singleton dimension
+  szX  = size (X);
+  if (isempty (dim))
+    dim  = find (szX > 1, 1, 'first');
+  endif
+
+  # Window length is not a vector wlen = [wpre wpos]
+  if (isscalar (wlen))
+    # Check for proper window length
+    # TODO: Matlab accepts even windows
+    if (mod (wlen, 2) == 0)
+      error ('Octave:invalid-input-arg', 'Window must be of odd length');
+    endif
+    if (wlen == 1)
+      error ('Octave:invalid-input-arg', 'Window must be larger than 1');
+    endif
+  endif
+
+  # Check that array is longer that wlen at dim. At least one full window must 
+  # fit. Function max is used to include the case when wlen is an array.
+  # TODO: consider bc to decide what to do here
+  if (max (wlen) > szX(dim))
+      error ('Octave:invalid-input-arg', ...
+        'window length (%d) must be shorter than length along dim (%d=%d)', ...
+        wlen, dim, szX(dim));
+  endif
+
+  # Move the desired dim to the 1st dimension
+  dims  = length (szX);                  % number of dimensions
+  dperm = [dim 1:(dim-1) (dim+1):dims];  % permutation of dimensions
+  X     = permute (X, dperm);            % permute dim to first dimensions
+  ncols = prod (szX(dperm(2:end)));      % rest dimensions as single column
+  N     = szX(dperm(1));                 % length of dim
+  X     = reshape (X, N, ncols);         % reshape input
+
+  # obtain function for boundary conditions
+  bcfunc = dispatch.(tolower (bc));
+
+  # obtain slicer
+  [slc, C, Cpre, Cpos, win] = movslice (N, wlen);
+
+  # Check that outdim makes sense
+  tmp     = func (zeros (length (win), 1));  % output for window
+  noutdim = length (tmp);                    % number of output dimensions
+  if (!isempty (outdim))
+    if (max (outdim) > noutdim)
+      error ('Octave:invalid-input-arg', ...
+      'Selected output dimension (%d) is larger than output dimensions (%d)', ...
+        max (outdim), noutdim);
+    endif
+  else
+    outdim = 1:noutdim;
+  endif
+  soutdim = length (outdim); % length of selected output dimensions
+  # If noutdim is not one then modify function to handle multiple outputs
+  if (noutdim > 1)
+    func_ = @(x) reshape (func (x), size (x, 2), noutdim)(:,outdim);
+  else
+    func_ = func;
+  endif
+
+  # apply processing to each column
+  # Is it faster with cellfun? Do not think so
+  # It could be parallel
+  Y       = zeros (N, ncols, soutdim);
+  for i = 1:ncols
+    Y(:,i,:) = movfun_oncol (func_, X(:,i), wlen, bcfunc, ...
+          slc, C, Cpre, Cpos, win, soutdim);
+  endfor
+
+  # Restore shape
+  Y = reshape (Y, [szX(dperm), soutdim]);
+  Y = permute (Y, [dperm, dims+1]);
+  Y = squeeze (Y);
+
+endfunction
+
+function y = movfun_oncol (func, x, wlen, bcfunc, I, C, Cpre, Cpos, win, odim)
+  N = length (x);
+  y = NA (N, odim);
+  # process center part
+  y(C,:) = func (x(I));
+
+  # process boundaries
+  if !isempty (Cpre) % don't process zero length bkw-window
+    y(Cpre,:) = bcfunc (func, x, Cpre, win, wlen, odim);
+  endif
+  if !isempty (Cpos) % don't process zero length fwd-window
+    y(Cpos,:) = bcfunc (func, x, Cpos, win, wlen, odim);
+  endif
+endfunction
+
+function y = open_bc (func, x, idxp, win, wlen, odim)
+  N   = length (x);
+  idx = idxp + win;
+  tf  = !((idx < 1) | (idx > N)); % idx inside boundaries
+
+  n   = length (idxp);
+  y   = zeros (n, odim);
+  for i = 1:n
+    k      = idx(tf(:,i),i);
+    y(i,:) = func (x(k));
+  endfor
+endfunction
+
+function y = periodic_bc (func, x, idxp, win)
+  N       = length (x);
+  idx     = idxp + win;
+  tf      = idx < 1;
+  idx(tf) = N + idx(tf);
+  tf      = idx > N;
+  idx(tf) = idx(tf) - N;
+  y       = func (x(idx));
+endfunction
+
+function y = same_bc (func, x, idxp, win)
+  idx          = idxp + win;
+  idx(idx < 1) = 1;
+  N            = length (x);
+  idx(idx > N) = N;
+  y            = func (x(idx));
+endfunction
+
+function y = zero_bc (func, x, idxp, win, wlen)
+  if isscalar (wlen)
+    wlen = [wlen wlen];
+  endif
+  N = length (x);
+  if (min (idxp) == 1)
+    x = prepad (x, N + wlen(1));
+    idx = idxp + win + wlen(1);
+  elseif (max (idxp) == N)
+    x   = postpad (x, N + wlen(2));
+    idx = idxp + win;
+  endif
+  y = func (x(idx));
+endfunction
+
+function y = discard_bc (func, x, idxp, win, wlen, odim)
+  y = nan (length (idxp), odim);
+endfunction
+
+%!demo
+%! t  = 2 * pi * linspace (0,1,100).';
+%! x  = sin (3 * t);
+%! xn = x + 0.1 * randn (size (x));
+%! x_o = movfun (@mean, xn, 5, 'Endpoints', 'open');
+%! x_p = movfun (@mean, xn, 5, 'Endpoints', 'periodic');
+%! x_f = movfun (@mean, xn, 5, 'Endpoints', 'same');
+%! x_z = movfun (@mean, xn, 5, 'Endpoints', 'zero');
+%! x_d = movfun (@mean, xn, 5, 'Endpoints', 'discard');
+%!
+%! figure (), clf
+%!    h = plot (
+%!              t, xn, 'o;noisy signal;',
+%!              t, x, '-;true;',
+%!              t, x_o, '-;open;',
+%!              t, x_p, '-;periodic;',
+%!              t, x_f, '-;same;',
+%!              t, x_z, '-;zero;',
+%!              t, x_d, '-;discard;');
+%!    set (h(1), 'markerfacecolor', 'auto');
+%!    set (h(2:end), 'linewidth', 3);
+%!    axis tight
+%!    xlabel ('time')
+%!    ylabel ('signal')
+%! #####
+%! # Moving mean of noisy sinusoidal function with different boundary 
+%! # conditions.
+
+%!demo
+%! t  = 2 * pi * linspace (0,1,100).';
+%! x  = sin (3 * t);
+%! xn = x + 0.1 * randn (size (x));
+%! nw = 5;
+%! x_ = zeros (size(x,1), nw);
+%! w = 3 + (1:nw) * 4;
+%! for i=1:nw
+%!    x_(:,i) = movfun (@mean, xn, w(i), 'Endpoints', 'periodic');
+%!  endfor
+%!
+%! figure (), clf
+%!    h = plot (
+%!              t, xn, 'o',
+%!              t, x, '-',
+%!              t, x_, '-');
+%!    set (h(1), 'markerfacecolor', 'auto');
+%!    set (h(2:end), 'linewidth', 3);
+%!    axis tight
+%!    xlabel ('time')
+%!    ylabel ('signal')
+%!    legend (h, {'noisy', 'true', strsplit(num2str(w)){:}});
+%! #####
+%! # Moving mean of noisy sinusoidal function with periodic boundary conditions
+%! # using windows of different lengths.
+
+%!demo
+%! t  = linspace (0,1,100).';
+%! X  = exp(-(t - [0.1:0.3:1]).^2/2/0.1^2);
+%! Y  = movfun (@max, X, 15);
+%! figure (), clf
+%!    h = plot (
+%!              t, X, '-',
+%!              t, Y, '--');
+%!    axis tight
+%!    xlabel ('time')
+%!    ylabel ('signal')
+%! #####
+%! # Moving max of different Gaussian functions. Illustrates the application
+%! # on inputs with several columns.
+
+%!demo
+%! t  = linspace (0,1-1e-2,100).';
+%! w  = 2 * pi * 3;
+%! x  = sin (w * t);
+%! y  = cos (w * t);
+%! y_  = movfun (@diff, x, [1 0], 'Endpoints', 'periodic');
+%! # Is the same as y_ = x(2:end) - x(1:end-1);
+%! dt = t(2) - t(1);
+%! y_  = y_ / w / dt;
+%! figure (), clf
+%!    h = plot (t, x, '-',
+%!              t, y, '-', 
+%!              t, y_, ':');
+%!    set (h, 'linewidth', 3);
+%!    axis tight
+%!    xlabel ('time')
+%!    ylabel ('signal')
+%!    legend (h, {'sin', 'cos', 'bkw diff'});
+%! #####
+%! # Backward difference of sinusoidal function with periodic boundary 
+%! # conditions. Illustrates the use of asymmetric windows.
+
+%!demo
+%! N    = 1e3;
+%! wlen = 99;
+%! x  = linspace (-1, 1, N).';
+%! pp = [-2 0 1 0];
+%! y  = polyval (pp, x);
+%! yn = y + 0.1 * (abs (y) + 0.5) .* exp (randn (N, 1));
+%!
+%! st = movfun (@(y)statistics(y).', yn, wlen);
+%! figure (), clf
+%!    h = plot (x, y, '-',
+%!              x, yn, '.',
+%!              x, st(:,[3 6]), '-', 
+%!              x, st(:,6) + [-1 1].*st(:,7), '-', 
+%!              x, st(:,[1 2 4 5]), '-');
+%!    set (h([1 3:4]), 'linewidth', 3); % mean
+%!    set (h(5:end), 'color', 'k');
+%!    axis tight
+%!    xlabel ('x')
+%!    ylabel ('y')
+%!    legend (h, {'noiseless', 'noisy', 'mean', 'median'})
+%! #####
+%! # Moving window statistics. The plot highlights mean and median. black lines
+%! # show minimum, first quartile, third quartile, and maximum.
+%! # This illustrates the use of functions with multidimensional output.
+
+%!demo
+%! N    = 1e2;
+%! wlen = 9;
+%! x  = linspace (-1, 1, N).';
+%! pp = [-2 0 1 0];
+%! y  = polyval (pp, x);
+%! y(:,2) = y + 0.1 * (abs (y) + 0.5) .* exp (randn (N, 1));
+%! y(:,1) = -y(:,1) + 0.1 * randn (N, 1);
+%!
+%! func = @(y)[min(y) max(y)];
+%! st = movfun (func, y, wlen);
+%! figure (), clf
+%!    h = plot (x, y, 'o',
+%!              x, squeeze (st(:,1,:)), '-', 
+%!              x, squeeze (st(:,2,:)), '-');
+%!    axis tight
+%!    set (h(3:4), 'color', get(h(1), 'color'));
+%!    set (h(5:6), 'color', get(h(2), 'color'));
+%!    xlabel ('x')
+%!    ylabel ('y')
+%!    legend (h(1:2), {'data1', 'data2'})
+%! #####
+%! # Moving window bounds.
+%! # This illustrates the use of functions with flat multidimensional output.
+
+%!error(movfun(@min, [0;0], 1)) % wlen == 1
+%!error(movfun(@min, [0;0], 2)) % odd wlen
+%!error(movfun(@min, [0;0], 3)) % wlen larger than data
+%!error(movfun(@min, [0;0;0], [1 4])) % wlen larger than data
+%!error(movfun(@min, [0;0;0], [4 1])) % wlen larger than data
+
+%!test
+%! X = (1:10).' + [-3, 0, 4];
+%! ctrfun = @(x)x(2,:);
+%! valid_bc = {'same', 'periodic', 'zero'};
+%! for bc = valid_bc
+%!   assert (movfun(ctrfun, X, 3, 'Endpoints', bc{1}), X);
+%! endfor
+%! X_ = X; X_([1 end],:) = nan;
+%! assert (movfun(ctrfun, X, 3, 'Endpoints', 'discard'), X_);
+%! X_ = X; X_([1 end],:) = X([2 end],:);
+%! assert (movfun(ctrfun, X, 3, 'Endpoints', 'open'), X_);
+
+%!test % dim == 2 same as transpose
+%! X = randi (10, 3);
+%! ctrfun = @(x)x(2,:);
+%! valid_bc = {'same', 'periodic', 'zero'};
+%! for bc = valid_bc
+%!   assert (movfun(ctrfun, X.', 3, 'Endpoints', bc{1}, 'dim', 2), X.');
+%! endfor
+%! X_ = X; X_([1 end],:) = nan;
+%! assert (movfun(ctrfun, X.', 3, 'Endpoints', 'discard', 'dim', 2), X_.');
+%! X_ = X; X_([1 end],:) = X([2 end],:);
+%! assert (movfun(ctrfun, X.', 3, 'Endpoints', 'open', 'dim', 2), X_.');
+
+%!test
+%! X = randi (10, 3, 10, 2);
+%! Y = movfun (@(x)x(2,:), X, 3, 'Endpoints', 'same', 'dim', 2);
+%! assert (X, Y);
+
+%!test # bad zero_bc
+%! X = ones (10, 1);
+%! Y = X; Y(1:2) = Y([end end-1]) = [0.6;0.8];
+%! assert (movfun (@mean, X, 5, 'Endpoints', 'zero'), Y);
+
+## Asymmetric windows
+%!shared X,wlen,wlen0b,wlen0f,ctrfun,Xd,UNO,UNOd0b,UNOd0f
+%! X = (1:10).' + [-3, 0, 4];
+%! wlen = [2 1];
+%! wlen0b = [0 2];
+%! wlen0f = [2 0];
+%! ctrfun = @(x)x(wlen(1)+1,:);
+%! Xd = X; Xd([1:2 end],:) = nan;
+%! UNO = ones (7,1);
+%! UNOd0b = UNOd0f = UNO; 
+%! UNOd0b(end-1:end,:) = nan;
+%! UNOd0f(1:2,:) = nan;
+
+%!assert (movfun(ctrfun, X, wlen, 'Endpoints', 'same'), X);
+%!assert (movfun(ctrfun, X, wlen, 'Endpoints', 'discard'), Xd);
+%!assert (movfun(ctrfun, X, wlen, 'Endpoints', 'periodic'), X);
+%!assert (movfun(ctrfun, X, wlen, 'Endpoints', 'zero'), X);
+%!error movfun(ctrfun, X, wlen, 'Endpoints', 'open'); % for shorter x, indexing fails
+
+%!assert (movfun(@min, UNO, wlen0b, 'Endpoints', 'same'), UNO);
+%!assert (movfun(@min, UNO, wlen0f, 'Endpoints', 'same'), UNO);
+
+%!assert (movfun(@min, UNO, wlen, 'Endpoints', 'open'), UNO);
+%!assert (movfun(@min, UNO, wlen0b, 'Endpoints', 'open'), UNO);
+%!assert (movfun(@min, UNO, wlen0f, 'Endpoints', 'open'), UNO);
+
+%!assert (movfun(@min, UNO, wlen0b, 'Endpoints', 'discard'), UNOd0b);
+%!assert (movfun(@min, UNO, wlen0f, 'Endpoints', 'discard'), UNOd0f);
+
+%!assert (movfun(@min, UNO, wlen0b, 'Endpoints', 'periodic'), UNO);
+%!assert (movfun(@min, UNO, wlen0f, 'Endpoints', 'periodic'), UNO);
+
+%!assert (movfun(@max, UNO, wlen0b, 'Endpoints', 'zero'), UNO);
+%!assert (movfun(@max, UNO, wlen0f, 'Endpoints', 'zero'), UNO);
+
+## Multidimensional output
+%!error(movfun(@(x)[min(x) max(x)], (1:10).', 3, 'Outdim', 3)) % outdim > dim
+%!assert(size(movfun(@(x)[min(x) max(x)], (1:10).', 3)), [10 2])
+%!assert(size(movfun(@(x)[min(x) max(x)], cumsum(ones(10,5),2), 3)), [10 5 2])
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/signal/movmin.m	Sat Dec 15 07:29:27 2018 -0800
@@ -0,0 +1,37 @@
+## Copyright (C) 2018 Juan Pablo Carbajal
+##
+## 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/>.
+
+## Author: Juan Pablo Carbajal <ajuanpi+dev@gmail.com>
+## Created: 2018-08-13
+
+## -*- texinfo -*-
+## @defun {@var{y} =} movmin (@var{x}, @var{wlen})
+## @defunx {@var{y} =} movmin (@dots{}, @var{dim})
+## @defunx {@var{y} =} movmin (@dots{}, @var{nanstr})
+## @defunx {@var{y} =} movmin (@dots{}, @var{property}, @var{value})
+## Minimum of @var{x} over sliding window of length @var{wlen}.
+##
+## This is equivalent to
+## @code{movfun (@@min, @var{x}, @var{wlen}, args@{:@}}
+## where @code{args = @{'dim', @var{dim}, 'nancond', @var{nanstr}, @dots{}@}}
+##
+## Property @asis{'SamplePoints'} are not implemented yet
+##
+## @seealso{movfun}
+## @end defun
+
+function y = movmin (x, wlen, varargin)
+  y = movfun (@min, x, wlen, parse_movargs(varargin{:}){:});
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/signal/movslice.m	Sat Dec 15 07:29:27 2018 -0800
@@ -0,0 +1,42 @@
+## Copyright (C) 2018 Juan Pablo Carbajal
+##
+## 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/>.
+
+## Author: Juan Pablo Carbajal <ajuanpi+dev@gmail.com>
+## Created: 2018-12-08
+
+## -*- texinfo -*-
+## @defun {@var{slc} =} movslice (@var{N}, @var{wlen})
+## @defunx {[@dots{} @var{c} @var{pre} @var{pos} @var{w}] =} movslice (@dots{})
+## Generate indices to slice a 1D array of length @var{N} in windows @var{wlen}
+##
+## TODO
+## @seealso{movfun, reshapemod}
+## @end defun
+
+function [I, C, Cpre, Cpos, win] = movslice (N, wlen)
+  # TODO asymmetric window
+  if (isscalar (wlen))
+    hwlen = (wlen - 1) / 2;
+    wlen = [hwlen hwlen];
+  endif
+
+  Cpre = 1:wlen(1);               % centers that can't fit the pre-window
+  Cnf  = N - wlen(2) + 1;         % first center that can't fit the post-window
+  Cpos = Cnf:N;                   % centers that can't fit post-window
+  C    = (wlen(1) + 1):(Cnf - 1);
+  win  = (-wlen(1):wlen(2)).';
+  I    = C + win;
+endfunction
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/signal/private/parse_movargs.m	Sat Dec 15 07:29:27 2018 -0800
@@ -0,0 +1,36 @@
+## Copyright (C) 2018 Juan Pablo Carbajal
+##
+## 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/>.
+
+## Author: Juan Pablo Carbajal <ajuanpi+dev@gmail.com>
+## Created: 2018-08-13
+
+## -*- texinfo -*-
+## @defun {@var{y} =} parse_movargs (@var{x})
+## Parse arguments in compatibility with other software.
+##
+## Undocumented private function
+## @end defun
+
+function args = parse_movargs (varargin)
+  if (numel (varargin) == 1) # scalar was given --> dim or nancond
+    if (ischar (varargin{1})) # --> nancond
+      args = {'nancond', varargin{1}};
+    else # --> dim
+      args = {'dim', varargin{1}};
+    endif
+  else # Name,Value pairs
+    args = varargin;
+  endif
+endfunction