analyzePRF.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. function results = analyzePRF(stimulus,data,tr,options)
  2. % function results = analyzePRF(stimulus,data,tr,options)
  3. %
  4. % <stimulus> provides the apertures as a cell vector of R x C x time.
  5. % values should be in [0,1]. the number of time points can differ across runs.
  6. % <data> provides the data as a cell vector of voxels x time. can also be
  7. % X x Y x Z x time. the number of time points should match the number of
  8. % time points in <stimulus>.
  9. % <tr> is the TR in seconds (e.g. 1.5)
  10. % <options> (optional) is a struct with the following fields:
  11. % <vxs> (optional) is a vector of voxel indices to analyze. (we automatically
  12. % sort the voxel indices and ensure uniqueness.) default is 1:V where
  13. % V is total number of voxels. to reduce computational time, you may want
  14. % to create a binary brain mask, perform find() on it, and use the result as <vxs>.
  15. % <wantglmdenoise> (optional) is whether to use GLMdenoise to determine
  16. % nuisance regressors to add into the PRF model. note that in order to use
  17. % this feature, there must be at least two runs (and conditions must repeat
  18. % across runs). we automatically determine the GLM design matrix based on
  19. % the contents of <stimulus>. special case is to pass in the noise regressors
  20. % directly (e.g. from a previous call). default: 0.
  21. % <hrf> (optional) is a column vector with the hemodynamic response function (HRF)
  22. % to use in the model. the first value of <hrf> should be coincident with the onset
  23. % of the stimulus, and the HRF should indicate the timecourse of the response to
  24. % a stimulus that lasts for one TR. default is to use a canonical HRF (calculated
  25. % using getcanonicalhrf(tr,tr)').
  26. % <maxpolydeg> (optional) is a non-negative integer indicating the maximum polynomial
  27. % degree to use for drift terms. can be a vector whose length matches the number
  28. % of runs in <data>. default is to use round(L/2) where L is the number of minutes
  29. % in the duration of a given run.
  30. % <seedmode> (optional) is a vector consisting of one or more of the
  31. % following values (we automatically sort and ensure uniqueness):
  32. % 0 means use generic large PRF seed
  33. % 1 means use generic small PRF seed
  34. % 2 means use best seed based on super-grid
  35. % default: [0 1 2]. a special case is to pass <seedmode> as -2. this causes the
  36. % best seed based on the super-grid to be returned as the final estimate, thereby
  37. % bypassing the computationally expensive optimization procedure. further notes
  38. % on this case are given below.
  39. % <xvalmode> (optional) is
  40. % 0 means just fit all the data
  41. % 1 means two-fold cross-validation (first half of runs; second half of runs)
  42. % 2 means two-fold cross-validation (first half of each run; second half of each run)
  43. % default: 0. (note that we round when halving.)
  44. % <numperjob> (optional) is
  45. % [] means to run locally (not on the cluster)
  46. % N where N is a positive integer indicating the number of voxels to analyze in each
  47. % cluster job. this option requires a customized computational setup!
  48. % default: [].
  49. % <maxiter> (optional) is the maximum number of iterations. default: 500.
  50. % <display> (optional) is 'iter' | 'final' | 'off'. default: 'iter'.
  51. % <typicalgain> (optional) is a typical value for the gain in each time-series.
  52. % default: 10.
  53. %
  54. % Analyze pRF data and return the results.
  55. %
  56. % The results structure contains the following fields:
  57. % <ang> contains pRF angle estimates. Values range between 0 and 360 degrees.
  58. % 0 corresponds to the right horizontal meridian, 90 corresponds to the upper vertical
  59. % meridian, and so on.
  60. % <ecc> contains pRF eccentricity estimates. Values are in pixel units with a lower
  61. % bound of 0 pixels.
  62. % <rfsize> contains pRF size estimates. pRF size is defined as sigma/sqrt(n) where
  63. % sigma is the standard of the 2D Gaussian and n is the exponent of the power-law
  64. % function. Values are in pixel units with a lower bound of 0 pixels.
  65. % <expt> contains pRF exponent estimates.
  66. % <gain> contains pRF gain estimates. Values are in the same units of the data
  67. % and are constrained to be non-negative.
  68. % <R2> contains R^2 values that indicate the goodness-of-fit of the model to the data.
  69. % Values are in percentages and generally range between 0% and 100%. The R^2 values
  70. % are computed after projecting out polynomials from both the data and the model fit.
  71. % (Because of this projection, R^2 values can sometimes drop below 0%.) Note that
  72. % if cross-validation is used (see <xvalmode>), the interpretation of <R2> changes
  73. % accordingly.
  74. % <resnorms> and <numiters> contain optimization details (residual norms and
  75. % number of iterations, respectively).
  76. % <meanvol> contains the mean volume, that is, the mean of each voxel's time-series.
  77. % <noisereg> contains a record of the noise regressors used in the model.
  78. % <params> contains a record of the raw parameter estimates that are obtained internally
  79. % in the code. These raw parameters are transformed to a more palatable format for
  80. % the user (as described above).
  81. % <options> contains a record of the options used in the call to analyzePRF.m.
  82. %
  83. % Details on the pRF model:
  84. % - Before analysis, we zero out any voxel that has a non-finite value or has all zeros
  85. % in at least one of the runs. This prevents weird issues due to missing or bad data.
  86. % - The pRF model that is fit is similar to that described in Dumoulin and Wandell (2008),
  87. % except that a static power-law nonlinearity is added to the model. This new model,
  88. % called the Compressive Spatial Summation (CSS) model, is described in Kay, Winawer,
  89. % Mezer, & Wandell (2013).
  90. % - The model involves computing the dot-product between the stimulus and a 2D isotropic
  91. % Gaussian, raising the result to an exponent, scaling the result by a gain factor,
  92. % and then convolving the result with a hemodynamic response function (HRF). Polynomial
  93. % terms are included (on a run-by-run basis) to model the baseline signal level.
  94. % - The 2D isotropic Gaussian is scaled such that the summation of the values in the
  95. % Gaussian is equal to one. This eases the interpretation of the gain of the model.
  96. % - The exponent parameter in the model is constrained to be non-negative.
  97. % - The gain factor in the model is constrained to be non-negative; this aids the
  98. % interpretation of the model (e.g. helps avoid voxels with negative BOLD responses
  99. % to the stimuli).
  100. % - The workhorse of the analysis is fitnonlinearmodel.m, which is essentially a wrapper
  101. % around routines in the MATLAB Optimization Toolbox. We use the Levenberg-Marquardt
  102. % algorithm for optimization, minimizing squared error between the model and the data.
  103. % - A two-stage optimization strategy is used whereby all parameters excluding the
  104. % exponent parameter are first optimized (holding the exponent parameter fixed) and
  105. % then all parameters are optimized (including the exponent parameter). This
  106. % strategy helps avoid local minima.
  107. %
  108. % Regarding GLMdenoise:
  109. % - If the <wantglmdenoise> option is specified, we derive noise regressors using
  110. % GLMdenoise prior to model fitting. This is done by creating a GLM design matrix
  111. % based on the contents of <stimulus> and then using this design matrix in conjunction
  112. % with GLMdenoise to analyze the data. The noise regressors identified by GLMdenoise
  113. % are then used in the fitting of the pRF models (the regressors enter the model
  114. % additively, just like the polynomial regressors).
  115. %
  116. % Regarding seeding issues:
  117. % - To minimize the impact of local minima, the default strategy is to perform full
  118. % optimizations starting from three different initial seeds.
  119. % - The first seed is a generic large pRF that is centered with respect to the stimulus,
  120. % has a pRF size equal to 1/4th of the stimulus extent (thus, +/- 2 pRF sizes matches
  121. % the stimulus extent), and has an exponent of 0.5.
  122. % - The second seed is a generic small pRF that is just like the first seed except has
  123. % a pRF size that is 10 times smaller.
  124. % - The third seed is a "supergrid" seed that is identified by performing a quick grid
  125. % search prior to optimization (similar in spirit to methods described in Dumoulin and
  126. % Wandell, 2008). In this procedure, a list of potential seeds is constructed by
  127. % exploring a range of eccentricities, angles, and exponents. For each potential
  128. % seed, the model prediction is computed, and the seed that produces the closest
  129. % match to the data is identified. Note that the supergrid seed may be different
  130. % for different voxels.
  131. %
  132. % Regarding the "quick" mode:
  133. % - When <seedmode> is -2, optimization is not performed and instead the best seed
  134. % based on the super-grid is returned as the final estimate. If this case is used,
  135. % we automatically enforce that:
  136. % - opt.xvalmode is 0
  137. % - opt.vxs is []
  138. % - opt.numperjob is []
  139. % Also, in terms of outputs:
  140. % - The <gain> output is not estimated, and gain values are just returned as <typicalgain>.
  141. % - The <R2> output will contain correlation values (r) that range between -1 and 1.
  142. % These correlation values reflect the correlation between the model prediction and the
  143. % data after projecting out polynomial regressors and the noise regressors (if
  144. % <wantglmdenoise> is specified) from both the model prediction and the data.
  145. % - The <resnorms> and <numiters> outputs will be empty.
  146. %
  147. % history:
  148. % 2015/02/07 - version 1.2
  149. % 2015/02/07 - make analyzePRFcomputesupergridseeds.m less memory intensive
  150. % 2014/11/10 - implement <wantglmdenoise> for the <seedmode> -2 case.
  151. % also, now the super-grid seed computation now
  152. % takes into account noise regressors (previously, the
  153. % noise regressors were ignored).
  154. % 2014/10/20 - add -2 case for <seedmode>
  155. % 2014/06/17 - version 1.1
  156. % 2014/06/15 - add inputs <hrf> and <maxpolydeg>.
  157. % 2014/06/10 - version 1.0
  158. % 2014/04/27 - gain seed is now set to 0; add gain to the output
  159. % 2014/04/29 - use typicalgain now (default 10). allow display input.
  160. % internal notes:
  161. % - for cluster mode, need to make sure fitnonlinearmodel is compiled (compilemcc.m)
  162. % - to check whether local minima are a problem, can look at results.resnorms
  163. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% REPORT
  164. fprintf('*** analyzePRF: started at %s. ***\n',datestr(now));
  165. stime = clock; % start time
  166. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% INTERNAL CONSTANTS
  167. % define
  168. remotedir = '/scratch/knk/input/';
  169. remotedir2 = '/scratch/knk/output/';
  170. remotelogin = 'knk@login2.chpc.wustl.edu';
  171. remoteuser = 'knk';
  172. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SETUP AND PREPARATION
  173. % massage cell inputs
  174. if ~iscell(stimulus)
  175. stimulus = {stimulus};
  176. end
  177. if ~iscell(data)
  178. data = {data};
  179. end
  180. % calc
  181. is3d = size(data{1},4) > 1;
  182. if is3d
  183. dimdata = 3;
  184. dimtime = 4;
  185. xyzsize = sizefull(data{1},3);
  186. else
  187. dimdata = 1;
  188. dimtime = 2;
  189. xyzsize = size(data{1},1);
  190. end
  191. numvxs = prod(xyzsize);
  192. % calc
  193. res = sizefull(stimulus{1},2);
  194. resmx = max(res);
  195. numruns = length(data);
  196. % deal with inputs
  197. if ~exist('options','var') || isempty(options)
  198. options = struct();
  199. end
  200. if ~isfield(options,'vxs') || isempty(options.vxs)
  201. options.vxs = 1:numvxs;
  202. end
  203. if ~isfield(options,'wantglmdenoise') || isempty(options.wantglmdenoise)
  204. options.wantglmdenoise = 0;
  205. end
  206. if ~isfield(options,'hrf') || isempty(options.hrf)
  207. options.hrf = [];
  208. end
  209. if ~isfield(options,'maxpolydeg') || isempty(options.maxpolydeg)
  210. options.maxpolydeg = [];
  211. end
  212. if ~isfield(options,'seedmode') || isempty(options.seedmode)
  213. options.seedmode = [0 1 2];
  214. end
  215. if ~isfield(options,'xvalmode') || isempty(options.xvalmode)
  216. options.xvalmode = 0;
  217. end
  218. if ~isfield(options,'numperjob') || isempty(options.numperjob)
  219. options.numperjob = [];
  220. end
  221. if ~isfield(options,'maxiter') || isempty(options.maxiter)
  222. options.maxiter = 500;
  223. end
  224. if ~isfield(options,'display') || isempty(options.display)
  225. options.display = 'iter';
  226. end
  227. if ~isfield(options,'typicalgain') || isempty(options.typicalgain)
  228. options.typicalgain = 10;
  229. end
  230. % massage
  231. wantquick = isequal(options.seedmode,-2);
  232. options.seedmode = union(options.seedmode(:),[]);
  233. % massage more
  234. if wantquick
  235. opt.xvalmode = 0;
  236. opt.vxs = 1:numvxs;
  237. opt.numperjob = [];
  238. end
  239. % calc
  240. usecluster = ~isempty(options.numperjob);
  241. % prepare stimuli
  242. for p=1:length(stimulus)
  243. stimulus{p} = squish(stimulus{p},2)'; % frames x pixels
  244. stimulus{p} = [stimulus{p} p*ones(size(stimulus{p},1),1)]; % add a dummy column to indicate run breaks
  245. stimulus{p} = single(stimulus{p}); % make single to save memory
  246. end
  247. % deal with data badness (set bad voxels to be always all 0)
  248. bad = cellfun(@(x) any(~isfinite(x),dimtime) | all(x==0,dimtime),data,'UniformOutput',0); % if non-finite or all 0
  249. bad = any(cat(dimtime,bad{:}),dimtime); % badness in ANY run
  250. for p=1:numruns
  251. data{p}(repmat(bad,[ones(1,dimdata) size(data{p},dimtime)])) = 0;
  252. end
  253. % calc mean volume
  254. meanvol = mean(catcell(dimtime,data),dimtime);
  255. % what HRF should we use?
  256. if isempty(options.hrf)
  257. options.hrf = getcanonicalhrf(tr,tr)';
  258. end
  259. numinhrf = length(options.hrf);
  260. % what polynomials should we use?
  261. if isempty(options.maxpolydeg)
  262. options.maxpolydeg = cellfun(@(x) round(size(x,dimtime)*tr/60/2),data);
  263. end
  264. if isscalar(options.maxpolydeg)
  265. options.maxpolydeg = repmat(options.maxpolydeg,[1 numruns]);
  266. end
  267. fprintf('using the following maximum polynomial degrees: %s\n',mat2str(options.maxpolydeg));
  268. % initialize cluster stuff
  269. if usecluster
  270. localfilestodelete = {};
  271. remotefilestodelete = {};
  272. end
  273. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% FIGURE OUT NOISE REGRESSORS
  274. if isequal(options.wantglmdenoise,1)
  275. noisereg = analyzePRFcomputeGLMdenoiseregressors(stimulus,data,tr);
  276. elseif isequal(options.wantglmdenoise,0)
  277. noisereg = [];
  278. else
  279. noisereg = options.wantglmdenoise;
  280. end
  281. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE MODEL
  282. % pre-compute some cache
  283. [d,xx,yy] = makegaussian2d(resmx,2,2,2,2);
  284. % define the model (parameters are R C S G N)
  285. 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.hrf,dd(:,prod(res)+1));
  286. model = {{[] [1-res(1)+1 1-res(2)+1 0 0 NaN;
  287. 2*res(1)-1 2*res(2)-1 Inf Inf Inf] modelfun} ...
  288. {@(ss)ss [1-res(1)+1 1-res(2)+1 0 0 0;
  289. 2*res(1)-1 2*res(2)-1 Inf Inf Inf] @(ss)modelfun}};
  290. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE SEEDS
  291. % init
  292. seeds = [];
  293. % generic large seed
  294. if ismember(0,options.seedmode)
  295. seeds = [seeds;
  296. (1+res(1))/2 (1+res(2))/2 resmx/4*sqrt(0.5) options.typicalgain 0.5];
  297. end
  298. % generic small seed
  299. if ismember(1,options.seedmode)
  300. seeds = [seeds;
  301. (1+res(1))/2 (1+res(2))/2 resmx/4*sqrt(0.5)/10 options.typicalgain 0.5];
  302. end
  303. % super-grid seed
  304. if any(ismember([2 -2],options.seedmode))
  305. [supergridseeds,rvalues] = analyzePRFcomputesupergridseeds(res,stimulus,data,modelfun, ...
  306. options.maxpolydeg,dimdata,dimtime, ...
  307. options.typicalgain,noisereg);
  308. end
  309. % make a function that individualizes the seeds
  310. if exist('supergridseeds','var')
  311. seedfun = @(vx) [[seeds];
  312. [subscript(squish(supergridseeds,dimdata),{vx ':'})]];
  313. else
  314. seedfun = @(vx) [seeds];
  315. end
  316. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PERFORM OPTIMIZATION
  317. % if this is true, we can bypass all of the optimization stuff!
  318. if wantquick
  319. else
  320. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE RESAMPLING STUFF
  321. % define wantresampleruns and resampling
  322. switch options.xvalmode
  323. case 0
  324. wantresampleruns = [];
  325. resampling = 0;
  326. case 1
  327. wantresampleruns = 1;
  328. half1 = copymatrix(zeros(1,length(data)),1:round(length(data)/2),1);
  329. half2 = ~half1;
  330. resampling = [(1)*half1 + (-1)*half2;
  331. (-1)*half1 + (1)*half2];
  332. case 2
  333. wantresampleruns = 0;
  334. resampling = [];
  335. for p=1:length(data)
  336. half1 = copymatrix(zeros(1,size(data{p},2)),1:round(size(data{p},2)/2),1);
  337. half2 = ~half1;
  338. resampling = cat(2,resampling,[(1)*half1 + (-1)*half2;
  339. (-1)*half1 + (1)*half2]);
  340. end
  341. end
  342. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE STIMULUS AND DATA
  343. %%%%% CLUSTER CASE
  344. if usecluster
  345. % save stimulus and transport to the remote server
  346. while 1
  347. filename0 = sprintf('stim%s.mat',randomword(5)); % file name
  348. localfile0 = [tempdir '/' filename0]; % local path to file
  349. remotefile0 = [remotedir '/' filename0]; % remote path to file
  350. % redo if file already exists locally or remotely
  351. if exist(localfile0) || 0==unix(sprintf('ssh %s ls %s',remotelogin,remotefile0))
  352. continue;
  353. end
  354. % save file and transport it
  355. save(localfile0,'stimulus');
  356. assert(0==unix(sprintf('rsync -av %s %s:"%s/"',localfile0,remotelogin,remotedir)));
  357. % record
  358. localfilestodelete{end+1} = localfile0;
  359. remotefilestodelete{end+1} = remotefile0;
  360. % stop
  361. break;
  362. end
  363. clear stimulus; % don't let it bleed through anywhere!
  364. % define stimulus
  365. stimulus = @() loadmulti(remotefile0,'stimulus');
  366. % save data and transport to the remote server
  367. while 1
  368. filename0 = sprintf('data%s',randomword(5)); % directory name that will contain 001.bin, etc.
  369. localfile0 = [tempdir '/' filename0]; % local path to dir
  370. remotefile0 = [remotedir '/' filename0]; % remote path to dir
  371. % redo if dir already exists locally or remotely
  372. if exist(localfile0) || 0==unix(sprintf('ssh %s ls %s',remotelogin,remotefile0))
  373. continue;
  374. end
  375. % save files and transport them
  376. assert(mkdir(localfile0));
  377. for p=1:numruns
  378. savebinary([localfile0 sprintf('/%03d.bin',p)],'single',squish(data{p},dimdata)'); % notice squish
  379. end
  380. assert(0==unix(sprintf('rsync -av %s %s:"%s/"',localfile0,remotelogin,remotedir)));
  381. % record
  382. localfilestodelete{end+1} = localfile0;
  383. remotefilestodelete{end+1} = remotefile0;
  384. % stop
  385. break;
  386. end
  387. clear data;
  388. % define data
  389. binfiles = cellfun(@(x) [remotefile0 sprintf('/%03d.bin',x)],num2cell(1:numruns),'UniformOutput',0);
  390. data = @(vxs) cellfun(@(x) double(loadbinary(x,'single',[0 numvxs],-vxs)),binfiles,'UniformOutput',0);
  391. % prepare the output directory name
  392. while 1
  393. filename0 = sprintf('prfresults%s',randomword(5));
  394. localfile0 = [tempdir '/' filename0];
  395. remotefile0 = [remotedir2 '/' filename0];
  396. if exist(localfile0) || 0==unix(sprintf('ssh %s ls %s',remotelogin,remotefile0))
  397. continue;
  398. end
  399. localfilestodelete{end+1} = localfile0;
  400. localfilestodelete{end+1} = [localfile0 '.mat']; % after consolidation
  401. remotefilestodelete{end+1} = remotefile0;
  402. break;
  403. end
  404. outputdirlocal = localfile0;
  405. outputdirremote = remotefile0;
  406. outputdir = outputdirremote;
  407. %%%%% NON-CLUSTER CASE
  408. else
  409. stimulus = {stimulus};
  410. data = @(vxs) cellfun(@(x) subscript(squish(x,dimdata),{vxs ':'})',data,'UniformOutput',0);
  411. outputdir = [];
  412. end
  413. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE OPTIONS
  414. % last-minute prep
  415. if iscell(noisereg)
  416. noiseregINPUT = {noisereg};
  417. else
  418. noiseregINPUT = noisereg;
  419. end
  420. % construct the options struct
  421. opt = struct( ...
  422. 'outputdir',outputdir, ...
  423. 'stimulus',stimulus, ...
  424. 'data',data, ...
  425. 'vxs',options.vxs, ...
  426. 'model',{model}, ...
  427. 'seed',seedfun, ...
  428. 'optimoptions',{{'Display' options.display 'Algorithm' 'levenberg-marquardt' 'MaxIter' options.maxiter}}, ...
  429. 'wantresampleruns',wantresampleruns, ...
  430. 'resampling',resampling, ...
  431. 'metric',@calccod, ...
  432. 'maxpolydeg',options.maxpolydeg, ...
  433. 'wantremovepoly',1, ...
  434. 'extraregressors',noiseregINPUT, ...
  435. 'wantremoveextra',0, ...
  436. 'dontsave',{{'modelfit' 'opt' 'vxsfull' 'modelpred' 'testdata'}}); % 'resnorms' 'numiters'
  437. % 'outputfcn',@(a,b,c,d) pause2(.1) | outputfcnsanitycheck(a,b,c,1e-6,10) | outputfcnplot(a,b,c,1,d), ...
  438. %'outputfcn',@(a,b,c,d) pause2(.1) | outputfcnsanitycheck(a,b,c,1e-6,10) | outputfcnplot(a,b,c,1,d));
  439. % % debugging:
  440. % chpcstimfile = '/stone/ext1/knk/HCPretinotopy/conimagesB.mat';
  441. % chpcdatadir2 = outputdir2; % go back
  442. % opt.outputdir='~/temp1';
  443. % profile on;
  444. % results = fitnonlinearmodel(opt,100,100);
  445. % results = fitnonlinearmodel(opt,1,715233);
  446. % profsave(profile('info'),'~/inout/profile_results');
  447. % % modelfit = feval(modelfun,results.params,feval(stimulusINPUT));
  448. % % thedata = feval(dataINPUT,52948);
  449. % % pmatrix = projectionmatrix(constructpolynomialmatrix(304,0:3));
  450. % % figure; hold on;
  451. % % plot(pmatrix*thedata,'k-');
  452. % % plot(pmatrix*modelfit,'r-');
  453. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% FIT MODEL
  454. %%%%% CLUSTER CASE
  455. if usecluster
  456. % submit jobs
  457. jobnames = {};
  458. jobnames = [jobnames {makedirid(opt.outputdir,1)}];
  459. jobids = [];
  460. jobids = [jobids chpcrun(jobnames{end},'fitnonlinearmodel',options.numperjob, ...
  461. 1,ceil(length(options.vxs)/options.numperjob),[], ...
  462. {'data' 'stimulus' 'bad' 'd' 'xx' 'yy' 'modelfun' 'model'})];
  463. % record additional files to delete
  464. for p=1:length(jobnames)
  465. remotefilestodelete{end+1} = sprintf('~/sgeoutput/job_%s.*',jobnames{p}); % .o and .e files
  466. remotefilestodelete{end+1} = sprintf('~/mcc/job_%s.mat',jobnames{p});
  467. localfilestodelete{end+1} = sprintf('~/mcc/job_%s.mat',jobnames{p});
  468. end
  469. % wait for jobs to finish
  470. sgewaitjobs(jobnames,jobids,remotelogin,remoteuser);
  471. % download the results
  472. assert(0==unix(sprintf('rsync -a %s:"%s" "%s/"',remotelogin,outputdirremote,tempdir)));
  473. % consolidate the results
  474. fitnonlinearmodel_consolidate(outputdirlocal);
  475. % load the results
  476. a1 = load([outputdirlocal '.mat']);
  477. %%%%% NON-CLUSTER CASE
  478. else
  479. a1 = fitnonlinearmodel(opt);
  480. end
  481. end
  482. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE OUTPUT
  483. % depending on which analysis we did (quick or full optimization),
  484. % we have to get the outputs in a common format
  485. if wantquick
  486. paramsA = permute(squish(supergridseeds,dimdata),[3 2 1]); % fits x parameters x voxels
  487. rA = squish(rvalues,dimdata)'; % fits x voxels
  488. else
  489. paramsA = a1.params; % fits x parameters x voxels
  490. rA = a1.trainperformance; % fits x voxels
  491. end
  492. % calc
  493. numfits = size(paramsA,1);
  494. % init
  495. clear results;
  496. results.ang = NaN*zeros(numvxs,numfits);
  497. results.ecc = NaN*zeros(numvxs,numfits);
  498. results.expt = NaN*zeros(numvxs,numfits);
  499. results.rfsize = NaN*zeros(numvxs,numfits);
  500. results.R2 = NaN*zeros(numvxs,numfits);
  501. results.gain = NaN*zeros(numvxs,numfits);
  502. results.resnorms = cell(numvxs,1);
  503. results.numiters = cell(numvxs,1);
  504. % massage model parameters for output and put in 'results' struct
  505. results.ang(options.vxs,:) = permute(mod(atan2((1+res(1))/2 - paramsA(:,1,:), ...
  506. paramsA(:,2,:) - (1+res(2))/2),2*pi)/pi*180,[3 1 2]);
  507. results.ecc(options.vxs,:) = permute(sqrt(((1+res(1))/2 - paramsA(:,1,:)).^2 + ...
  508. (paramsA(:,2,:) - (1+res(2))/2).^2),[3 1 2]);
  509. results.expt(options.vxs,:) = permute(posrect(paramsA(:,5,:)),[3 1 2]);
  510. results.rfsize(options.vxs,:) = permute(abs(paramsA(:,3,:)) ./ sqrt(posrect(paramsA(:,5,:))),[3 1 2]);
  511. results.R2(options.vxs,:) = permute(rA,[2 1]);
  512. results.gain(options.vxs,:) = permute(posrect(paramsA(:,4,:)),[3 1 2]);
  513. if ~wantquick
  514. results.resnorms(options.vxs) = a1.resnorms;
  515. results.numiters(options.vxs) = a1.numiters;
  516. end
  517. % reshape
  518. results.ang = reshape(results.ang, [xyzsize numfits]);
  519. results.ecc = reshape(results.ecc, [xyzsize numfits]);
  520. results.expt = reshape(results.expt, [xyzsize numfits]);
  521. results.rfsize = reshape(results.rfsize, [xyzsize numfits]);
  522. results.R2 = reshape(results.R2, [xyzsize numfits]);
  523. results.gain = reshape(results.gain, [xyzsize numfits]);
  524. results.resnorms = reshape(results.resnorms, [xyzsize 1]);
  525. results.numiters = reshape(results.numiters, [xyzsize 1]);
  526. % add some more stuff
  527. results.meanvol = meanvol;
  528. results.noisereg = noisereg;
  529. results.params = paramsA;
  530. results.options = options;
  531. % save 'results' to a temporary file so we don't lose these precious results!
  532. file0 = [tempname '.mat'];
  533. fprintf('saving results to %s (just in case).\n',file0);
  534. save(file0,'results');
  535. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLEAN UP
  536. % no clean up necessary in the quick case
  537. if ~wantquick
  538. %%%%% CLUSTER CASE
  539. if usecluster
  540. % delete local files and directories
  541. for p=1:length(localfilestodelete)
  542. if exist(localfilestodelete{p},'dir') % first dir, then file
  543. rmdir(localfilestodelete{p},'s');
  544. elseif exist(localfilestodelete{p},'file')
  545. delete(localfilestodelete{p});
  546. end
  547. end
  548. % delete remote files and directories
  549. for p=1:length(remotefilestodelete)
  550. assert(0==unix(sprintf('ssh %s "rm -rf %s"',remotelogin,remotefilestodelete{p})));
  551. end
  552. %%%%% NON-CLUSTER CASE
  553. else
  554. end
  555. end
  556. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% REPORT
  557. fprintf('*** analyzePRF: ended at %s (%.1f minutes). ***\n', ...
  558. datestr(now),etime(clock,stime)/60);
  559. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% JUNK
  560. % % define the model (parameters are R C S G N [HRF])
  561. % 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));
  562. % model = {{[] [1-res(1)+1 1-res(2)+1 0 0 NaN repmat(NaN,[1 numinhrf]);
  563. % 2*res(1)-1 2*res(2)-1 Inf Inf Inf repmat(Inf,[1 numinhrf])] modelfun} ...
  564. % {@(ss)ss [1-res(1)+1 1-res(2)+1 0 0 0 repmat(NaN,[1 numinhrf]);
  565. % 2*res(1)-1 2*res(2)-1 Inf Inf Inf repmat(Inf,[1 numinhrf])] @(ss)modelfun} ...
  566. % {@(ss)ss [1-res(1)+1 1-res(2)+1 0 0 0 repmat(-Inf,[1 numinhrf]);
  567. % 2*res(1)-1 2*res(2)-1 Inf Inf Inf repmat(Inf,[1 numinhrf])] @(ss)modelfun}};
  568. %
  569. % % if not fitting the HRF, exclude the last model step
  570. % if ~wantfithrf
  571. % model = model(1:2);
  572. % end
  573. %wantfithrf = 0; % for now, leave at 0
  574. % results.hrf = NaN*zeros(numvxs,numinhrf,numfits);
  575. % results.hrf(options.vxs,:,:) = permute(a1.params(:,5+(1:numinhrf),:),[3 2 1]);
  576. % results.hrf = reshape(results.hrf, [xyzsize numinhrf numfits]);