function [z,s,exitflag] = smoothn(varargin)
%SMOOTHN Robust spline smoothing for 1-D to N-D data.
% SMOOTHN provides a fast, automatized and robust discretized smoothing
% spline for data of any dimension.
%
% Z = SMOOTHN(Y) automatically smoothes the uniformly-sampled array Y. Y
% can be any N-D noisy array (time series, images, 3D data,...). Non
% finite data (NaN or Inf) are treated as missing values.
%
% Z = SMOOTHN(Y,S) smoothes the array Y using the smoothing parameter S.
% S must be a real positive scalar. The larger S is, the smoother the
% output will be. If the smoothing parameter S is omitted (see previous
% option) or empty (i.e. S = []), it is automatically determined using
% the generalized cross-validation (GCV) method.
%
% Z = SMOOTHN(Y,W) or Z = SMOOTHN(Y,W,S) specifies a weighting array W of
% real positive values, that must have the same size as Y. Note that a
% nil weight corresponds to a missing value.
%
% Robust smoothing
% ----------------
% Z = SMOOTHN(...,'robust') carries out a robust smoothing that minimizes
% the influence of outlying data.
%
% [Z,S] = SMOOTHN(...) also returns the calculated value for S so that
% you can fine-tune the smoothing subsequently if needed.
%
% An iteration process is used in the presence of weighted and/or missing
% values. Z = SMOOTHN(...,OPTION_NAME,OPTION_VALUE) smoothes with the
% termination parameters specified by OPTION_NAME and OPTION_VALUE. They
% can contain the following criteria:
% -----------------
% TolZ: Termination tolerance on Z (default = 1e-3)
% TolZ must be in ]0,1[
% MaxIter: Maximum number of iterations allowed (default = 100)
% -----------------
% Syntax: [Z,...] = SMOOTHN(...,'MaxIter',500,'TolZ',1e-4);
%
% [Z,S,EXITFLAG] = SMOOTHN(...) returns a boolean value EXITFLAG that
% describes the exit condition of SMOOTHN:
% 1 SMOOTHN converged.
% 0 Maximum number of iterations was reached.
%
% Over- & under-smoothing
% -----------------------
% [...] = SMOOTHN(...,'over') or [...] = SMOOTHN(...,'under') forces
% over- or under-smoothing. The smoothing parameter S is first determined
% automatically then multiplied (over-smoothing) or divided (under-
% smoothing) by 100.
%
% Class Support
% -------------
% Input array can be numeric or logical. The returned array is of class
% double.
%
% Notes
% -----
% The N-D (inverse) discrete cosine transform functions DCTN and IDCTN are required.
%
% Reference
% ---------
% Garcia D, Robust smoothing of gridded data in one and higher dimensions
% with missing values. Computational Statistics & Data Analysis, 2010.
% PDF download
%
% Examples:
% --------
% % 1-D example
% x = linspace(0,100,2^8);
% y = cos(x/10)+(x/50).^2 + randn(size(x))/10;
% y([70 75 80]) = [5.5 5 6];
% z = smoothn(y); % Regular smoothing
% zr = smoothn(y,'robust'); % Robust smoothing
% subplot(121), plot(x,y,'r.',x,z,'k','LineWidth',2)
% axis square, title('Regular smoothing')
% subplot(122), plot(x,y,'r.',x,zr,'k','LineWidth',2)
% axis square, title('Robust smoothing')
%
% % 2-D example
% xp = 0:.02:1;
% [x,y] = meshgrid(xp);
% f = exp(x+y) + sin((x-2*y)*3);
% fn = f + randn(size(f))*0.5;
% fs = smoothn(fn);
% subplot(121), surf(xp,xp,fn), zlim([0 8]), axis square
% subplot(122), surf(xp,xp,fs), zlim([0 8]), axis square
%
% % 2-D example with missing data
% n = 256;
% y0 = peaks(n);
% y = y0 + rand(size(y0))*2;
% I = randperm(n^2);
% y(I(1:n^2*0.5)) = NaN; % lose 1/2 of data
% y(40:90,140:190) = NaN; % create a hole
% z = smoothn(y); % smooth data
% subplot(2,2,1:2), imagesc(y), axis equal off
% title('Noisy corrupt data')
% subplot(223), imagesc(z), axis equal off
% title('Recovered data ...')
% subplot(224), imagesc(y0), axis equal off
% title('... compared with original data')
%
% % 3-D example
% [x,y,z] = meshgrid(-2:.2:2);
% xslice = [-0.8,1]; yslice = 2; zslice = [-2,0];
% vn = x.*exp(-x.^2-y.^2-z.^2) + randn(size(x))*0.06;
% subplot(121), slice(x,y,z,vn,xslice,yslice,zslice,'cubic')
% title('Noisy data')
% v = smoothn(vn);
% subplot(122), slice(x,y,z,v,xslice,yslice,zslice,'cubic')
% title('Smoothed data')
%
% % Cardioid
% t = linspace(0,2*pi,1000);
% x = 2*cos(t).*(1-cos(t)) + randn(size(t))*0.1;
% y = 2*sin(t).*(1-cos(t)) + randn(size(t))*0.1;
% z = smoothn(complex(x,y));
% plot(x,y,'r.',real(z),imag(z),'k','linewidth',2)
% axis equal tight
%
% % Cellular vortical flow
% [x,y] = meshgrid(linspace(0,1,24));
% Vx = cos(2*pi*x+pi/2).*cos(2*pi*y);
% Vy = sin(2*pi*x+pi/2).*sin(2*pi*y);
% Vx = Vx + sqrt(0.05)*randn(24,24); % adding Gaussian noise
% Vy = Vy + sqrt(0.05)*randn(24,24); % adding Gaussian noise
% I = randperm(numel(Vx));
% Vx(I(1:30)) = (rand(30,1)-0.5)*5; % adding outliers
% Vy(I(1:30)) = (rand(30,1)-0.5)*5; % adding outliers
% Vx(I(31:60)) = NaN; % missing values
% Vy(I(31:60)) = NaN; % missing values
% Vs = smoothn(complex(Vx,Vy),'robust'); % automatic smoothing
% subplot(121), quiver(x,y,Vx,Vy,2.5), axis square
% title('Noisy velocity field')
% subplot(122), quiver(x,y,real(Vs),imag(Vs)), axis square
% title('Smoothed velocity field')
%
% See also SMOOTH, GSMOOTHN, DCTN, IDCTN.
%
% -- Damien Garcia -- 2009/03, revised 2010/04
% Visit my website for more details about SMOOTHN
% Check input arguments
error(nargchk(1,8,nargin));
%% Test & prepare the variables
%---
k = 0;
while k=0')
end
%---
% "Maximal number of iterations" criterion
I = find(strcmpi(varargin,'MaxIter'),1);
if isempty(I)
MaxIter = 100; % default value for MaxIter
else
try
MaxIter = varargin{I+1};
catch
error('MATLAB:smoothn:IncorrectMaxIter',...
'MaxIter must be an integer >=1')
end
if ~isnumeric(MaxIter) || ~isscalar(MaxIter) ||...
MaxIter<1 || MaxIter~=round(MaxIter)
error('MATLAB:smoothn:IncorrectMaxIter',...
'MaxIter must be an integer >=1')
end
end
%---
% "Tolerance on smoothed output" criterion
I = find(strcmpi(varargin,'TolZ'),1);
if isempty(I)
TolZ = 1e-3; % default value for TolZ
else
try
TolZ = varargin{I+1};
catch
error('MATLAB:smoothn:IncorrectTolZ',...
'TolZ must be in ]0,1[')
end
if ~isnumeric(TolZ) || ~isscalar(TolZ) || TolZ<=0 || TolZ>=1
error('MATLAB:smoothn:IncorrectTolZ',...
'TolZ must be in ]0,1[')
end
end
%---
% Weights. Zero weights are assigned to not finite values (Inf or NaN),
% (Inf/NaN values = missing data).
IsFinite = isfinite(y);
nof = nnz(IsFinite); % number of finite elements
W = W.*IsFinite;
if any(W<0)
error('MATLAB:smoothn:NegativeWeights',...
'Weights must all be >=0')
else
W = W/max(W(:));
end
%---
% Weighted or missing data?
isweighted = any(W(:)<1);
%---
% Robust smoothing?
isrobust = any(strcmpi(varargin,'robust'));
%---
% Automatic smoothing?
isauto = isempty(s);
%---
% Over- or under-smoothing?
isover = any(strcmpi(varargin,'over'));
isunder = any(strcmpi(varargin,'under'));
if isover&&isunder
isover = false; isunder = false;
elseif isover&&~isauto
error('MATLAB:smoothn:OverOption',...
'''over'' option requires automatic smoothing.')
elseif isunder&&~isauto
error('MATLAB:smoothn:UnderOption',...
'''under'' option requires automatic smoothing.')
end
%---
% DCTN and IDCTN are required
test4DCTNandIDCTN
%% Creation of the Lambda tensor
%---
% Lambda contains the eingenvalues of the difference matrix used in this
% penalized least squares process.
d = ndims(y);
Lambda = zeros(sizy);
for i = 1:d
siz0 = ones(1,d);
siz0(i) = sizy(i);
Lambda = bsxfun(@plus,Lambda,...
cos(pi*(reshape(1:sizy(i),siz0)-1)/sizy(i)));
end
Lambda = -2*(d-Lambda);
if ~isauto, Gamma = 1./(1+s*Lambda.^2); end
%% Upper and lower bound for the smoothness parameter
% The average leverage (h) is by definition in [0 1]. Weak smoothing occurs
% if h is close to 1, while over-smoothing appears when h is near 0. Upper
% and lower bounds for h are given to avoid under- or over-smoothing. See
% equation relating h to the smoothness parameter (Equation #12 in the
% referenced CSDA paper).
N = sum(sizy~=1); % tensor rank of the y-array
hMin = 1e-6; hMax = 0.99;
sMinBnd = (((1+sqrt(1+8*hMax.^(2/N)))/4./hMax.^(2/N)).^2-1)/16;
sMaxBnd = (((1+sqrt(1+8*hMin.^(2/N)))/4./hMin.^(2/N)).^2-1)/16;
%% Initialize before iterating
%---
Wtot = W;
%--- Initial conditions for z
if isweighted
%--- With weighted/missing data
% An initial guess is provided to ensure faster convergence. For that
% purpose, a nearest neighbor interpolation followed by a coarse
% smoothing are performed.
%---
z = InitialGuess(y,IsFinite);
else
z = zeros(sizy);
end
%---
z0 = z;
y(~IsFinite) = 0; % arbitrary values for missing y-data
%---
tol = 1;
RobustIterativeProcess = true;
RobustStep = 1;
nit = 0;
%--- Error on p. Smoothness parameter s = 10^p
errp = 0.1;
opt = optimset('TolX',errp);
%--- Relaxation factor RF: to speedup convergence
RF = 1 + 0.75*isweighted;
%% Main iterative process
%---
while RobustIterativeProcess
%--- "amount" of weights (see the function GCVscore)
aow = sum(Wtot(:))/noe; % 0 < aow <= 1
%---
while tol>TolZ && nit tol=0 (no iteration)
tol = isweighted*norm(z0(:)-z(:))/norm(z(:));
z0 = z;
end
exitflag = nit0.9 % aow = 1 means that all of the data are equally weighted
% very much faster: does not require any inverse DCT
RSS = norm(DCTy(:).*(Gamma(:)-1))^2;
else
% take account of the weights to calculate RSS:
yhat = idctn(Gamma.*DCTy);
RSS = norm(sqrt(Wtot(IsFinite)).*(y(IsFinite)-yhat(IsFinite)))^2;
end
%---
TrH = sum(Gamma(:));
GCVscore = RSS/nof/(1-TrH/noe)^2;
end
end
%% Robust weights
function W = RobustWeights(r,I,h)
% weights for robust smoothing.
MAD = median(abs(r(I)-median(r(I)))); % median absolute deviation
u = abs(r/(1.4826*MAD)/sqrt(1-h)); % studentized residuals
c = 4.685; W = (1-(u/c).^2).^2.*((u/c)<1); % bisquare weights
% c = 2.385; W = 1./(1+(u/c).^2); % Cauchy weights
W(isnan(W)) = 0;
end
%% Test for DCTN and IDCTN
function test4DCTNandIDCTN
if ~exist('dctn','file')
error('MATLAB:smoothn:MissingFunction',...
['DCTN and IDCTN are required. Download DCTN here.'])
elseif ~exist('idctn','file')
error('MATLAB:smoothn:MissingFunction',...
['DCTN and IDCTN are required. Download IDCTN here.'])
end
end
%% Initial Guess with weighted/missing data
function z = InitialGuess(y,I)
%-- nearest neighbor interpolation (in case of missing values)
if any(~I(:))
if license('test','image_toolbox')
[z,L] = bwdist(I);
z = y;
z(~I) = y(L(~I));
else
% If BWDIST does not exist, NaN values are all replaced with the
% same scalar. The initial guess is not optimal and a warning
% message thus appears.
z = y;
z(~I) = mean(y(I));
warning('MATLAB:smoothn:InitialGuess',...
['BWDIST (Image Processing Toolbox) does not exist. ',...
'The initial guess may not be optimal; additional',...
' iterations can thus be required to ensure complete',...
' convergence. Increase ''MaxIter'' criterion if necessary.'])
end
else
z = y;
end
%-- coarse fast smoothing using one-tenth of the DCT coefficients
siz = size(z);
z = dctn(z);
for k = 1:ndims(z)
z(ceil(siz(k)/10)+1:end,:) = 0;
z = reshape(z,circshift(siz,[0 1-k]));
z = shiftdim(z,1);
end
z = idctn(z);
end