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