function R = slidefun (FUN, W, V, windowmode, varargin) 
% SLIDEFUN - apply function to a moving window over a vector
%
%   R = SLIDEFUN(FUN, W, V) evaluates the function FUN to a moving window
%   of W consecutive elements of the vector V. The function FUN is
%   specified by a function handle or a function name and should return a
%   scalar for a vector input. W specifies the size of the window and
%   should be a positive integer. At the two edges of the vector less than
%   W elements will be used. R will have the same size as V.
%
%   Effectively SLIDEFUN applies the function FUN to a moving window of
%   consecutive elemens V(x0:(x0+W)) using FEVAL, and returns the result in
%   R(x).
%
%   The window [x0:(x0+W)] is positioned relative to the current point x in V. 
%   R = SLIDEFUN(FUN, W, V, WINDOWMODE) denotes the type of windowing being
%   used. WINDOWMODE can be (the first letters of) one the following:
%       - 'central', or '' (default): the window is centered around each point,
%          so that x0 equals x - floor(W/2);
%       - 'backward': window is using W points before the current point, so
%          that x0 equals x-W+1
%       - 'forward': window is using W points following the current point,
%          so that x0 equals x
%
%  R = SLIDEFUN(FUN,W,V,WINDOWMODE, P1,P2,...) provides for aditional
%  parameters which are passed to the function FUN. 
%   
%   Example 1) Sliding max filter - return the maximum of every three
%   consecutive elements:
%      V =  [1  2  3  9  4  2  1  1  5  6] ;
%      R = slidefun(@max, 3, V)
%      % -> [2  3  9  9  9  4  2  5  6  6]
%      % So R(i) = max(R(i-1:i+1)) ;
%      % and R(1) = max(V(1:2))
%
%   Example 2) Sum every four consecutive elements
%      V =  [1  2  3  4  3  2  1] ;
%      R = slidefun('sum',4, V) 
%      % -> [3  6 10 12 12 10  6]
%      % So R(i) = sum(R(i-2:i+1)) ;
%      %    R(1) = sum([1 2]) ; 
%      %    R(2) = sum([1 2 3]) ;
%   
%   Example 3) Range of every three consecutive elements
%      myfun = inline('max(x) - min(x)','x') ; % (Matlab R13)
%      V =  [1  4  3  3  3  2  9  8] ;
%      R = slidefun(myfun,3, V) 
%      % -> [3  3  1  0  1  7  7  1]
%
%   Example 4) Mimick cumsum
%      V = 1:10 ;
%      R = slidefun(@sum, numel(V), V, 'backward') 
%      isequal(R,cumsum(V))
%
%   Example 5) Inverse cumprod ignoring zeros
%      V = [1:3 0 5:8] ;
%      myfun = inline('prod(x(x~=0))','x') ;
%      R = slidefun(myfun, numel(V), V, 'forward') 
%
%   Example 6) Replace values when they are outliers given their enighbours
%      V =   [1  1  2  3  8  4  5  4  4  6  5  7  8  9] ; % 5th value (10) is an outlier
%      N = 2 ; % window of 2*2+1  = 5 elements, central element has index N+1
%      isoutlier = slidefun(@(V,N) abs(V(N)-mean(V)) > 1.5*std(V), 2*N+1, V, [] ,N+1) 
%      % ->  [0  0  0  0  1  0  0  0  0  0  0  0  0]
%      V(isoutlier) = NaN
%
%   Note that for some specific functions (e.g., MEAN) filter can do the
%   same job faster. See FEVAL for more information about passing
%   functions.
%
%   See also FEVAL, INLINE, FILTER, BOOTSTRP
%   and Function handles, Anonymous functions.

% Written and tested in Matlab R13
% version 4.0 (sep 2008)
% (c) Jos van der Geest
% email: jos@jasen.nl

% History
% 1.0 (sep 2006). This file was inspired by a post on CSSM in sep 2006.
% 2.0 (oct 2006). Use for-loop instead of large matrices
% 3.0 (oct 2006). Added windowmode option (after File 9428 by John
%                 D'Errico)
% 4.0 (sep 2008). Can now handle (anonymous) function handles properly.

% check input arguments,expected
% <function name>, <window size>, <vector>, <windowmode>, <optional arguments ...>
error(nargchk(3,Inf,nargin)) ;

if nargin==3 || isempty(windowmode),
    windowmode = 'central' ;
end

% based on code by John D'Errico
if ~ischar(windowmode),
    error('WindowMode should be a character array') ;
else
    validmodes = {'central','backward','forward'} ;
    windowmode = strmatch(lower(windowmode), validmodes) ; %#ok
    if isempty(windowmode),
        error('Invalid window mode') ;
    end
end
% windowmode will 1, 2, or 3

if (numel(W) ~= 1) || (fix(W) ~= W) || (W < 1),
    error('Window size W must be a positive integer scalar.') ;
end

nV = numel(V) ;

if isa(FUN,'function_handle')
    FUNstr = func2str(FUN);
end

if nV==0,
    % trivial case
    R = V ;
    return
end

% can the function be applied succesfully?
try
    R = feval(FUN,V(1:min(W,nV)),varargin{:}) ;
    % feval did ok. Now check for scalar output
    if numel(R) ~= 1,
        error('Function "%s" does not return a scalar output for a vector input.', FUNstr) ;
    end
    
catch
    % Rewrite the error, likely to be caused by feval
    % For instance, function expects more arguments, ...    
    ERR = lasterror ;
    if numel(varargin)>0,
        ERR.message = sprintf('%s\r(This could be caused by the additional arguments given to %s).',ERR.message, upper(mfilename)) ;
    end
    rethrow(ERR) ;
end % try-catch

% where is the first relative element
switch windowmode 
    case 1 % central
        x0 = -floor(W/2) ;
    case 2 % backward
        x0 = -(W-1) ;
    case 3 % forward
        x0 = 0 ;
end
x1 = x0+W-1 ; % last relative element
x = x0:x1 ; % window vector (has W elements)

R = R(ones(size(V))) ; % pre-allocation !!

% The engine: seperation in three sections is faster than using a single
% loop with calls to min and max. 

% 1. leading elements
iend = min(-x0,nV-x1) ; % what is the last leading element, note that this might not exist
for i=1:iend,
    R(i) = feval(FUN,V(1:i+x1),varargin{:}) ;
end

% 2. main portion of V, start were section 1 finished
for i=(iend+1):(nV-x1),
    R(i) = feval(FUN,V(x+i),varargin{:}) ;
end

% 3. trailing elements, start were section 2 finished
for i=(i+1):nV,
    R(i) = feval(FUN,V((i+x0):nV),varargin{:}) ;
end

% Some timings:
%   V = rand(10000,1) ; N = 3 ; tic ; R = slidefun(@max,N, V) ; toc ;
%   % -> elapsed_time = 0.1870
%   V = rand(10000,1) ; N = 100 ; tic ; R = slidefun(@max,N, V) ; toc ;
%   % -> elapsed_time = 0.3440