-
Notifications
You must be signed in to change notification settings - Fork 9
/
nt_tsr.m
151 lines (131 loc) · 5.15 KB
/
nt_tsr.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
function [y,idx,w]=nt_tsr(x,ref,shifts,wx,wref,keep,thresh)
%[y,idx,w]=nt_tsr(x,ref,shifts,wx,wref,keep,thresh) - time-shift regression (TSPCA)
%
% y: denoised data
% idx: x(idx) is aligned with y
% w: weights applied by tsr
%
% x: data to denoise (time * channels * trials)
% ref: reference (time * channels * trials)
% shifts: array of shifts to apply to ref (default: [0])
% wx: weights to apply to x (time * 1 * trials);
% wref: weights to apply to ref (time * 1 * trials);
% keep: number of shifted-ref PCs to retain (default: all)
% thresh: ignore shifted-ref PCs smaller than thresh (default: 10.^-12)
%
% NoiseTools
nt_greetings;
% Copyright 2007, 2008 Alain de Cheveigne
% See:
% de Cheveign\'e, A. and Simon, J. Z. (2007). "Denoising based on
% Time-Shift PCA." Journal of Neuroscience Methods 165: 297-305.
%
% The basic idea is to project the signal X on a basis formed by the
% orthogonalized time-shifted REF, and remove the projection. Supposing REF
% gives a good observation of the noise that contaminates X, the noise is
% removed. By allowing time shifts, the algorithm finds the optimal FIR filter
% to apply to REF so as to compensate for any convolutional mismatch
% between X and REF.
%
% Implementation issues:
% - Data are often available as an array of epochs. This implementation
% caters for 3D data (time * channnels * trials);
% - It is important to deemphasize high amplitude artifacts and glitches
% so that they do not dominate the solution. This implementation uses
% weighted covariance and means.
% - Processing assumes zero-means data. Means are calculated with weights.
% - The implementation tries to be efficent and minimize memory requirements
% so as to handle large data sets.
%
% Larger data sets (disk based) could be handled by performing mean and
% covariance calculations block-by-block, in several passes.
%
if nargin<2; error('too few arguments'); end
if nargin<3 || isempty(shifts); shifts=0; end
if nargin<4; wx=[]; end
if nargin<5; wref=[]; end
if nargin<6 || isempty(keep); keep=[]; end
if nargin<7 || isempty(thresh); thresh=10.^-20; end
% check argument values for sanity
if size(x,1)~=size(ref,1); error('X and REF should have same nrows'); end
if size(x,3)~=size(ref,3); error('X and REF should have same npages'); end
if ~isempty(wx) && size(x,3)~=size(wx,3); error('X and WX should have same npages'); end
if ~isempty(wx) && size(x,1)~=size(wx,1); error('X and WX should have same nrows'); end
if ~isempty(wref) && size(ref,1)~=size(wref,1); error('REF and WREF should have same nrows'); end
if ~isempty(wref) && size(ref,3)~=size(wref,3); error('REF and WREF should have same npages'); end
if max(shifts)-min(0,min(shifts)) >= size(x,1); error('X has too few samples to support SHIFTS'); end
if ~isempty(wx) && size(wx,2)~=1; error('wx should have ncols=1'); end
if ~isempty(wref) && size(wref,2)~=1; error('wref should have ncols=1'); end
if ~isempty(wx) && sum(wx(:))==0; error('weights on x are all zero!'); end
if ~isempty(wref) && sum(wref(:))==0; error('weights on ref are all zero!'); end
if numel(shifts)>1000; error(['numel(shifts)=',num2str(numel(shifts)), ' (if OK comment out this line)']); end
% We need to adjust x and ref to ensure that shifts are non-negative.
% If some values of shifts are negative, we increment shifts and truncate x.
% adjust x to make shifts non-negative
offset1=max(0,-min(shifts));
idx=1+offset1:size(x,1);
x=x(idx,:,:); % truncate x
if ~isempty(wx); wx=wx(idx,:,:); end
shifts=shifts+offset1; % shifts are now positive
% adjust size of x
offset2=max(0,max(shifts));
idx=1: size(ref,1)-offset2;
x=x(idx,:,:); % part of x that overlaps with time-shifted refs
if ~isempty(wx); wx=wx(idx,:,:); end
[mx,nx,ox]=size(x);
[mref,nref,oref]=size(ref);
% consolidate weights into single weight matrix
w=zeros([mx,1,oref]);
if isempty(wx) && isempty(wref)
w(1:mx,:,:)=1;
elseif isempty(wref);
w(:,:,:)=wx(:,:,:);
elseif isempty(wx)
for k=1:ox
wr=wref(:,:,k);
wr=ts_multishift(wr,shifts);
wr=min(wr,[],2);
w(:,:,k)=wr;
end;
else
for k=1:ox
wr=wref(:,:,k);
wr=nt_multishift(wr,shifts);
wr=min(wr,[],2);
wr=min(wr,wx(1:size(wr,1),:,k));
w(:,:,k)=wr;
end
end
wx=w;
wref=zeros(mref,1,oref);
wref(idx,:,:)=w;
% remove weighted means
x0=x;
x=nt_demean(x,wx);
mn1=x-x0;
ref=nt_demean(ref,wref);
% equalize power of ref chans, then equalize power of ref PCs
ref=nt_normcol(ref,wref);
ref=nt_pca(ref,0,[],10^-6);
ref=nt_normcol(ref,wref);
% covariances and cross covariance with time-shifted refs
[cref,twcref]=nt_cov(ref,shifts,wref);
[cxref,twcxref]=nt_xcov(x,ref,shifts,wx);
% regression matrix of x on time-shifted refs
r=nt_regcov(cxref/twcxref,cref/twcref,keep,thresh);
%r=r*0.765;
% TSPCA: clean x by removing regression on time-shifted refs
y=zeros(mx,nx,ox);
for k=1:ox
z=nt_multishift(ref(:,:,k),shifts)*r;
%plot([x(1:size(z,1),1,k), z(:,1)]); pause
y(:,:,k)=x(1:size(z,1),:,k)-z;
end
y0=y;
y=nt_demean(y,wx); % multishift(ref) is not necessarily 0 mean
mn2=y-y0;
%idx=1+offset1:n0-offset2;
idx=1+offset1:size(y,1)+offset1;
mn=mn1+mn2;
w=wref;
%y=vecadd(y,mn);