Repository navigation
Expand file tree
/
Copy pathsmooth.m
More file actions
683 lines (605 loc) · 19.7 KB
/
Copy pathsmooth.m
File metadata and controls
683 lines (605 loc) · 19.7 KB
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
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
function [c,ww] = smooth(varargin)
%SMOOTH Smooth data.
% Z = SMOOTH(Y) smooths data Y using a 5-point moving average.
%
% Z = SMOOTH(Y,SPAN) smooths data Y using SPAN as the number of points used
% to compute each element of Z.
%
% Z = SMOOTH(Y,SPAN,METHOD) smooths data Y with specified METHOD. The
% available methods are:
%
% 'moving' - Moving average (default)
% 'lowess' - Lowess (linear fit)
% 'loess' - Loess (quadratic fit)
% 'sgolay' - Savitzky-Golay
% 'rlowess' - Robust Lowess (linear fit)
% 'rloess' - Robust Loess (quadratic fit)
%
% Z = SMOOTH(Y,METHOD) uses the default SPAN 5.
%
% Z = SMOOTH(Y,SPAN,'sgolay',DEGREE) and Z = SMOOTH(Y,'sgolay',DEGREE)
% additionally specify the degree of the polynomial to be used in the
% Savitzky-Golay method. The default DEGREE is 2. DEGREE must be smaller
% than SPAN.
%
% Z = SMOOTH(X,Y,...) additionally specifies the X coordinates. If X is
% not provided, methods that require X coordinates assume X = 1:N, where
% N is the length of Y.
%
% Notes:
% 1. When X is given and X is not uniformly distributed, the default method
% is 'lowess'. The 'moving' method is not recommended.
%
% 2. For the 'moving' and 'sgolay' methods, SPAN must be odd.
% If an even SPAN is specified, it is reduced by 1.
%
% 3. If SPAN is greater than the length of Y, it is reduced to the
% length of Y.
%
% 4. In the case of (robust) lowess and (robust) loess, it is also
% possible to specify the SPAN as a percentage of the total number
% of data points. When SPAN is less than or equal to 1, it is
% treated as a percentage.
%
% For example:
%
% Z = SMOOTH(Y) uses the moving average method with span 5 and
% X=1:length(Y).
%
% Z = SMOOTH(Y,7) uses the moving average method with span 7 and
% X=1:length(Y).
%
% Z = SMOOTH(Y,'sgolay') uses the Savitzky-Golay method with DEGREE=2,
% SPAN = 5, X = 1:length(Y).
%
% Z = SMOOTH(X,Y,'lowess') uses the lowess method with SPAN=5.
%
% Z = SMOOTH(X,Y,SPAN,'rloess') uses the robust loess method.
%
% Z = SMOOTH(X,Y) where X is unevenly distributed uses the
% 'lowess' method with span 5.
%
% Z = SMOOTH(X,Y,8,'sgolay') uses the Savitzky-Golay method with
% span 7 (8 is reduced by 1 to make it odd).
%
% Z = SMOOTH(X,Y,0.3,'loess') uses the loess method where span is
% 30% of the data, i.e. span = ceil(0.3*length(Y)).
%
% See also SPLINE.
% Copyright 2001-2009 The MathWorks, Inc.
% $Revision: 1.17.4.14 $ $Date: 2009/12/02 06:41:43 $
if nargin < 1
error('curvefit:smooth:needMoreArgs', ...
'SMOOTH needs at least one argument.');
end
if nargout > 1 % Called from the GUI cftool
ws = warning('off', 'all'); % turn warning off and record the previous warning state.
[lw,lwid] = lastwarn;
lastwarn('');
else
ws = warning('query','all'); % Leave warning state alone but save it so resets are no-ops.
end
% is x given as the first argument?
if nargin==1 || ( nargin > 1 && (length(varargin{2})==1 || ischar(varargin{2})) )
% smooth(Y) | smooth(Y,span,...) | smooth(Y,method,...)
is_x = 0; % x is not given
y = varargin{1};
y = y(:);
x = (1:length(y))';
else % smooth(X,Y,...)
is_x = 1;
y = varargin{2};
x = varargin{1};
y = y(:);
x = x(:);
end
% is span given?
span = [];
if nargin == 1+is_x || ischar(varargin{2+is_x})
% smooth(Y), smooth(X,Y) || smooth(X,Y,method,..), smooth(Y,method)
is_span = 0;
else
% smooth(...,SPAN,...)
is_span = 1;
span = varargin{2+is_x};
end
% is method given?
method = [];
if nargin >= 2+is_x+is_span
% smooth(...,Y,method,...) | smooth(...,Y,span,method,...)
method = varargin{2+is_x+is_span};
end
t = length(y);
if t == 0
c = y;
ww = '';
if nargout > 1
ww = lastwarn;
lastwarn(lw,lwid);
warning(ws); % turn warning back to the previous state.
end
return
elseif length(x) ~= t
warning(ws); % reset warn state before erroring
error('curvefit:smooth:XYmustBeSameLength',...
'X and Y must be the same length.');
end
if isempty(method)
diffx = diff(x);
if uniformx(diffx,x,y)
method = 'moving'; % uniformly distributed X.
else
method = 'lowess';
end
end
% realize span
if span <= 0
warning(ws); % reset warn state before erroring
error('curvefit:smooth:spanMustBePositive', ...
'SPAN must be positive.');
end
if span < 1, span = ceil(span*t); end % percent convention
if isempty(span), span = 5; end % smooth(Y,[],method)
idx = 1:t;
sortx = any(diff(isnan(x))<0); % if NaNs not all at end
if sortx || any(diff(x)<0) % sort x
[x,idx] = sort(x);
y = y(idx);
end
c = NaN(size(y));
ok = ~isnan(x);
switch method
case 'moving'
c(ok) = moving(x(ok),y(ok),span);
case {'lowess','loess','rlowess','rloess'}
robust = 0;
iter = 5;
if method(1)=='r'
robust = 1;
method = method(2:end);
end
c(ok) = lowess(x(ok),y(ok),span, method,robust,iter);
case 'sgolay'
if nargin >= 3+is_x+is_span
degree = varargin{3+is_x+is_span};
else
degree = 2;
end
if degree < 0 || degree ~= floor(degree) || degree >= span
warning(ws); % reset warn state before erroring
error('curvefit:smooth:invalidDegree', ...
'Degree must be an integer between 0 and span-1.');
end
c(ok) = sgolay(x(ok),y(ok),span,degree);
otherwise
warning(ws); % reset warn state before erroring
error('curvefit:smooth:unrecognizedMethod', ...
'SMOOTH: Unrecognized method.');
end
c(idx) = c;
if nargout > 1
ww = lastwarn;
lastwarn(lw,lwid);
warning(ws); % turn warning back to the previous state.
end
%--------------------------------------------------------------------
function c = moving(x,y, span)
% moving average of the data.
ynan = isnan(y);
span = floor(span);
n = length(y);
span = min(span,n);
width = span-1+mod(span,2); % force it to be odd
xreps = any(diff(x)==0);
if width==1 && ~xreps && ~any(ynan), c = y; return; end
if ~xreps && ~any(ynan)
% simplest method for most common case
c = filter(ones(width,1)/width,1,y);
cbegin = cumsum(y(1:width-2));
cbegin = cbegin(1:2:end)./(1:2:(width-2))';
cend = cumsum(y(n:-1:n-width+3));
cend = cend(end:-2:1)./(width-2:-2:1)';
c = [cbegin;c(width:end);cend];
elseif ~xreps
% with no x repeats, can take ratio of two smoothed sequences
yy = y;
yy(ynan) = 0;
nn = double(~ynan);
ynum = moving(x,yy,span);
yden = moving(x,nn,span);
c = ynum ./ yden;
else
% with some x repeats, loop
notnan = ~ynan;
yy = y;
yy(ynan) = 0;
c = zeros(n,1);
for i=1:n
if i>1 && x(i)==x(i-1)
c(i) = c(i-1);
continue;
end
R = i; % find rightmost value with same x
while(R<n && x(R+1)==x(R))
R = R+1;
end
hf = ceil(max(0,(span - (R-i+1))/2)); % need this many more on each side
hf = min(min(hf,(i-1)), (n-R));
L = i-hf; % find leftmost point needed
while(L>1 && x(L)==x(L-1))
L = L-1;
end
R = R+hf; % find rightmost point needed
while(R<n && x(R)==x(R+1))
R = R+1;
end
c(i) = sum(yy(L:R)) / sum(notnan(L:R));
end
end
%--------------------------------------------------------------------
function c = lowess(x,y, span, method, robust, iter)
% LOWESS Smooth data using Lowess or Loess method.
%
% The difference between LOWESS and LOESS is that LOWESS uses a
% linear model to do the local fitting whereas LOESS uses a
% quadratic model to do the local fitting. Some other software
% may not have LOWESS, instead, they use LOESS with order 1 or 2 to
% represent these two smoothing methods.
%
% Reference:
% [C79] W.S.Cleveland, "Robust Locally Weighted Regression and Smoothing
% Scatterplots", _J. of the American Statistical Ass._, Vol 74, No. 368
% (Dec.,1979), pp. 829-836.
% http://www.math.tau.ac.il/~yekutiel/MA%20seminar/Cleveland%201979.pdf
n = length(y);
span = floor(span);
span = min(span,n);
c = y;
if span == 1
return;
end
useLoess = false;
if isequal(method,'loess')
useLoess = true;
end
diffx = diff(x);
% For problems where x is uniform, there's a faster way
isuniform = uniformx(diffx,x,y);
if isuniform
% For uniform data, an even span actually covers an odd number of
% points. For example, the four closest points to 5 in the
% sequence 1:10 are {3,4,5,6}, but 7 is as close as 3.
% Therefore force an odd span.
span = 2*floor(span/2) + 1;
c = unifloess(y,span,useLoess);
if ~robust || span<=2
return;
end
end
% Turn off warnings when called from command line (already off if called from
% cftool).
ws = warning( 'off', 'MATLAB:rankDeficientMatrix' );
cleanup = onCleanup( @() warning( ws ) );
ynan = isnan(y);
anyNans = any(ynan(:));
seps = sqrt(eps);
theDiffs = [1; diffx; 1];
if isuniform
% We've already computed the non-robust smooth, so in preparation for
% the robust smooth, compute the following arrays directly
halfw = floor(span/2);
% Each local interval is from |halfw| below the current index to |halfw|
% above
lbound = (1:n)-halfw;
rbound = (1:n)+halfw;
% However, there always has to be at least |span| points to the right of the
% left bound
lbound = min( n+1-span, lbound );
% ... and at least |span| points to the left of the right bound
rbound = max( span, rbound );
% Furthermore, because these bounds index into vectors of length n, they
% must contain valid indices
lbound = max( 1, lbound );
rbound = min( n, rbound );
% Since the input is uniform we can use natural numbers for the input when
% we need them.
x = (1:numel(x))';
else
if robust
% pre-allocate space for lower and upper indices for each fit,
% to avoid re-computing this information in robust iterations
lbound = zeros(n,1);
rbound = zeros(n,1);
end
% Compute the non-robust smooth for non-uniform x
for i=1:n
% if x(i) and x(i-1) are equal we just use the old value.
if theDiffs(i) == 0
c(i) = c(i-1);
if robust
lbound(i) = lbound(i-1);
rbound(i) = rbound(i-1);
end
continue;
end
% Find nearest neighbours
idx = iKNearestNeighbours( span, i, x, ~ynan );
if robust
% Need to store neighborhoods for robust loop
lbound(i) = min(idx);
rbound(i) = max(idx);
end
if isempty(idx)
c(i) = NaN;
continue
end
x1 = x(idx)-x(i); % center around current point to improve conditioning
d1 = abs(x1);
y1 = y(idx);
weight = iTricubeWeights( d1 );
if all(weight<seps)
weight(:) = 1; % if all weights are 0, just skip weighting
end
v = [ones(size(x1)) x1];
if useLoess
v = [v x1.*x1]; %#ok<AGROW> There is no significant growth here
end
v = weight(:,ones(1,size(v,2))).*v;
y1 = weight.*y1;
if size(v,1)==size(v,2)
% Square v may give infs in the \ solution, so force least squares
b = [v;zeros(1,size(v,2))]\[y1;0];
else
b = v\y1;
end
c(i) = b(1);
end
end
% now that we have a non-robust fit, we can compute the residual and do
% the robust fit if required
maxabsyXeps = max(abs(y))*eps;
if robust
for k = 1:iter
r = y-c;
% Compute robust weights
rweight = iBisquareWeights( r, maxabsyXeps );
% Find new value for each point.
for i=1:n
if i>1 && x(i)==x(i-1)
c(i) = c(i-1);
continue;
end
if isnan(c(i)),
continue;
end
idx = lbound(i):rbound(i);
if anyNans
idx = idx(~ynan(idx));
end
% check robust weights for removed points
if any( rweight(idx) <= 0 )
idx = iKNearestNeighbours( span, i, x, (rweight > 0) );
end
x1 = x(idx) - x(i);
d1 = abs(x1);
y1 = y(idx);
weight = iTricubeWeights( d1 );
if all(weight<seps)
weight(:) = 1; % if all weights 0, just skip weighting
end
v = [ones(size(x1)) x1];
if useLoess
v = [v x1.*x1]; %#ok<AGROW> There is no significant growth here
end
% Modify the weights based on x values by multiplying them by
% robust weights.
weight = weight.*rweight(idx);
v = weight(:,ones(1,size(v,2))).*v;
y1 = weight.*y1;
if size(v,1)==size(v,2)
% Square v may give infs in the \ solution, so force least squares
b = [v;zeros(1,size(v,2))]\[y1;0];
else
b = v\y1;
end
c(i) = b(1);
end
end
end
%--------------------------------------------------------------------
function c=sgolay(x,y,f,k)
% savitziki-golay smooth
% (x,y) are given data. f is the frame length to be taken, should
% be an odd number. k is the degree of polynomial filter. It should
% be less than f.
% Reference: Orfanidis, S.J., Introduction to Signal Processing,
% Prentice-Hall, Englewood Cliffs, NJ, 1996.
n = length(x);
f = floor(f);
f = min(f,n);
f = f-mod(f-1,2); % will subtract 1 if frame is even.
diffx = diff(x);
notnan = ~isnan(y);
nomissing = all(notnan);
if f <= k && all(diffx>0) && nomissing, c = y; return; end
hf = (f-1)/2; % half frame length
idx = 1:n;
if any(diffx<0) % make sure x is monotonically increasing
[x,idx]=sort(x);
y = y(idx);
notnan = notnan(idx);
diffx = diff(x);
end
% note that x is sorted so max(abs(x)) must be abs(x(1)) or abs(x(end));
% already calculated diffx for monotonic case, so use it again. Only
% recalculate if we sort x.
if nomissing && uniformx(diffx,x,y)
v = ones(f,k+1);
t=(-hf:hf)';
for i=1:k
v(:,i+1)=t.^i;
end
[q,~]=qr(v,0);
ymid = filter(q*q(hf+1,:)',1,y);
ybegin = q(1:hf,:)*q'*y(1:f);
yend = q((hf+2):end,:)*q'*y(n-f+1:n);
c = [ybegin;ymid(f:end);yend];
return;
end
% non-uniformly distributed data
c = y;
% Turn off warnings when called from command line (already off if called from
% cftool).
ws = warning('off', 'all');
[lastwarnmsg,lastwarnid]=lastwarn;
for i = 1:n
if i>1 && x(i)==x(i-1)
c(i) = c(i-1);
continue
end
L = i; R = i; % find leftmost and rightmost values
while(R<n && x(R+1)==x(i))
R = R+1;
end
while(L>1 && x(L-1)==x(i))
L = L-1;
end
HF = ceil(max(0,(f - (R-L+1))/2)); % need this many more on each side
L = min(n-f+1,max(1,L-HF)); % find leftmost point needed
while(L>1 && x(L)==x(L-1))
L = L-1;
end
R = min(n,max(R+HF,L+f-1)); % find rightmost point needed
while(R<n && x(R)==x(R+1))
R = R+1;
end
tidx = L:R;
tidx = tidx(notnan(tidx));
if isempty(tidx)
c(i) = NaN;
continue;
end
q = x(tidx) - x(i); % center to improve conditioning
vrank = 1 + sum(diff(q)>0);
ncols = min(k+1, vrank);
v = ones(length(q),ncols);
for j = 1:ncols-1
v(:,j+1) = q.^j;
end
if size(v,1)==size(v,2)
% Square v may give infs in the \ solution, so force least squares
d = [v;zeros(1,size(v,2))]\[y(tidx);0];
else
d = v\y(tidx);
end
c(i) = d(1);
end
c(idx) = c;
lastwarn(lastwarnmsg,lastwarnid);
warning(ws);
% --------------------------------------------
function ys = unifloess(y,span,useLoess)
%UNIFLOESS Apply loess on uniformly spaced X values
y = y(:);
% Omit points at the extremes, which have zero weight
halfw = (span-1)/2; % halfwidth of entire span
d = abs((1-halfw:halfw-1)); % distances to pts with nonzero weight
dmax = halfw; % max distance for tri-cubic weight
% Set up weighted Vandermonde matrix using equally spaced X values
x1 = (2:span-1)-(halfw+1);
weight = (1 - (d/dmax).^3).^1.5; % tri-cubic weight
v = [ones(length(x1),1) x1(:)];
if useLoess
v = [v x1(:).^2];
end
V = v .* repmat(weight',1,size(v,2));
% Do QR decomposition
[Q,~] = qr(V,0);
% The projection matrix is Q*Q'. We want to project onto the middle
% point, so we can take just one row of the first factor.
alpha = Q(halfw,:)*Q';
% This alpha defines the linear combination of the weighted y values that
% yields the desired smooth values. Incorporate the weights into the
% coefficients of the linear combination, then apply filter.
alpha = alpha .* weight;
ys = filter(alpha,1,y);
% We need to slide the values into the center of the array.
ys(halfw+1:end-halfw) = ys(span-1:end-1);
% Now we have taken care of everything except the end effects. Loop over
% the points where we don't have a complete span. Now the Vandermonde
% matrix has span-1 points, because only 1 has zero weight.
x1 = 1:span-1;
v = [ones(length(x1),1) x1(:)];
if useLoess
v = [v x1(:).^2];
end
for j=1:halfw
% Compute weights based on deviations from the jth point,
% then compute weights and apply them as above.
d = abs((1:span-1) - j);
weight = (1 - (d/(span-j)).^3).^1.5;
V = v .* repmat(weight(:),1,size(v,2));
[Q,~] = qr(V,0);
alpha = Q(j,:)*Q';
alpha = alpha .* weight;
ys(j) = alpha * y(1:span-1);
% These coefficients can be applied to the other end as well
ys(end+1-j) = alpha * y(end:-1:end-span+2);
end
% -----------------------------------------
function isuniform = uniformx(diffx,x,y)
%ISUNIFORM True if x is of the form a:b:c
if any(isnan(y)) || any(isnan(x))
isuniform = false;
else
isuniform = all(abs(diff(diffx)) <= eps*max(abs([x(1),x(end)])));
end
%------------------------
function idx = iKNearestNeighbours( k, i, x, in )
% Find the k points from x(in) closest to x(i)
if nnz( in ) <= k
% If we have k points or fewer, then return them all
idx = find( in );
else
% Find the distance to the k closest point
d = abs( x - x(i) );
ds = sort( d(in) );
dk = ds(k);
% Find all points that are as close as or closer than the k closest point
close = (d <= dk);
% The required indices are those points that are both close and "in"
idx = find( close & in );
end
% -----------------------------------------
% Bi-square (robust) weight function
function delta = iBisquareWeights( r, myeps )
% Convert residuals to weights using the bi-square weight function.
% NOTE that this function returns the square root of the weights
% Only use non-NaN residuals to compute median
idx = ~isnan( r );
% And bound the median away from zero
s = max( 1e8 * myeps, median( abs( r(idx) ) ) );
% Covert the residuals to weights
delta = iBisquare( r/(6*s) );
% Everything with NaN residual should have zero weight
delta(~idx) = 0;
function b = iBisquare( x )
% This is this bi-square function defined at the top of the left hand
% column of page 831 in [C79]
% NOTE that this function returns the square root of the weights
b = zeros( size( x ) );
idx = abs( x ) < 1;
b(idx) = abs( 1 - x(idx).^2 );
%------------------------
% Tri-cubic weight function
function w = iTricubeWeights( d )
% Convert distances into weights using tri-cubic weight function.
% NOTE that this function returns the square-root of the weights.
%
% Protect against divide-by-zero. This can happen if more points than the span
% are coincident.
maxD = max( d );
if maxD > 0
d = d/max( d );
end
w = (1 - d.^3).^1.5;