-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathanalyzePRF.m
829 lines (728 loc) · 35.3 KB
/
analyzePRF.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
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
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
function results = analyzePRF(stimulus,data,tr,options)
% function results = analyzePRF(stimulus,data,tr,options)
%
% <stimulus> provides the apertures as a cell vector of R x C x time.
% values should be in [0,1]. the number of time points can differ across runs.
% consider passing <stimulus> in double format to ensure that computation
% occurs with optimal precision (less chance of local minima)!
% <data> provides the data as a cell vector of voxels x time. can also be
% X x Y x Z x time. the number of time points should match the number of
% time points in <stimulus>.
% <tr> is the TR in seconds (e.g. 1.5)
% <options> (optional) is a struct with the following fields:
% <vxs> (optional) is a vector of voxel indices to analyze. (we automatically
% sort the voxel indices and ensure uniqueness.) default is 1:V where
% V is total number of voxels. to reduce computational time, you may want
% to create a binary brain mask, perform find() on it, and use the result as <vxs>.
% <wantglmdenoise> (optional) is whether to use GLMdenoise to determine
% nuisance regressors to add into the PRF model. this is fairly experimental
% and not recommended for general use at this time. note that in order to use
% this feature, there must be at least two runs (and conditions must repeat
% across runs). we automatically determine the GLM design matrix based on
% the contents of <stimulus>. special case is to pass in the noise regressors
% directly (e.g. from a previous call). default: 0.
% <hrf> (optional) is a column vector with the hemodynamic response function (HRF)
% to use in the model. the first value of <hrf> should be coincident with the onset
% of the stimulus, and the HRF should indicate the timecourse of the response to
% a stimulus that lasts for one TR. default is to use a canonical HRF (calculated
% using getcanonicalhrf(tr,tr)').
% <maxpolydeg> (optional) is a non-negative integer indicating the maximum polynomial
% degree to use for drift terms. can be a vector whose length matches the number
% of runs in <data>. default is to use round(L/2) where L is the number of minutes
% in the duration of a given run. special case is NaN, which means to not include
% any drift terms at all (not even a constant term).
% <seedmode> (NOW REQUIRED) is a vector consisting of one or more of the
% following values (we automatically sort and ensure uniqueness):
% 0 means use generic large PRF seed
% 1 means use generic small PRF seed
% 2 means use best seed based on super-grid
% a special case is to pass <seedmode> as -2. this causes the
% best seed based on the super-grid to be returned as the final estimate, thereby
% bypassing the computationally expensive optimization procedure. further notes
% on this case are given below. another special case is passing <seedmode> as
% {S}, in which case we directly use the seeds contained in S, a matrix of
% dimensions one-or-more-seeds x 5 parameters as the PRF seed(s).
% NOTE: <seedmode> used to be optional (and defaulted to [0 1 2]). The new
% behavior is to require the user to specify this. A suggestion is
% to set <seedmode> to 2. This is a fairly robust strategy (but has
% an initial overhead for the seed evaluation. An alternative is to
% set <seedmode> to 0, which avoids the overhead, but is more likely
% to get stuck at a local minimum close to the initial seed.
% <modelmode> (optional) is
% 1 means a two-stage approach (optimize all parameters excluding exponent,
% and then optimize all parameters)
% 2 means a one-stage approach (optimize all parameters)
% 3 means a one-stage approach (optimize all parameters excluding exponent)
% default: 1.
% <xvalmode> (optional) is
% 0 means just fit all the data
% 1 means two-fold cross-validation (first half of runs; second half of runs)
% 2 means two-fold cross-validation (first half of each run; second half of each run)
% default: 0. (note that we round when halving.)
% <numperjob> (optional) is
% [] means to run locally (not on the cluster)
% N where N is a positive integer indicating the number of voxels to analyze in each
% cluster job. this option requires a customized computational setup!
% default: [].
% <maxiter> (optional) is the maximum number of iterations. default: 500.
% <display> (optional) is 'iter' | 'final' | 'off'. default: 'iter'.
% <algorithm> (optional) is passed to MATLAB's optimizer, can be like
% 'levenberg-marquardt', 'trust-region-reflective', etc.
% default: 'levenberg-marquardt'.
% <typicalgain> (optional) is a typical value for the gain in each time-series.
% default: 10. if you are using <seedmode> of either 2 or -2, this involves
% the "super-grid" strategy, and in this case, we ignore <typicalgain> and
% instead do some special calculations to set the gain more appropriately
% (specifically, to 0.75 of the value that would produce the least-squares
% fit for the chosen seed, with a restriction to non-negative values only).
% <exptlowerbound> (optional) is the lower bound for the exponent parameter.
% This should be a positive number. Default: 0.001.
% <wantsparse> (optional) is whether to convert stimulus to sparse (for
% potential major execution speed-ups). Default: 1.
%
% Analyze pRF data and return the results.
%
% The results structure contains the following fields:
% <ang> contains pRF angle estimates. Values range between 0 and 360 degrees.
% 0 corresponds to the right horizontal meridian, 90 corresponds to the upper vertical
% meridian, and so on. In the case where <ecc> is estimated to be exactly equal
% to 0, the corresponding <ang> value is deliberately set to NaN.
% <ecc> contains pRF eccentricity estimates. Values are in pixel units with a lower
% bound of 0 pixels.
% <rfsize> contains pRF size estimates. pRF size is defined as sigma/sqrt(n) where
% sigma is the standard of the 2D Gaussian and n is the exponent of the power-law
% function. Values are in pixel units with a lower bound of 0 pixels.
% <expt> contains pRF exponent estimates.
% <gain> contains pRF gain estimates. Values are in the same units of the data
% and are constrained to be non-negative.
% <R2> contains R^2 values that indicate the goodness-of-fit of the model to the data.
% Values are in percentages and generally range between 0% and 100%. The R^2 values
% are computed after projecting out polynomials from both the data and the model fit.
% (Because of this projection, R^2 values can sometimes drop below 0%.) Note that
% if cross-validation is used (see <xvalmode>), the <R2> is still the _training_
% performance for each iteration.
% <resnorms> and <numiters> contain optimization details (residual norms and
% number of iterations, respectively).
% <meanvol> contains the mean volume, that is, the mean of each voxel's time-series.
% <noisereg> contains a record of the noise regressors used in the model.
% <params> contains a record of the raw parameter estimates that are obtained internally
% in the code. These raw parameters are transformed to a more palatable format for
% the user (as described above).
% <options> contains a record of the options used in the call to analyzePRF.m.
%
% If cross-validation (<xvalmode> ~= 0) is used, the results structure also contains:
% <testperformance> contains R^2 values for the testing performance on each iteration.
% <aggregatedtestperformance> contains R^2 values for testing performance after
% aggregating predictions across iterations.
%
% Details on the pRF model:
% - Before analysis, we zero out any voxel that has a non-finite value or has all zeros
% in at least one of the runs. This prevents weird issues due to missing or bad data.
% - The pRF model that is fit is similar to that described in Dumoulin and Wandell (2008),
% except that a static power-law nonlinearity is added to the model. This new model,
% called the Compressive Spatial Summation (CSS) model, is described in Kay, Winawer,
% Mezer, & Wandell (2013).
% - The model involves computing the dot-product between the stimulus and a 2D isotropic
% Gaussian, raising the result to an exponent, scaling the result by a gain factor,
% and then convolving the result with a hemodynamic response function (HRF). Polynomial
% terms are included (on a run-by-run basis) to model the baseline signal level.
% - The 2D isotropic Gaussian is scaled such that the summation of the values in the
% Gaussian is equal to one. This eases the interpretation of the gain of the model.
% - The exponent parameter in the model is constrained to be non-negative.
% - The gain factor in the model is constrained to be non-negative; this aids the
% interpretation of the model (e.g. helps avoid voxels with negative BOLD responses
% to the stimuli).
% - The workhorse of the analysis is fitnonlinearmodel.m, which is essentially a wrapper
% around routines in the MATLAB Optimization Toolbox. We default to use the
% Levenberg-Marquardt algorithm for optimization (but this can be controlled by
% options.algorithm), minimizing squared error between the model and the data.
% - A two-stage optimization strategy is used whereby all parameters excluding the
% exponent parameter are first optimized (holding the exponent parameter fixed) and
% then all parameters are optimized (including the exponent parameter). This
% strategy helps avoid local minima.
%
% Regarding GLMdenoise:
% - Note this is probably not recommended for use due to the various complexities involved.
% - If the <wantglmdenoise> option is specified, we derive noise regressors using
% GLMdenoise prior to model fitting. This is done by creating a GLM design matrix
% based on the contents of <stimulus> and then using this design matrix in conjunction
% with GLMdenoise to analyze the data. The noise regressors identified by GLMdenoise
% are then used in the fitting of the pRF models (the regressors enter the model
% additively, just like the polynomial regressors).
%
% Regarding seeding issues:
% - To minimize the impact of local minima, the default strategy is to perform full
% optimizations starting from three different initial seeds.
% - The first seed is a generic large pRF that is centered with respect to the stimulus,
% has a pRF size equal to 1/4th of the stimulus extent (thus, +/- 2 pRF sizes matches
% the stimulus extent), and has an exponent of 0.5.
% - The second seed is a generic small pRF that is just like the first seed except has
% a pRF size that is 10 times smaller.
% - The third seed is a "supergrid" seed that is identified by performing a quick grid
% search prior to optimization (similar in spirit to methods described in Dumoulin and
% Wandell, 2008). In this procedure, a list of potential seeds is constructed by
% exploring a range of eccentricities, angles, and exponents. For each potential
% seed, the model prediction is computed, and the seed that produces the closest
% match to the data is identified. Note that the supergrid seed may be different
% for different voxels.
%
% Regarding the "quick" mode:
% - When <seedmode> is -2, optimization is not performed and instead the best seed
% based on the super-grid is returned as the final estimate. If this case is used,
% we automatically enforce that:
% - options.xvalmode is 0
% - options.vxs is []
% - options.numperjob is []
% Also, in terms of outputs:
% - The <gain> output is set to 0.75 of the idealized setting.
% - The <R2> output will contain correlation values (r) that range between -1 and 1.
% These correlation values reflect the correlation between the model prediction and the
% data after projecting out polynomial regressors and the noise regressors (if
% <wantglmdenoise> is specified) from both the model prediction and the data.
% - The <resnorms> and <numiters> outputs will be empty.
%
% Regarding using a linear pRF model:
% - Recent options that have now been implemented will allow easy fitting of
% a linear pRF model (i.e. exponent equal to 1). To do this, you can use
% <modelmode> set to 3, which will cause the exponent to not be optimized.
% And then in conjunction with that, you can set <seedmode> to an explicit
% seed like {[R C SD GAIN 1]} (where the R and C provide the row and column
% index of the seed (in pixel units), SD provides the sigma parameter (in
% pixel units), GAIN is the gain parameter, and 1 is the exponent parameter.
% This is a little clunky but works...
%
% history:
% 2022/06/14 - the case of non-square stimulus preparations was being handled
% incorrectly (e.g. ang and ecc were being computed incorrectly).
% it is now fixed.
% 2022/03/26 - implement new option <wantsparse>. this changes past behavior,
% in a sense, but the differences are tiny.
% 2021/12/05 - tag as version 1.5.
% 2021/12/04 - implement new option <exptlowerbound>. this changes past behavior.
% for very small exponents, strange numerical inaccuracies were
% occurring, causing implausible 'rfsize' outputs. after enforcing
% a reasonable lower bound (e.g. 0.001), these inaccuracies were
% resolved.
% 2021/12/04 - we now force the user to specify <seedmode>. this is so that the
% user understands this is major parameter that will affect speed
% and accuracy.
% 2021/11/22 - tag this as version 1.4.
% 2021/11/22 - For 2 and -2 "supergrid" cases for <seedmode>, we now always
% use the <typicalgain>==NaN setting, which means to use an
% appropriate initial value for the gain. this should improve
% overall result quality. Note that this changes past behavior!
% 2021/11/11 - tag this as version 1.3.1.
% 2021/11/11 - implemented a speed-up.
% 2021/11/11 - tag this as version 1.3.
% 2021/11/11 - update exampledataset.mat to be in double format.
% also, we now issue a warning if the user provides single-format data!
% 2021/03/22 - Several changes:
% (1) When eccentricity is estimated to be exactly 0, the
% corresponding angle values are now deliberately
% set to NaN. Previous behavior resulted in angle values being
% returned as 0 (since atan2(0,0) results in 0).
% (2) Add support for <maxpolydeg> to be NaN which allows
% for no drift terms to be included (not even a constant).
% (3) Add support for allowing user input <options.algorithm>.
% (4) Add support for <options.seedmode> to be the {S} case.
% This allows the user to directly specify the initial seed(s).
% (5) Add support for <modelmode> to be 3. This makes it such that
% the exponent is fixed and not optimized. This could be useful
% to fit stricly linear pRF models (see documentation above).
% 2020/06/18 - add <modelmode> input option. Fix opt -> options typo bug.
% 2020/03/05 - BUG FIX. Previously, when <xvalmode> ~= 0, the <R2> values that were output
% were training performance values (even though we implied that they were
% testing performance values). Now, we preserve that behavior, but now also
% output new fields <testperformance> and <aggregatedtestperformance>,
% which indicate the cross-validated values.
% 2019/08/23 - Major change: the <stimulus> variable is now no longer forced to become
% single format. This means the user controls whether computations are done
% in double or single format. Please note that behavior (including finicky
% local minima issues) can be highly dependent on the format. Parameter estimates
% may be substantially more accurate (and may take substantially more computational
% time / iterations to converge) if computations are performed in double format.
% 2015/02/07 - version 1.2
% 2015/02/07 - make analyzePRFcomputesupergridseeds.m less memory intensive
% 2014/11/10 - implement <wantglmdenoise> for the <seedmode> -2 case.
% also, now the super-grid seed computation now
% takes into account noise regressors (previously, the
% noise regressors were ignored).
% 2014/10/20 - add -2 case for <seedmode>
% 2014/06/17 - version 1.1
% 2014/06/15 - add inputs <hrf> and <maxpolydeg>.
% 2014/06/10 - version 1.0
% 2014/04/27 - gain seed is now set to 0; add gain to the output
% 2014/04/29 - use typicalgain now (default 10). allow display input.
% internal notes:
% - for cluster mode, need to make sure fitnonlinearmodel is compiled (compilemcc.m)
% - to check whether local minima are a problem, can look at results.resnorms
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% REPORT
fprintf('*** analyzePRF: started at %s. ***\n',datestr(now));
stime = clock; % start time
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% INTERNAL CONSTANTS
% define
remotedir = '/scratch/knk/input/';
remotedir2 = '/scratch/knk/output/';
remotelogin = '[email protected]';
remoteuser = 'knk';
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SETUP AND PREPARATION
% massage cell inputs
if ~iscell(stimulus)
stimulus = {stimulus};
end
if ~iscell(data)
data = {data};
end
% check single or double
if ~isequal(class(stimulus{1}),'double') || ~isequal(class(data{1}),'double')
warning('either <stimulus> or <data> is not double format. use double format for better accuracy/precision!');
end
% calc
is3d = size(data{1},4) > 1;
if is3d
dimdata = 3;
dimtime = 4;
xyzsize = sizefull(data{1},3);
else
dimdata = 1;
dimtime = 2;
xyzsize = size(data{1},1);
end
numvxs = prod(xyzsize);
% calc
res = sizefull(stimulus{1},2);
resmx = max(res);
numruns = length(data);
% deal with inputs
if ~exist('options','var') || isempty(options)
options = struct();
end
if ~isfield(options,'vxs') || isempty(options.vxs)
options.vxs = 1:numvxs;
end
if ~isfield(options,'wantglmdenoise') || isempty(options.wantglmdenoise)
options.wantglmdenoise = 0;
end
if ~isfield(options,'hrf') || isempty(options.hrf)
options.hrf = [];
end
if ~isfield(options,'maxpolydeg') || isempty(options.maxpolydeg)
options.maxpolydeg = [];
end
if ~isfield(options,'seedmode') || isempty(options.seedmode)
error('<seedmode> is now required to be specified! Suggestion is 2 (see documentation).');
%%options.seedmode = [0 1 2];
end
if ~isfield(options,'modelmode') || isempty(options.modelmode)
options.modelmode = 1;
end
if ~isfield(options,'xvalmode') || isempty(options.xvalmode)
options.xvalmode = 0;
end
if ~isfield(options,'numperjob') || isempty(options.numperjob)
options.numperjob = [];
end
if ~isfield(options,'maxiter') || isempty(options.maxiter)
options.maxiter = 500;
end
if ~isfield(options,'display') || isempty(options.display)
options.display = 'iter';
end
if ~isfield(options,'algorithm') || isempty(options.algorithm)
options.algorithm = 'levenberg-marquardt';
end
if ~isfield(options,'typicalgain') || isempty(options.typicalgain)
options.typicalgain = 10;
end
if ~isfield(options,'exptlowerbound') || isempty(options.exptlowerbound)
options.exptlowerbound = 0.001;
end
if ~isfield(options,'wantsparse') || isempty(options.wantsparse)
options.wantsparse = 1;
end
% massage
wantquick = isequal(options.seedmode,-2);
if ~iscell(options.seedmode)
options.seedmode = union(options.seedmode(:),[]);
end
% massage more
if wantquick
options.xvalmode = 0;
options.vxs = 1:numvxs;
options.numperjob = [];
end
% calc
usecluster = ~isempty(options.numperjob);
% prepare stimuli
for p=1:length(stimulus)
stimulus{p} = squish(stimulus{p},2)'; % frames x pixels
stimulus{p} = [stimulus{p} p*ones(size(stimulus{p},1),1)]; % add a dummy column to indicate run breaks
%% REMOVED ON AUG 23 2019! THIS CHANGES PAST BEHAVIOR.
%% stimulus{p} = single(stimulus{p}); % make single to save memory
if options.wantsparse
if isequal(class(stimulus{p}),'double')
fprintf('converting stimulus (run %d) to sparse.\n',p);
stimulus{p} = sparse(stimulus{p});
else
fprintf('stimulus (run %d) is not double, so not converting to sparse.\n');
end
end
end
% deal with data badness (set bad voxels to be always all 0)
bad = cellfun(@(x) any(~isfinite(x),dimtime) | all(x==0,dimtime),data,'UniformOutput',0); % if non-finite or all 0
bad = any(cat(dimtime,bad{:}),dimtime); % badness in ANY run
for p=1:numruns
data{p}(repmat(bad,[ones(1,dimdata) size(data{p},dimtime)])) = 0;
end
% calc mean volume
meanvol = mean(catcell(dimtime,data),dimtime);
% what HRF should we use?
if isempty(options.hrf)
options.hrf = getcanonicalhrf(tr,tr)';
end
numinhrf = length(options.hrf);
% what polynomials should we use?
if isempty(options.maxpolydeg)
options.maxpolydeg = cellfun(@(x) round(size(x,dimtime)*tr/60/2),data);
end
if isscalar(options.maxpolydeg)
options.maxpolydeg = repmat(options.maxpolydeg,[1 numruns]);
end
fprintf('using the following maximum polynomial degrees: %s\n',mat2str(options.maxpolydeg));
% initialize cluster stuff
if usecluster
localfilestodelete = {};
remotefilestodelete = {};
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% FIGURE OUT NOISE REGRESSORS
if isequal(options.wantglmdenoise,1)
noisereg = analyzePRFcomputeGLMdenoiseregressors(stimulus,data,tr);
elseif isequal(options.wantglmdenoise,0)
noisereg = [];
else
noisereg = options.wantglmdenoise;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE MODEL
% pre-compute some cache
[d,xx,yy] = makegaussian2d(resmx,2,2,2,2);
% define the model (parameters are R C S G N)
modelfun = @(pp,dd) conv2run(posrect(pp(4)) * (dd*[vflatten(placematrix(zeros(res),makegaussian2d(resmx,pp(1),pp(2),abs(pp(3)),abs(pp(3)),xx,yy,0,0) / (2*pi*abs(pp(3))^2))); 0]) .^ posrect(pp(5),options.exptlowerbound),options.hrf,dd(:,prod(res)+1));
switch options.modelmode % NOTE: these bounds may not have any effect if you use levenberg-marquardt optimization!
case 1
model = {{[] [1-resmx+1 1-resmx+1 0 0 NaN;
2*resmx-1 2*resmx-1 Inf Inf Inf] modelfun} ...
{@(ss)ss [1-resmx+1 1-resmx+1 0 0 options.exptlowerbound;
2*resmx-1 2*resmx-1 Inf Inf Inf] @(ss)modelfun}};
case 2
model = {{[] [1-resmx+1 1-resmx+1 0 0 options.exptlowerbound;
2*resmx-1 2*resmx-1 Inf Inf Inf] modelfun}};
case 3
model = {{[] [1-resmx+1 1-resmx+1 0 0 NaN;
2*resmx-1 2*resmx-1 Inf Inf Inf] modelfun}};
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE SEEDS
% init
seeds = [];
if iscell(options.seedmode)
seeds = [seeds;
options.seedmode{1}];
else
% generic large seed
if ismember(0,options.seedmode)
seeds = [seeds;
(1+resmx)/2 (1+resmx)/2 resmx/4*sqrt(0.5) options.typicalgain 0.5];
end
% generic small seed
if ismember(1,options.seedmode)
seeds = [seeds;
(1+resmx)/2 (1+resmx)/2 resmx/4*sqrt(0.5)/10 options.typicalgain 0.5];
end
% super-grid seed
if any(ismember([2 -2],options.seedmode))
[supergridseeds,rvalues] = analyzePRFcomputesupergridseeds(res,stimulus,data,modelfun, ...
options.maxpolydeg,dimdata,dimtime, ...
NaN,noisereg);
end
end
% make a function that individualizes the seeds
if exist('supergridseeds','var')
seedfun = @(vx) [[seeds];
[subscript(squish(supergridseeds,dimdata),{vx ':'})]];
else
seedfun = @(vx) [seeds];
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PERFORM OPTIMIZATION
% if this is true, we can bypass all of the optimization stuff!
if wantquick
else
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE RESAMPLING STUFF
% define wantresampleruns and resampling
switch options.xvalmode
case 0
wantresampleruns = [];
resampling = 0;
case 1
wantresampleruns = 1;
half1 = copymatrix(zeros(1,length(data)),1:round(length(data)/2),1);
half2 = ~half1;
resampling = [(1)*half1 + (-1)*half2;
(-1)*half1 + (1)*half2];
case 2
wantresampleruns = 0;
resampling = [];
for p=1:length(data)
half1 = copymatrix(zeros(1,size(data{p},2)),1:round(size(data{p},2)/2),1);
half2 = ~half1;
resampling = cat(2,resampling,[(1)*half1 + (-1)*half2;
(-1)*half1 + (1)*half2]);
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE STIMULUS AND DATA
%%%%% CLUSTER CASE
if usecluster
% save stimulus and transport to the remote server
while 1
filename0 = sprintf('stim%s.mat',randomword(5)); % file name
localfile0 = [tempdir '/' filename0]; % local path to file
remotefile0 = [remotedir '/' filename0]; % remote path to file
% redo if file already exists locally or remotely
if exist(localfile0) || 0==unix(sprintf('ssh %s ls %s',remotelogin,remotefile0))
continue;
end
% save file and transport it
save(localfile0,'stimulus');
assert(0==unix(sprintf('rsync -av %s %s:"%s/"',localfile0,remotelogin,remotedir)));
% record
localfilestodelete{end+1} = localfile0;
remotefilestodelete{end+1} = remotefile0;
% stop
break;
end
clear stimulus; % don't let it bleed through anywhere!
% define stimulus
stimulus = @() loadmulti(remotefile0,'stimulus');
% save data and transport to the remote server
while 1
filename0 = sprintf('data%s',randomword(5)); % directory name that will contain 001.bin, etc.
localfile0 = [tempdir '/' filename0]; % local path to dir
remotefile0 = [remotedir '/' filename0]; % remote path to dir
% redo if dir already exists locally or remotely
if exist(localfile0) || 0==unix(sprintf('ssh %s ls %s',remotelogin,remotefile0))
continue;
end
% save files and transport them
assert(mkdir(localfile0));
for p=1:numruns
savebinary([localfile0 sprintf('/%03d.bin',p)],'single',squish(data{p},dimdata)'); % notice squish
end
assert(0==unix(sprintf('rsync -av %s %s:"%s/"',localfile0,remotelogin,remotedir)));
% record
localfilestodelete{end+1} = localfile0;
remotefilestodelete{end+1} = remotefile0;
% stop
break;
end
clear data;
% define data
binfiles = cellfun(@(x) [remotefile0 sprintf('/%03d.bin',x)],num2cell(1:numruns),'UniformOutput',0);
data = @(vxs) cellfun(@(x) double(loadbinary(x,'single',[0 numvxs],-vxs)),binfiles,'UniformOutput',0);
% prepare the output directory name
while 1
filename0 = sprintf('prfresults%s',randomword(5));
localfile0 = [tempdir '/' filename0];
remotefile0 = [remotedir2 '/' filename0];
if exist(localfile0) || 0==unix(sprintf('ssh %s ls %s',remotelogin,remotefile0))
continue;
end
localfilestodelete{end+1} = localfile0;
localfilestodelete{end+1} = [localfile0 '.mat']; % after consolidation
remotefilestodelete{end+1} = remotefile0;
break;
end
outputdirlocal = localfile0;
outputdirremote = remotefile0;
outputdir = outputdirremote;
%%%%% NON-CLUSTER CASE
else
stimulus = {stimulus};
data = @(vxs) cellfun(@(x) subscript(squish(x,dimdata),{vxs ':'})',data,'UniformOutput',0);
outputdir = [];
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE OPTIONS
% last-minute prep
if iscell(noisereg)
noiseregINPUT = {noisereg};
else
noiseregINPUT = noisereg;
end
% construct the options struct
opt = struct( ...
'outputdir',outputdir, ...
'stimulus',stimulus, ...
'data',data, ...
'vxs',options.vxs, ...
'model',{model}, ...
'seed',seedfun, ...
'optimoptions',{{'Display' options.display 'Algorithm' options.algorithm 'MaxIter' options.maxiter}}, ...
'wantresampleruns',wantresampleruns, ...
'resampling',resampling, ...
'metric',@calccod, ...
'maxpolydeg',options.maxpolydeg, ...
'wantremovepoly',1, ...
'extraregressors',noiseregINPUT, ...
'wantremoveextra',0, ...
'dontsave',{{'modelfit' 'opt' 'vxsfull' 'modelpred' 'testdata'}}); % 'resnorms' 'numiters'
% 'outputfcn',@(a,b,c,d) pause2(.1) | outputfcnsanitycheck(a,b,c,1e-6,10) | outputfcnplot(a,b,c,1,d), ...
%'outputfcn',@(a,b,c,d) pause2(.1) | outputfcnsanitycheck(a,b,c,1e-6,10) | outputfcnplot(a,b,c,1,d));
% % debugging:
% chpcstimfile = '/stone/ext1/knk/HCPretinotopy/conimagesB.mat';
% chpcdatadir2 = outputdir2; % go back
% opt.outputdir='~/temp1';
% profile on;
% results = fitnonlinearmodel(opt,100,100);
% results = fitnonlinearmodel(opt,1,715233);
% profsave(profile('info'),'~/inout/profile_results');
% % modelfit = feval(modelfun,results.params,feval(stimulusINPUT));
% % thedata = feval(dataINPUT,52948);
% % pmatrix = projectionmatrix(constructpolynomialmatrix(304,0:3));
% % figure; hold on;
% % plot(pmatrix*thedata,'k-');
% % plot(pmatrix*modelfit,'r-');
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% FIT MODEL
%%%%% CLUSTER CASE
if usecluster
% submit jobs
jobnames = {};
jobnames = [jobnames {makedirid(options.outputdir,1)}];
jobids = [];
jobids = [jobids chpcrun(jobnames{end},'fitnonlinearmodel',options.numperjob, ...
1,ceil(length(options.vxs)/options.numperjob),[], ...
{'data' 'stimulus' 'bad' 'd' 'xx' 'yy' 'modelfun' 'model'})];
% record additional files to delete
for p=1:length(jobnames)
remotefilestodelete{end+1} = sprintf('~/sgeoutput/job_%s.*',jobnames{p}); % .o and .e files
remotefilestodelete{end+1} = sprintf('~/mcc/job_%s.mat',jobnames{p});
localfilestodelete{end+1} = sprintf('~/mcc/job_%s.mat',jobnames{p});
end
% wait for jobs to finish
sgewaitjobs(jobnames,jobids,remotelogin,remoteuser);
% download the results
assert(0==unix(sprintf('rsync -a %s:"%s" "%s/"',remotelogin,outputdirremote,tempdir)));
% consolidate the results
fitnonlinearmodel_consolidate(outputdirlocal);
% load the results
a1 = load([outputdirlocal '.mat']);
%%%%% NON-CLUSTER CASE
else
a1 = fitnonlinearmodel(opt);
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE OUTPUT
% depending on which analysis we did (quick or full optimization),
% we have to get the outputs in a common format
if wantquick
paramsA = permute(squish(supergridseeds,dimdata),[3 2 1]); % fits x parameters x voxels
rA = squish(rvalues,dimdata)'; % fits x voxels
else
paramsA = a1.params; % fits x parameters x voxels
rA = a1.trainperformance; % fits x voxels
end
% calc
numfits = size(paramsA,1);
% init
clear results;
results.ang = NaN*zeros(numvxs,numfits);
results.ecc = NaN*zeros(numvxs,numfits);
results.expt = NaN*zeros(numvxs,numfits);
results.rfsize = NaN*zeros(numvxs,numfits);
results.R2 = NaN*zeros(numvxs,numfits);
results.gain = NaN*zeros(numvxs,numfits);
results.resnorms = cell(numvxs,1);
results.numiters = cell(numvxs,1);
if options.xvalmode ~= 0
results.testperformance = NaN*zeros(numvxs,numfits);
results.aggregatedtestperformance = NaN*zeros(numvxs,1);
end
% massage model parameters for output and put in 'results' struct
results.ang(options.vxs,:) = permute(mod(atan2((1+resmx)/2 - paramsA(:,1,:), ...
paramsA(:,2,:) - (1+resmx)/2),2*pi)/pi*180,[3 1 2]);
results.ecc(options.vxs,:) = permute(sqrt(((1+resmx)/2 - paramsA(:,1,:)).^2 + ...
(paramsA(:,2,:) - (1+resmx)/2).^2),[3 1 2]);
results.expt(options.vxs,:) = permute(posrect(paramsA(:,5,:),options.exptlowerbound),[3 1 2]);
results.rfsize(options.vxs,:) = permute(abs(paramsA(:,3,:)) ./ sqrt(posrect(paramsA(:,5,:),options.exptlowerbound)),[3 1 2]);
results.R2(options.vxs,:) = permute(rA,[2 1]);
results.gain(options.vxs,:) = permute(posrect(paramsA(:,4,:)),[3 1 2]);
if ~wantquick
results.resnorms(options.vxs) = a1.resnorms;
results.numiters(options.vxs) = a1.numiters;
end
if options.xvalmode ~= 0
results.testperformance(options.vxs,:) = permute(a1.testperformance,[2 1]);
results.aggregatedtestperformance(options.vxs) = a1.aggregatedtestperformance;
end
% check for special case of ecc==0; set angle to NaN since it is undefined
results.ang(results.ecc==0) = NaN;
% reshape
results.ang = reshape(results.ang, [xyzsize numfits]);
results.ecc = reshape(results.ecc, [xyzsize numfits]);
results.expt = reshape(results.expt, [xyzsize numfits]);
results.rfsize = reshape(results.rfsize, [xyzsize numfits]);
results.R2 = reshape(results.R2, [xyzsize numfits]);
results.gain = reshape(results.gain, [xyzsize numfits]);
results.resnorms = reshape(results.resnorms, [xyzsize 1]);
results.numiters = reshape(results.numiters, [xyzsize 1]);
if options.xvalmode ~= 0
results.testperformance = reshape(results.testperformance,[xyzsize numfits]);
results.aggregatedtestperformance = reshape(results.aggregatedtestperformance,[xyzsize 1]);
end
% add some more stuff
results.meanvol = meanvol;
results.noisereg = noisereg;
results.params = paramsA;
results.options = options;
% save 'results' to a temporary file so we don't lose these precious results!
file0 = [tempname '.mat'];
fprintf('saving results to %s (just in case).\n',file0);
save(file0,'results');
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLEAN UP
% no clean up necessary in the quick case
if ~wantquick
%%%%% CLUSTER CASE
if usecluster
% delete local files and directories
for p=1:length(localfilestodelete)
if exist(localfilestodelete{p},'dir') % first dir, then file
rmdir(localfilestodelete{p},'s');
elseif exist(localfilestodelete{p},'file')
delete(localfilestodelete{p});
end
end
% delete remote files and directories
for p=1:length(remotefilestodelete)
assert(0==unix(sprintf('ssh %s "rm -rf %s"',remotelogin,remotefilestodelete{p})));
end
%%%%% NON-CLUSTER CASE
else
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% REPORT
fprintf('*** analyzePRF: ended at %s (%.1f minutes). ***\n', ...
datestr(now),etime(clock,stime)/60);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% JUNK
% % define the model (parameters are R C S G N [HRF])
% modelfun = @(pp,dd) conv2run(posrect(pp(4)) * (dd*[vflatten(placematrix(zeros(res),makegaussian2d(resmx,pp(1),pp(2),abs(pp(3)),abs(pp(3)),xx,yy,0,0) / (2*pi*abs(pp(3))^2))); 0]) .^ posrect(pp(5)),pp(5+(1:numinhrf))',dd(:,prod(res)+1));
% model = {{[] [1-res(1)+1 1-res(2)+1 0 0 NaN repmat(NaN,[1 numinhrf]);
% 2*res(1)-1 2*res(2)-1 Inf Inf Inf repmat(Inf,[1 numinhrf])] modelfun} ...
% {@(ss)ss [1-res(1)+1 1-res(2)+1 0 0 0 repmat(NaN,[1 numinhrf]);
% 2*res(1)-1 2*res(2)-1 Inf Inf Inf repmat(Inf,[1 numinhrf])] @(ss)modelfun} ...
% {@(ss)ss [1-res(1)+1 1-res(2)+1 0 0 0 repmat(-Inf,[1 numinhrf]);
% 2*res(1)-1 2*res(2)-1 Inf Inf Inf repmat(Inf,[1 numinhrf])] @(ss)modelfun}};
%
% % if not fitting the HRF, exclude the last model step
% if ~wantfithrf
% model = model(1:2);
% end
%wantfithrf = 0; % for now, leave at 0
% results.hrf = NaN*zeros(numvxs,numinhrf,numfits);
% results.hrf(options.vxs,:,:) = permute(a1.params(:,5+(1:numinhrf),:),[3 2 1]);
% results.hrf = reshape(results.hrf, [xyzsize numinhrf numfits]);