36
36
% - h_stimulus: height of stimulus images in pixels
37
37
%
38
38
% optional inputs are
39
- % - hrf : either a column vector containing a single hemodynamic
39
+ % - hrf : either a column vector containing a single hemodynamic
40
40
% response used for every voxel;
41
41
% or a matrix with a unique hemodynamic response along
42
42
% its columns for each voxel.
43
- % By default the canonical two-gamma hemodynamic response
43
+ % By default the canonical two-gamma hemodynamic response
44
44
% function is generated internally based on the scan parameters.
45
45
%
46
46
% this class has the following functions
54
54
% - pRF.create_timecourses();
55
55
% - results = pRF.mapping(data);
56
56
%
57
- % use help pRF.function to get more detailed help on any specific function
57
+ % use help pRF.function to get more detailed help on any specific function
58
58
% (e.g. help pRF.mapping)
59
59
%
60
60
% typical workflow:
135
135
136
136
function hrf = get_hrf(self )
137
137
% returns the hemodynamic response used by the class.
138
- % If a single hrf is used for every voxel, this function
138
+ % If a single hrf is used for every voxel, this function
139
139
% returns a column vector.
140
- % If a unique hrf is used for each voxel, this function returns
140
+ % If a unique hrf is used for each voxel, this function returns
141
141
% a matrix with columns corresponding to time and the remaining
142
142
% dimensions reflecting the spatial dimensions of the data.
143
143
if size(self .hrf ,2 )>1
149
149
end
150
150
151
151
function stimulus = get_stimulus(self )
152
- % returns the stimulus used by the class as a 3D matrix of
152
+ % returns the stimulus used by the class as a 3D matrix of
153
153
% dimensions height-by-width-by-time.
154
154
stimulus = reshape(self .stimulus ,self .h_stimulus ,...
155
155
self .w_stimulus ,self .n_samples );
156
156
end
157
157
158
158
function tc = get_timecourses(self )
159
- % returns the timecourses predicted based on the effective
160
- % stimulus and each combination of pRF model parameters as a
159
+ % returns the timecourses predicted based on the effective
160
+ % stimulus and each combination of pRF model parameters as a
161
161
% time-by-combinations matrix.
162
- % Note that the predicted timecourses have not been convolved
162
+ % Note that the predicted timecourses have not been convolved
163
163
% with a hemodynamic response.
164
164
tc = ifft(self .tc_fft );
165
165
end
@@ -173,14 +173,14 @@ function set_hrf(self,hrf)
173
173
self.hrf = reshape(hrf ,self .l_hrf ,self .n_total );
174
174
else
175
175
self.hrf = hrf ;
176
- end
176
+ end
177
177
end
178
178
179
179
function set_stimulus(self ,stimulus )
180
180
% provide a stimulus matrix.
181
- % This is useful if the .png files have already been imported
181
+ % This is useful if the .png files have already been imported
182
182
% and stored in matrix form.
183
- % Note that the provided stimulus matrix can be either 3D
183
+ % Note that the provided stimulus matrix can be either 3D
184
184
% (height-by-width-by-time) or 2D (height*width-by-time).
185
185
if ndims(stimulus )==3
186
186
self.stimulus = reshape(stimulus ,...
@@ -192,9 +192,9 @@ function set_stimulus(self,stimulus)
192
192
end
193
193
194
194
function import_stimulus(self )
195
- % imports the series of .png files constituting the stimulation
195
+ % imports the series of .png files constituting the stimulation
196
196
% protocol of the pRF experiment.
197
- % This series is stored internally in matrix format
197
+ % This series is stored internally in matrix format
198
198
% (height-by-width-by-time).
199
199
% The stimulus is required to generate timecourses.
200
200
@@ -232,9 +232,9 @@ function import_stimulus(self)
232
232
end
233
233
234
234
function create_timecourses(self ,varargin )
235
- % creates predicted timecourses based on the effective stimulus
236
- % and a range of isotropic receptive fields.
237
- % Isotropic receptive fields are generated for a grid of
235
+ % creates predicted timecourses based on the effective stimulus
236
+ % and a range of isotropic receptive fields.
237
+ % Isotropic receptive fields are generated for a grid of
238
238
% location (x,y) and size parameters.
239
239
%
240
240
% optional inputs are
@@ -276,8 +276,8 @@ function create_timecourses(self,varargin)
276
276
mod(floor(i /(n_slope )),n_xy )+1 ,...
277
277
mod(i ,n_slope )+1 ];
278
278
279
- n_lower = ceil(n_xy / 2 );
280
- n_upper = floor(n_xy / 2 );
279
+ n_lower = ceil(n_xy / 2 );
280
+ n_upper = floor(n_xy / 2 );
281
281
self.ecc = exp([linspace(log(max_r ),log(.1 ),n_lower ),...
282
282
linspace(log(.1 ),log(max_r ),n_upper )]);
283
283
self.pa = linspace(0 ,(n_xy - 1 )/n_xy * 2 * pi ,n_xy );
@@ -292,7 +292,7 @@ function create_timecourses(self,varargin)
292
292
W(j ,: ) = self .gauss(x ,y ,sigma ,X_ ,Y_ )' ;
293
293
waitbar(j / self .n_points *.9 ,wb );
294
294
end
295
-
295
+
296
296
tc = W * self .stimulus ;
297
297
waitbar(1 ,wb );
298
298
self.tc_fft = fft(tc ' );
@@ -310,7 +310,7 @@ function create_timecourses(self,varargin)
310
310
% - Eccentricity
311
311
% - Polar_Angle
312
312
%
313
- % the dimension of each field corresponds to the dimensions
313
+ % the dimension of each field corresponds to the dimensions
314
314
% of the data.
315
315
%
316
316
% required inputs are
@@ -327,75 +327,88 @@ function create_timecourses(self,varargin)
327
327
p = inputParser ;
328
328
addRequired(p ,' data' ,@isnumeric );
329
329
addOptional(p ,' threshold' ,100 );
330
+ addOptional(p ,' mask' ,[]);
330
331
p .parse(data ,varargin{: });
331
332
332
333
data = p .Results .data ;
333
334
threshold = p .Results .threshold ;
335
+ mask = p .Results .mask ;
334
336
335
337
data = reshape(data(1 : self .n_samples ,: ,: ,: ),...
336
338
self .n_samples ,self .n_total );
337
339
mean_signal = mean(data );
338
340
data = zscore(data );
339
- mag_d = sqrt(sum(data .^ 2 ));
341
+
342
+ if isempty(mask )
343
+ mask = mean_signal >= threshold ;
344
+ end
345
+ mask = mask(: );
346
+ voxel_index = find(mask );
347
+ n_voxels = numel(voxel_index );
348
+
349
+ mag_d = sqrt(sum(data(: ,mask ).^2 ));
340
350
341
351
results.corr_fit = zeros(self .n_total ,1 );
342
352
results.mu_x = zeros(self .n_total ,1 );
343
353
results.mu_y = zeros(self .n_total ,1 );
344
354
results.sigma = zeros(self .n_total ,1 );
345
355
356
+
346
357
if size(self .hrf ,2 )==1
347
358
hrf_fft = fft(repmat([self .hrf ;...
348
359
zeros(self .n_samples - self .l_hrf ,1 )],...
349
360
[1 ,self .n_points ]));
350
361
tc = zscore(ifft(self .tc_fft .* hrf_fft ))' ;
351
362
mag_tc = sqrt(sum(tc .^ 2 ,2 ));
352
- for v= 1 : self .n_total
353
- if mean_signal(v )>threshold
354
- CS = (tc * data(: ,v ))./...
355
- (mag_tc * mag_d(v ));
356
- id = isinf(CS ) | isnan(CS );
357
- CS(id ) = 0 ;
358
- [results .corr_fit(v ),j ] = max(CS );
359
- results .mu_x(v ) = cos(self .pa(self .idx(j ,1 ))) * ...
360
- self .ecc(self .idx(j ,2 ));
361
- results .mu_y(v ) = sin(self .pa(self .idx(j ,1 ))) * ...
362
- self .ecc(self .idx(j ,2 ));
363
- results .sigma(v ) = self .ecc(self .idx(j ,2 )) * ...
364
- self .slope(self .idx(j ,3 ));
365
- end
366
- waitbar(v / self .n_total ,wb )
363
+ for m= 1 : n_voxels
364
+ v = voxel_index(m );
365
+
366
+ CS = (tc * data(: ,v ))./...
367
+ (mag_tc * mag_d(m ));
368
+ id = isinf(CS ) | isnan(CS );
369
+ CS(id ) = 0 ;
370
+ [results .corr_fit(v ),j ] = max(CS );
371
+ results .mu_x(v ) = cos(self .pa(self .idx(j ,1 ))) * ...
372
+ self .ecc(self .idx(j ,2 ));
373
+ results .mu_y(v ) = sin(self .pa(self .idx(j ,1 ))) * ...
374
+ self .ecc(self .idx(j ,2 ));
375
+ results .sigma(v ) = self .ecc(self .idx(j ,2 )) * ...
376
+ self .slope(self .idx(j ,3 ));
377
+
378
+ waitbar(v / n_voxels ,wb )
367
379
end
368
380
else
369
381
hrf_fft_all = fft([self .hrf ;...
370
- zeros(self .n_samples - self .l_hrf ,self .n_total )]);
371
- for v= 1 : self .n_total
372
- if mean_signal(v )>threshold
373
- hrf_fft = repmat(hrf_fft_all(: ,v ),...
374
- [1 ,self .n_points ]);
375
- tc = zscore(ifft(self .tc_fft .* hrf_fft ))' ;
376
- mag_tc = sqrt(sum(tc .^ 2 ,2 ));
377
-
378
- CS = (tc * data(: ,v ))./...
379
- (mag_tc * mag_d(v ));
380
- id = isinf(CS ) | isnan(CS );
381
- CS(id ) = 0 ;
382
- [results .corr_fit(v ),j ] = max(CS );
383
- results .mu_x(v ) = cos(self .pa(self .idx(j ,1 ))) * ...
384
- self .ecc(self .idx(j ,2 ));
385
- results .mu_y(v ) = sin(self .pa(self .idx(j ,1 ))) * ...
386
- self .ecc(self .idx(j ,2 ));
387
- results .sigma(v ) = self .ecc(self .idx(j ,2 )) * ...
388
- self .slope(self .idx(j ,3 ));
389
- end
390
- waitbar(v / self .n_total ,wb )
382
+ zeros(self .n_samples - self .l_hrf ,self .n_total )]);
383
+ for m= 1 : n_voxels
384
+ v = voxel_index(m );
385
+
386
+ hrf_fft = repmat(hrf_fft_all(: ,v ),...
387
+ [1 ,self .n_points ]);
388
+ tc = zscore(ifft(self .tc_fft .* hrf_fft ))' ;
389
+ mag_tc = sqrt(sum(tc .^ 2 ,2 ));
390
+
391
+ CS = (tc * data(: ,v ))./...
392
+ (mag_tc * mag_d(m ));
393
+ id = isinf(CS ) | isnan(CS );
394
+ CS(id ) = 0 ;
395
+ [results .corr_fit(v ),j ] = max(CS );
396
+ results .mu_x(v ) = cos(self .pa(self .idx(j ,1 ))) * ...
397
+ self .ecc(self .idx(j ,2 ));
398
+ results .mu_y(v ) = sin(self .pa(self .idx(j ,1 ))) * ...
399
+ self .ecc(self .idx(j ,2 ));
400
+ results .sigma(v ) = self .ecc(self .idx(j ,2 )) * ...
401
+ self .slope(self .idx(j ,3 ));
402
+
403
+ waitbar(v / n_voxels ,wb )
391
404
end
392
405
end
393
406
results.corr_fit = reshape(results .corr_fit ,self .n_rows ,self .n_cols ,self .n_slices );
394
407
results.mu_x = reshape(results .mu_x ,self .n_rows ,self .n_cols ,self .n_slices );
395
408
results.mu_y = reshape(results .mu_y ,self .n_rows ,self .n_cols ,self .n_slices );
396
409
results.sigma = reshape(results .sigma ,self .n_rows ,self .n_cols ,self .n_slices );
397
- results.Eccentricity = abs(results .mu_x + results .mu_y * 1i );
398
- results.Polar_Angle = angle(results .mu_x + results .mu_y * 1i );
410
+ results.eccentricity = abs(results .mu_x + results .mu_y * 1i );
411
+ results.polar_angle = angle(results .mu_x + results .mu_y * 1i );
399
412
close(wb )
400
413
fprintf(' done\n ' );
401
414
end
0 commit comments