Skip to content

Commit 49325fb

Browse files
committed
added masking | moved first regression outside of loop | improved variable naming
1 parent d42129e commit 49325fb

File tree

1 file changed

+68
-41
lines changed

1 file changed

+68
-41
lines changed

code/matlab/PEA.m

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
%
2626
% Phase-encoding analysis tool.
2727
%
28-
% pea = PEA(params) creates an instance of the PEA class.
29-
% params is a structure with 7 required fields
28+
% pea = PEA(parameters) creates an instance of the PEA class.
29+
% parameters is a structure with 7 required fields
3030
% - f_sampling: sampling frequency (1/TR)
3131
% - f_stim : stimulation frequency
3232
% - n_samples : number of samples (volumes)
@@ -46,7 +46,7 @@
4646
% function (e.g. help PEA.fitting)
4747
%
4848
% typical workflow:
49-
% 1. pea = PEA(params);
49+
% 1. pea = PEA(parameters);
5050
% 2. pea.set_delay(delay);
5151
% 3. pea.set_direction(direction);
5252
% 4. results = pea.fitting(data);
@@ -74,17 +74,17 @@
7474

7575
methods (Access = public)
7676

77-
function self = PEA(params)
77+
function self = PEA(parameters)
7878
% constructor
7979
self.is = 'PEA tool';
8080

81-
self.f_sampling = params.f_sampling;
82-
self.f_stim = params.f_stim;
81+
self.f_sampling = parameters.f_sampling;
82+
self.f_stim = parameters.f_stim;
8383
self.p_sampling = 1/self.f_sampling;
84-
self.n_samples = params.n_samples;
85-
self.n_rows = params.n_rows;
86-
self.n_cols = params.n_cols;
87-
self.n_slices = params.n_slices;
84+
self.n_samples = parameters.n_samples;
85+
self.n_rows = parameters.n_rows;
86+
self.n_cols = parameters.n_cols;
87+
self.n_slices = parameters.n_slices;
8888
self.n_total = self.n_rows*self.n_cols*self.n_slices;
8989
self.t = (0:self.p_sampling:self.p_sampling*self.n_samples-1);
9090
self.delay = 0;
@@ -132,7 +132,7 @@ function set_direction(self,direction)
132132
end
133133
end
134134

135-
function results = fitting(self,data)
135+
function results = fitting(self,data,varargin)
136136
% identifies phase and amplitude at stimulation frequency for
137137
% each voxel and returns a structure with the following fields
138138
% - Phase
@@ -146,50 +146,77 @@ function set_direction(self,direction)
146146
% required inputs are
147147
% - data : a matrix of empirically observed BOLD timecourses
148148
% whose columns correspond to time (volumes).
149+
%
150+
% optional inputs are
151+
% - threshold: minimum voxel intensity (default = 100.0)
152+
% - mask : binary mask for selecting voxels
149153

150154
text = 'performing phase encoding analysis...';
151155
fprintf('%s\n',text)
152156
wb = waitbar(0,text,'Name',self.is);
153157

158+
p = inputParser;
159+
addRequired(p,'data',@isnumeric);
160+
addOptional(p,'threshold',100);
161+
addOptional(p,'mask',[]);
162+
p.parse(data,varargin{:});
163+
164+
data = single(p.Results.data);
165+
threshold = p.Results.threshold;
166+
mask = p.Results.mask;
167+
154168
F = exp(self.direction*2i*pi*self.f_stim*(self.t-self.delay));
155169
X = zscore([real(F)',imag(F)']);
156-
XX = (X'*X)\X';
157-
data = zscore(reshape(single(data(1:self.n_samples,:,:,:)),...
158-
self.n_samples,self.n_total));
159-
std_signal = std(data);
170+
171+
data = reshape(single(data(1:self.n_samples,:,:,:)),...
172+
self.n_samples,self.n_total);
173+
174+
mean_signal = mean(data);
175+
176+
if isempty(mask)
177+
mask = mean_signal>=threshold;
178+
end
179+
mask = mask(:);
180+
voxel_index = find(mask);
181+
n_voxels = numel(voxel_index);
182+
183+
data = zscore(data(:, mask));
184+
185+
beta = (X' * X) \ X' * data;
186+
187+
Y_ = X * beta;
188+
residuals = data - Y_;
189+
160190
results.Phase = zeros(self.n_total,1);
161191
results.Amplitude = zeros(self.n_total,1);
162192
results.F_stat = zeros(self.n_total,1);
163193
results.P_value = ones(self.n_total,1);
164194

165195
df1 = 1;
166196
df2 = self.n_samples-2;
167-
for v=1:self.n_total
168-
if std_signal>0
169-
b = XX * data(:,v);
170-
y = X*b;
171-
172-
% estimate and correct for autocorrelation
173-
r = data(:,v) - y;
174-
T = [[0;r(1:end-1)],[zeros(2,1);r(1:end-2)]];
175-
W = [1; -((T'* T) \ T' * r)];
176-
Xc(:,1) = [X(:,1),[0;X(1:end-1,1)],[zeros(2,1);X(1:end-2,1)]] * W;
177-
Xc(:,2) = [X(:,2),[0;X(1:end-1,2)],[zeros(2,1);X(1:end-2,2)]] * W;
178-
Dc = [data(:,v),[0;data(1:end-1,v)],[zeros(2,1);data(1:end-2,v)]] * W;
179-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
180-
181-
b = (Xc' * Xc) \ Xc' * Dc;
182-
y = Xc * b;
183-
mu = mean(DC);
184-
MSM = (y-mu)'*(y-mu)/df1;
185-
MSE = (y-Dc)'*(y-Dc)/df2;
186-
187-
results.Phase(v) = angle(b(1)+b(2)*1i);
188-
results.Amplitude(v) = abs(b(1)+b(2)*1i);
189-
results.F_stat(v) = MSM/MSE;
190-
results.P_value(v) = max(1-fcdf(MSM/MSE,df1,df2),1e-20);
191-
end
192-
waitbar(v/self.n_total,wb)
197+
for m=1:n_voxels
198+
v = voxel_index(m);
199+
200+
% estimate and correct for autocorrelation
201+
T = [[0;residuals(1:end-1,m)],[zeros(2,1); residuals(1:end-2,m)]];
202+
W = [1; -((T'* T) \ T' * residuals(:,m))];
203+
Xc(:,1) = [X(:,1), [0;X(1:end-1,1)], [zeros(2,1);X(1:end-2,1)]] * W;
204+
Xc(:,2) = [X(:,2), [0;X(1:end-1,2)], [zeros(2,1);X(1:end-2,2)]] * W;
205+
Dc = [data(:,m), [0;data(1:end-1,m)], [zeros(2,1);data(1:end-2,m)]] * W;
206+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
207+
208+
b = (Xc' * Xc) \ Xc' * Dc;
209+
y = Xc * b;
210+
mu = mean(DC);
211+
MSM = (y-mu)'*(y-mu)/df1;
212+
MSE = (y-Dc)'*(y-Dc)/df2;
213+
214+
results.Phase(v) = angle(b(1)+b(2)*1i);
215+
results.Amplitude(v) = abs(b(1)+b(2)*1i);
216+
results.F_stat(v) = MSM/MSE;
217+
results.P_value(v) = max(1-fcdf(MSM/MSE,df1,df2),1e-20);
218+
219+
waitbar(m/n_voxels,wb)
193220
end
194221

195222
results.Phase = reshape(results.Phase,...

0 commit comments

Comments
 (0)