analyzePRF_modeltype.m 34 KB

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