-
Notifications
You must be signed in to change notification settings - Fork 0
/
smoothn.m
455 lines (438 loc) · 15.6 KB
/
smoothn.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
function [z,s,exitflag] = smoothn(varargin)
%SMOOTHN Fast smoothing of 1-D to N-D data.
% 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 <a
% href="matlab:web('http://www.biomecardio.com/matlab/dctn.html')"
% >DCTN</a> and <a
% href="matlab:web('http://www.biomecardio.com/matlab/idctn.html')"
% >IDCTN</a> are required.
%
% Reference
% ---------
% Garcia D, Robust smoothing of gridded data in one and higher dimensions
% with missing values. Computational Statistics & Data Analysis, 2009,
% <a href="matlab:web('http://www.biomecardio.com/pageshtm/publi/csda09.pdf')">doi:10.1016/j.csda.2009.09.020</a>
%
% 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, DCTN, IDCTN.
%
% -- Damien Garcia -- 2009/03, revised 2009/12
% Visit my <a
% href="matlab:web('http://www.biomecardio.com/matlab/smoothn.html')">website</a> for more details about SMOOTHN
% Check input arguments
error(nargchk(1,6,nargin));
%% Test & prepare the variables
%---
k = 0;
while k<nargin && ~ischar(varargin{k+1}), k = k+1; end
%---
% y = array to be smoothed
y = double(varargin{1});
sizy = size(y);
noe = prod(sizy); % number of elements
if noe<2, z = y; return, end
%---
% Smoothness parameter and weights
W = ones(sizy);
s = [];
if k==2
if isempty(varargin{2}) || isscalar(varargin{2}) % smoothn(y,s)
s = varargin{2}; % smoothness parameter
else % smoothn(y,W)
W = varargin{2}; % weight array
end
elseif k==3 % smoothn(y,W,s)
W = varargin{2}; % weight array
s = varargin{3}; % smoothness parameter
end
if ~isequal(size(W),sizy)
error('MATLAB:smoothn:SizeMismatch',...
'Arrays for data and weights must have same size.')
elseif ~isempty(s) && (~isscalar(s) || s<0)
error('MATLAB:smoothn:IncorrectSmoothingParameter',...
'The smoothing parameter must be a scalar >=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 ~isempty(s), 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 = 0;
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<MaxIter
nit = nit+1;
DCTy = dctn(Wtot.*(y-z)+z);
if isauto && ~rem(log2(nit),1)
%---
% The generalized cross-validation (GCV) method is used.
% We seek the smoothing parameter s that minimizes the GCV
% score i.e. s = Argmin(GCVscore)
% Because this process is time-consuming, it is performed from
% time to time (when nit is a power of 2)
%---
fminbnd(@gcv,log10(sMinBnd),log10(sMaxBnd),opt);
end
GammaDCTy = Gamma.*DCTy;
z = RF*idctn(GammaDCTy) + (1-RF)*z;
% if no weighted/missing data => tol=0 (no iteration)
tol = isweighted*norm(z0(:)-z(:))/norm(z(:));
z0 = z;
end
exitflag = nit<MaxIter;
if isrobust %-- Robust Smoothing: iteratively re-weighted process
%--- average leverage
h = sqrt(1+16*s); h = sqrt(1+h)/sqrt(2)/h; h = h^N;
%--- take robust weights into account
Wtot = W.*RobustWeights(y-z,IsFinite,h);
%--- re-initialize for another iterative weighted process
isweighted = true; tol = 1; nit = 0;
%---
RobustStep = RobustStep+1;
RobustIterativeProcess = RobustStep<4; % 3 robust steps are enough.
else
RobustIterativeProcess = false; % stop the whole process
end
end
%% Over- / Under-smoothing
%---
if isunder || isover
s = isunder*s/100 + isover*s*100;
Gamma = 1./(1+s*Lambda.^2);
GammaDCTy = Gamma.*DCTy;
z = idctn(GammaDCTy);
end
%% Warning messages
%---
if abs(log10(s)-log10(sMinBnd))<errp
warning('MATLAB:smoothn:SLowerBound',...
['s = ' num2str(s,'%.3e') ': the lower bound for s ',...
'has been reached. Put s as an input variable if required.'])
elseif abs(log10(s)-log10(sMaxBnd))<errp
warning('MATLAB:smoothn:SUpperBound',...
['s = ' num2str(s,'%.3e') ': the upper bound for s ',...
'has been reached. Put s as an input variable if required.'])
end
if nargout<3 && ~exitflag
warning('MATLAB:smoothn:MaxIter',...
['Maximum number of iterations (' int2str(MaxIter) ') has ',...
'been exceeded. Increase MaxIter option or decrease TolZ value.'])
end
%% GCV score
%---
function GCVscore = gcv(p)
% Search the smoothing parameter s that minimizes the GCV score
%---
s = 10^p;
Gamma = 1./(1+s*Lambda.^2);
%--- RSS = Residual sum-of-squares
if aow>0.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 <a href="matlab:web(''',...
'http://www.biomecardio.com/matlab/dctn.html'')">here</a>.'])
elseif ~exist('idctn','file')
error('MATLAB:smoothn:MissingFunction',...
['DCTN and IDCTN are required. Download IDCTN <a href="matlab:web(''',...
'http://www.biomecardio.com/matlab/idctn.html'')">here</a>.'])
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