Home > GLMdenoise > GLMestimatemodel.m

GLMestimatemodel

PURPOSE ^

function [results,cache] = GLMestimatemodel(design,data,stimdur,tr,hrfmodel,hrfknobs,resampling,opt,cache)

SYNOPSIS ^

function [results,cache] = GLMestimatemodel(design,data,stimdur,tr,hrfmodel,hrfknobs,resampling,opt,cache,mode)

DESCRIPTION ^

 function [results,cache] = GLMestimatemodel(design,data,stimdur,tr,hrfmodel,hrfknobs,resampling,opt,cache)

 <design> is the experimental design.  There are three possible cases:
   1. A where A is a matrix with dimensions time x conditions.
      Each column should be zeros except for ones indicating condition onsets.
      (Fractional values in the design matrix are also allowed.)
   2. {A1 A2 A3 ...} where each of the A's are like the previous case.
      The different A's correspond to different runs, and different runs
      can have different numbers of time points.
   3. {{C1_1 C2_1 C3_1 ...} {C1_2 C2_2 C3_2 ...} ...} where Ca_b is a vector of 
      onset times (in seconds) for condition a in run b.  Time starts at 0 
      and is coincident with the acquisition of the first volume.  This case 
      is compatible only with <hrfmodel> set to 'assume'.
 <data> is the time-series data with dimensions X x Y x Z x time or a cell vector of 
   elements that are each X x Y x Z x time.  XYZ can be collapsed such that the data 
   are given as a 2D matrix (XYZ x time); however, if you do this, then several of the 
   figures that are written out by this function will not be useful to look at.  The 
   dimensions of <data> should mirror that of <design>.  (For example, <design> and 
   <data> should have the same number of runs, the same number of time points, etc.  
   Thus, <data> should have at least two runs since <design> must have at least two 
   runs.)  <data> should not contain any NaNs. We automatically convert <data> to 
   single format (if necessary).
 <stimdur> is the duration of a trial in seconds
 <tr> is the sampling rate in seconds
 <hrfmodel> indicates the type of model to use for the HRF:
   'fir' indicates a finite impulse response model (a separate timecourse
     is estimated for every voxel and every condition)
   'assume' indicates that the HRF is provided (see <hrfknobs>)
   'optimize' indicates that we should estimate a global HRF from the data
 <hrfknobs> (optional) is as follows:
   if <hrfmodel> is 'fir', then <hrfknobs> should be the number of 
     time points in the future to model (N >= 0).  For example, if N is 10, 
     then timecourses will consist of 11 points, with the first point 
     coinciding with condition onset.
   if <hrfmodel> is 'assume', then <hrfknobs> should be time x 1 with
     the HRF to assume.
   if <hrfmodel> is 'optimize', then <hrfknobs> should be time x 1 with the 
     initial seed for the HRF.  The length of this vector indicates the
     number of time points that we will attempt to estimate in the HRF.
   Note on normalization:  In the case that <hrfmodel> is 'assume' or
   'optimize', we automatically divide <hrfknobs> by the maximum value
   so that the peak is equal to 1.  And if <hrfmodel> is 'optimize',
   then after fitting the HRF, we again normalize the HRF to peak at 1
   (and adjust amplitudes accordingly).  Default in the case of 'fir' is
   20.  Default in the case of 'assume' and 'optimize' is to use a 
   canonical HRF that is calculated based on <stimdur> and <tr>.
 <resampling> specifies the resampling scheme:
   0 means fit fully (don't bootstrap or cross-validate)
   A means bootstrap A times (A >= 1)
  -1 means perform leave-one-run-out cross-validation (in this case, there
     must be at least two runs)
 <opt> (optional) is a struct with the following fields:
   <extraregressors> (optional) is time x regressors or a cell vector
     of elements that are each time x regressors.  The dimensions of 
     <extraregressors> should mirror that of <design> (i.e. same number of 
     runs, same number of time points).  The number of extra regressors 
     does not have to be the same across runs, and each run can have zero 
     or more extra regressors.  If [] or not supplied, we do 
     not use extra regressors in the model.
   <maxpolydeg> (optional) is a non-negative integer with the maximum 
     polynomial degree to use for polynomial nuisance functions, which
     are used to capture low-frequency noise fluctuations in each run.
     Can be a vector with length equal to the number of runs (this
     allows you to specify different degrees for different runs).  
     Default is to use round(L/2) for each run where L is the 
     duration in minutes of a given run.
   <seed> (optional) is the random number seed to use (this affects
     the selection of bootstrap samples). Default: sum(100*clock).
   <bootgroups> (optional) is a vector of positive integers indicating
     the grouping of runs to use when bootstrapping.  For example, 
     a grouping of [1 1 1 2 2 2] means that of the six samples that are
     drawn, three samples will be drawn (with replacement) from the first
     three runs and three samples will be drawn (with replacement) from
     the second three runs.  This functionality is useful in situations
     where different runs involve different conditions.  Default: ones(1,D) 
     where D is the number of runs.
   <numforhrf> (optional) is a positive integer indicating the number 
     of voxels (with the best R^2 values) to consider in fitting the 
     global HRF.  This input matters only when <hrfmodel> is 'optimize'.
     Default: 50.  (If there are fewer than that number of voxels
     available, we just use the voxels that are available.)
   <hrffitmask> (optional) is X x Y x Z with 1s indicating all possible
     voxels to consider for fitting the global HRF.  This input matters
     only when <hrfmodel> is 'optimize'.  Special case is 1 which means
     all voxels can be potentially chosen.  Default: 1.
   <wantpercentbold> (optional) is whether to convert the amplitude estimates
     in 'models', 'modelmd', and 'modelse' to percent BOLD change.  This is
     done as the very last step, and is accomplished by dividing by the 
     absolute value of 'meanvol' and multiplying by 100.  (The absolute 
     value prevents negative values in 'meanvol' from flipping the sign.)
     Default: 1.
   <hrfthresh> (optional) is an R^2 threshold.  If the R^2 between the estimated
      HRF and the initial HRF is less than <hrfthresh>, we decide to just use
      the initial HRF.  Set <hrfthresh> to -Inf if you never want to reject
      the estimated HRF.  Default: 50.
   <suppressoutput> (optional) is whether to suppress fprintf statements.  Default: 0.
 <cache> (optional) is used for speeding up execution.  If you are calling this
   function with identical inputs except potentially for different <data>, then
   if you can take the <cache> returned by the first call and re-use it for
   subsequent calls.

 Based on the experimental design (<design>, <stimdur>, <tr>) and the model 
 specification (<hrfmodel>, <hrfknobs>), fit a GLM model to the data (<data>) 
 using a certain resampling scheme (<resampling>).

 Return <results> as a struct containing the following fields:
 <models> contains the full set of model estimates (e.g. all bootstrap results)
 <modelmd> contains the final model estimate (median of the estimates in <models>)
 <modelse> contains the standard error of the final model estimate (half of the
   68% range of the estimates in <models>).  Note that <modelse> will be
   computed in all resampling schemes (full-fit, bootstrapping, and 
   cross-validation) but can be interpreted as an estimate of standard 
   error only in the bootstrapping scheme.
 <R2> is X x Y x Z with model accuracy expressed in terms of R^2 (percentage).
   In the full-fit and bootstrap cases, <R2> is an R^2 value indicating how
     well the final model estimate (<modelmd>) fits the data.
   In the cross-validation case, <R2> is an R^2 value indicating how well
     the cross-validated predictions of the model match the data.  (The
     predictions and the data are each aggregated across runs before
     the computation of R^2.)
 <R2run> is X x Y x Z x runs with R^2 values calculated on a per-run basis.
 <signal> is X x Y x Z with the maximum absolute amplitude in <modelmd>
   (this is computed over all conditions and time points in the case of 'fir'
   and over all conditions in the case of 'assume' and 'optimize').
 <noise> is X x Y x Z with the average amplitude error in <modelse>.
 <SNR> is X x Y x Z with <signal> divided by <noise>.
 <hrffitvoxels> is X x Y x Z with 1s indicating the voxels used for fitting
   the global HRF.  This input is returned as [] if <hrfmodel> is not 'optimize'.
   In the bootstrap and cross-validation cases, <hrffitvoxels> indicates the
   voxels corresponding to the last iteration.
 <meanvol> is X x Y x Z with the mean of all volumes
 <inputs> is a struct containing all inputs used in the call to this
   function, excluding <data>.  We additionally include a field called 
   'datasize' which contains the size of each element of <data>.

 Additional details on the format of <models>, <modelmd>, and <modelse>:
 - If <hrfmodel> is 'fir', then model estimates consist of timecourses:
   <models> is X x Y x Z x conditions x time x resamples
   <modelmd> is X x Y x Z x conditions x time
   <modelse> is X x Y x Z x conditions x time
 - If <hrfmodel> is 'assume' or 'optimize', then model estimates consist
     of HRF estimates and amplitude estimates:
   <models> is {A B} where A is time x resamples (HRF estimates)
     and B is X x Y x Z x conditions x resamples (amplitude estimates)
   <modelmd> is {A B} where A is time x 1 and B is X x Y x Z x conditions
   <modelse> is {A B} where A is time x 1 and B is X x Y x Z x conditions

 Notes on model accuracy (R^2):
 - We quantify the accuracy of the GLM model as the amount of variance in the
 time-series data that is explained by the deterministic portion of the model,
 that is, the hemodynamic responses evoked by the various experimental conditions.
 Note that this does not include the nuisance components of the model, that is, 
 the polynomial regressors and any extra regressors provided by the user
 (see opt.extraregressors).
 - The metric that we use for accuracy is R^2.  Specifically:
     R^2 = 100 * (1-sum((data-model)^2)/sum(data^2))
 - Before computing R^2 between the model and the data, we project out
 polynomial regressors from both the model and the data. The purpose of 
 this is to reduce the influence of low-frequency fluctuations (which
 can be quite large in fMRI data) on the model accuracy metric.

 Notes on bootstrapping:
 - Bootstrap samples are drawn from entire runs.  (Bootstrapping individual
 data points would be inappropriate due to temporal correlations in fMRI noise.)
 For example, if there are 10 runs, each bootstrap sample consists of 10 runs 
 drawn with replacement from the 10 runs.
 - In cases of unbalanced designs, it is possible that a bootstrap sample contains
 no occurrences of a given condition; in this case, a warning is reported and
 the beta weight estimated for that condition is set to zero.

 Notes on the estimation of a global HRF:
 - When <hrfmodel> is 'optimize', we estimate a global HRF from the data.  
 This is achieved using an iterative fitting strategy:  First, the HRF is fixed 
 to the initial seed provided by <hrfknobs>, and we estimate the amplitudes
 using OLS.  Then, the amplitudes are fixed (to the estimates obtained in
 the previous step), and we estimate the HRF using OLS.  Next, the HRF is fixed
 (to the estimate obtained in the previous step), and we re-estimate the
 amplitudes using OLS.  This process is repeated until convergence.
 - The reason for the iterative fitting strategy is that the entire model 
 cannot be estimated at once using linear fitting techniques (and nonlinear
 techniques would be too costly).
 - At the HRF-estimation steps of the fitting process, the entire dataset can 
 in theory be fit.  However, this is undesirable for two reasons.  One, 
 fitting the entire dataset may have exorbitant memory requirements.  
 Two, assuming that most voxels are unrelated to the experimental paradigm 
 (as is typically the case in an fMRI experiment), fitting the entire dataset
 will result in a poor-quality (noisy) HRF.  To resolve these issues, we use 
 a strategy in which we determine the best voxels in terms of R^2 at a given 
 amplitude-estimation step and fit only these voxels in the subsequent
 HRF-estimation step.  The number of voxels that are chosen is controlled 
 by opt.numforhrf, and the pool of chosen voxels is updated at each
 amplitude-estimation step.
 - In some cases, the fitted global HRF may diverge wildly from the initial 
 seed.  This may indicate extremely low SNR and/or a problem with the coding
 of the experimental design and/or a poor initial seed for the HRF.  If the
 R^2 between the initial seed and the fitted global HRF is less than opt.hrfthresh,
 we issue a warning and simply use the initial seed as the HRF (instead of
 relying on the fitted global HRF).  These cases should be inspected and
 troubleshooted on a case-by-case basis.  (In GLMdenoisedata.m, a figure
 named "HRF.png" is created --- if the initial and estimated HRF are 
 exactly overlapping on the figure, this indicates that the exception 
 case occured.)

 Additional information:
 - In some circumstances (e.g. using a FIR model with insufficient data),
 the design matrix may be singular and there is no unique solution.  Our
 strategy for these cases is as follows: If MATLAB issues a warning during 
 the inversion of the autocorrelation matrix (i.e. X'*X), then program 
 execution halts.

 History:
 - 2014/07/31: return rawdesign in cache; change cubic to pchip to avoid warnings
 - 2013/12/11: now, after we are done using opt.seed, we reset the random number seed 
               to something random (specifically, sum(100*clock)).
 - 2013/11/18: add cache input/output; update documentation; new default for
               maxpolydeg (it used to always be 2); add opt.hrfthresh; 
               add opt.suppressoutput; some speed-ups
 - 2013/05/12: allow <design> to specify onset times
 - 2013/05/12: update to indicate fractional values in design matrix are allowed.
 - 2013/05/12 - regressors that are all zero now receive a 0 weight (instead of crashing)
 - 2013/05/12 - fixed a bug regarding how the extraregressors were being handled.
   previously, the extraregressors and the polynomial regressors were being regressed
   out sequentially, which is improper.  now, the two regressors are being fit
   simultaneously, which is the correct way to do it.
 - 2012/12/06: automatically convert data to single format
 - 2012/12/03: *** Tag: Version 1.02 ***. Use faster OLS computation (less
   error-checking; program execution will halt if design matrix is singular);
   implement various speed-ups; minor bug fixes.
 - 2012/11/24:
   - INPUTS: add stimdur and tr; hrfknobs is optional now; add opt.hrffitmask; add opt.wantpercentbold
   - OUTPUTS: add signal,noise,SNR; add hrffitvoxels; add meanvol; add inputs
   - add a speed-up (design2pre)
 - 2012/11/02 - Initial version.
 - 2012/10/30 - Automatic division of HRF. Ensure one complete round of fitting in optimize case.
                Add sanity check on HRF.

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SUBFUNCTIONS ^

SOURCE CODE ^

0001 function [results,cache] = GLMestimatemodel(design,data,stimdur,tr,hrfmodel,hrfknobs,resampling,opt,cache,mode)
0002 
0003 % function [results,cache] = GLMestimatemodel(design,data,stimdur,tr,hrfmodel,hrfknobs,resampling,opt,cache)
0004 %
0005 % <design> is the experimental design.  There are three possible cases:
0006 %   1. A where A is a matrix with dimensions time x conditions.
0007 %      Each column should be zeros except for ones indicating condition onsets.
0008 %      (Fractional values in the design matrix are also allowed.)
0009 %   2. {A1 A2 A3 ...} where each of the A's are like the previous case.
0010 %      The different A's correspond to different runs, and different runs
0011 %      can have different numbers of time points.
0012 %   3. {{C1_1 C2_1 C3_1 ...} {C1_2 C2_2 C3_2 ...} ...} where Ca_b is a vector of
0013 %      onset times (in seconds) for condition a in run b.  Time starts at 0
0014 %      and is coincident with the acquisition of the first volume.  This case
0015 %      is compatible only with <hrfmodel> set to 'assume'.
0016 % <data> is the time-series data with dimensions X x Y x Z x time or a cell vector of
0017 %   elements that are each X x Y x Z x time.  XYZ can be collapsed such that the data
0018 %   are given as a 2D matrix (XYZ x time); however, if you do this, then several of the
0019 %   figures that are written out by this function will not be useful to look at.  The
0020 %   dimensions of <data> should mirror that of <design>.  (For example, <design> and
0021 %   <data> should have the same number of runs, the same number of time points, etc.
0022 %   Thus, <data> should have at least two runs since <design> must have at least two
0023 %   runs.)  <data> should not contain any NaNs. We automatically convert <data> to
0024 %   single format (if necessary).
0025 % <stimdur> is the duration of a trial in seconds
0026 % <tr> is the sampling rate in seconds
0027 % <hrfmodel> indicates the type of model to use for the HRF:
0028 %   'fir' indicates a finite impulse response model (a separate timecourse
0029 %     is estimated for every voxel and every condition)
0030 %   'assume' indicates that the HRF is provided (see <hrfknobs>)
0031 %   'optimize' indicates that we should estimate a global HRF from the data
0032 % <hrfknobs> (optional) is as follows:
0033 %   if <hrfmodel> is 'fir', then <hrfknobs> should be the number of
0034 %     time points in the future to model (N >= 0).  For example, if N is 10,
0035 %     then timecourses will consist of 11 points, with the first point
0036 %     coinciding with condition onset.
0037 %   if <hrfmodel> is 'assume', then <hrfknobs> should be time x 1 with
0038 %     the HRF to assume.
0039 %   if <hrfmodel> is 'optimize', then <hrfknobs> should be time x 1 with the
0040 %     initial seed for the HRF.  The length of this vector indicates the
0041 %     number of time points that we will attempt to estimate in the HRF.
0042 %   Note on normalization:  In the case that <hrfmodel> is 'assume' or
0043 %   'optimize', we automatically divide <hrfknobs> by the maximum value
0044 %   so that the peak is equal to 1.  And if <hrfmodel> is 'optimize',
0045 %   then after fitting the HRF, we again normalize the HRF to peak at 1
0046 %   (and adjust amplitudes accordingly).  Default in the case of 'fir' is
0047 %   20.  Default in the case of 'assume' and 'optimize' is to use a
0048 %   canonical HRF that is calculated based on <stimdur> and <tr>.
0049 % <resampling> specifies the resampling scheme:
0050 %   0 means fit fully (don't bootstrap or cross-validate)
0051 %   A means bootstrap A times (A >= 1)
0052 %  -1 means perform leave-one-run-out cross-validation (in this case, there
0053 %     must be at least two runs)
0054 % <opt> (optional) is a struct with the following fields:
0055 %   <extraregressors> (optional) is time x regressors or a cell vector
0056 %     of elements that are each time x regressors.  The dimensions of
0057 %     <extraregressors> should mirror that of <design> (i.e. same number of
0058 %     runs, same number of time points).  The number of extra regressors
0059 %     does not have to be the same across runs, and each run can have zero
0060 %     or more extra regressors.  If [] or not supplied, we do
0061 %     not use extra regressors in the model.
0062 %   <maxpolydeg> (optional) is a non-negative integer with the maximum
0063 %     polynomial degree to use for polynomial nuisance functions, which
0064 %     are used to capture low-frequency noise fluctuations in each run.
0065 %     Can be a vector with length equal to the number of runs (this
0066 %     allows you to specify different degrees for different runs).
0067 %     Default is to use round(L/2) for each run where L is the
0068 %     duration in minutes of a given run.
0069 %   <seed> (optional) is the random number seed to use (this affects
0070 %     the selection of bootstrap samples). Default: sum(100*clock).
0071 %   <bootgroups> (optional) is a vector of positive integers indicating
0072 %     the grouping of runs to use when bootstrapping.  For example,
0073 %     a grouping of [1 1 1 2 2 2] means that of the six samples that are
0074 %     drawn, three samples will be drawn (with replacement) from the first
0075 %     three runs and three samples will be drawn (with replacement) from
0076 %     the second three runs.  This functionality is useful in situations
0077 %     where different runs involve different conditions.  Default: ones(1,D)
0078 %     where D is the number of runs.
0079 %   <numforhrf> (optional) is a positive integer indicating the number
0080 %     of voxels (with the best R^2 values) to consider in fitting the
0081 %     global HRF.  This input matters only when <hrfmodel> is 'optimize'.
0082 %     Default: 50.  (If there are fewer than that number of voxels
0083 %     available, we just use the voxels that are available.)
0084 %   <hrffitmask> (optional) is X x Y x Z with 1s indicating all possible
0085 %     voxels to consider for fitting the global HRF.  This input matters
0086 %     only when <hrfmodel> is 'optimize'.  Special case is 1 which means
0087 %     all voxels can be potentially chosen.  Default: 1.
0088 %   <wantpercentbold> (optional) is whether to convert the amplitude estimates
0089 %     in 'models', 'modelmd', and 'modelse' to percent BOLD change.  This is
0090 %     done as the very last step, and is accomplished by dividing by the
0091 %     absolute value of 'meanvol' and multiplying by 100.  (The absolute
0092 %     value prevents negative values in 'meanvol' from flipping the sign.)
0093 %     Default: 1.
0094 %   <hrfthresh> (optional) is an R^2 threshold.  If the R^2 between the estimated
0095 %      HRF and the initial HRF is less than <hrfthresh>, we decide to just use
0096 %      the initial HRF.  Set <hrfthresh> to -Inf if you never want to reject
0097 %      the estimated HRF.  Default: 50.
0098 %   <suppressoutput> (optional) is whether to suppress fprintf statements.  Default: 0.
0099 % <cache> (optional) is used for speeding up execution.  If you are calling this
0100 %   function with identical inputs except potentially for different <data>, then
0101 %   if you can take the <cache> returned by the first call and re-use it for
0102 %   subsequent calls.
0103 %
0104 % Based on the experimental design (<design>, <stimdur>, <tr>) and the model
0105 % specification (<hrfmodel>, <hrfknobs>), fit a GLM model to the data (<data>)
0106 % using a certain resampling scheme (<resampling>).
0107 %
0108 % Return <results> as a struct containing the following fields:
0109 % <models> contains the full set of model estimates (e.g. all bootstrap results)
0110 % <modelmd> contains the final model estimate (median of the estimates in <models>)
0111 % <modelse> contains the standard error of the final model estimate (half of the
0112 %   68% range of the estimates in <models>).  Note that <modelse> will be
0113 %   computed in all resampling schemes (full-fit, bootstrapping, and
0114 %   cross-validation) but can be interpreted as an estimate of standard
0115 %   error only in the bootstrapping scheme.
0116 % <R2> is X x Y x Z with model accuracy expressed in terms of R^2 (percentage).
0117 %   In the full-fit and bootstrap cases, <R2> is an R^2 value indicating how
0118 %     well the final model estimate (<modelmd>) fits the data.
0119 %   In the cross-validation case, <R2> is an R^2 value indicating how well
0120 %     the cross-validated predictions of the model match the data.  (The
0121 %     predictions and the data are each aggregated across runs before
0122 %     the computation of R^2.)
0123 % <R2run> is X x Y x Z x runs with R^2 values calculated on a per-run basis.
0124 % <signal> is X x Y x Z with the maximum absolute amplitude in <modelmd>
0125 %   (this is computed over all conditions and time points in the case of 'fir'
0126 %   and over all conditions in the case of 'assume' and 'optimize').
0127 % <noise> is X x Y x Z with the average amplitude error in <modelse>.
0128 % <SNR> is X x Y x Z with <signal> divided by <noise>.
0129 % <hrffitvoxels> is X x Y x Z with 1s indicating the voxels used for fitting
0130 %   the global HRF.  This input is returned as [] if <hrfmodel> is not 'optimize'.
0131 %   In the bootstrap and cross-validation cases, <hrffitvoxels> indicates the
0132 %   voxels corresponding to the last iteration.
0133 % <meanvol> is X x Y x Z with the mean of all volumes
0134 % <inputs> is a struct containing all inputs used in the call to this
0135 %   function, excluding <data>.  We additionally include a field called
0136 %   'datasize' which contains the size of each element of <data>.
0137 %
0138 % Additional details on the format of <models>, <modelmd>, and <modelse>:
0139 % - If <hrfmodel> is 'fir', then model estimates consist of timecourses:
0140 %   <models> is X x Y x Z x conditions x time x resamples
0141 %   <modelmd> is X x Y x Z x conditions x time
0142 %   <modelse> is X x Y x Z x conditions x time
0143 % - If <hrfmodel> is 'assume' or 'optimize', then model estimates consist
0144 %     of HRF estimates and amplitude estimates:
0145 %   <models> is {A B} where A is time x resamples (HRF estimates)
0146 %     and B is X x Y x Z x conditions x resamples (amplitude estimates)
0147 %   <modelmd> is {A B} where A is time x 1 and B is X x Y x Z x conditions
0148 %   <modelse> is {A B} where A is time x 1 and B is X x Y x Z x conditions
0149 %
0150 % Notes on model accuracy (R^2):
0151 % - We quantify the accuracy of the GLM model as the amount of variance in the
0152 % time-series data that is explained by the deterministic portion of the model,
0153 % that is, the hemodynamic responses evoked by the various experimental conditions.
0154 % Note that this does not include the nuisance components of the model, that is,
0155 % the polynomial regressors and any extra regressors provided by the user
0156 % (see opt.extraregressors).
0157 % - The metric that we use for accuracy is R^2.  Specifically:
0158 %     R^2 = 100 * (1-sum((data-model)^2)/sum(data^2))
0159 % - Before computing R^2 between the model and the data, we project out
0160 % polynomial regressors from both the model and the data. The purpose of
0161 % this is to reduce the influence of low-frequency fluctuations (which
0162 % can be quite large in fMRI data) on the model accuracy metric.
0163 %
0164 % Notes on bootstrapping:
0165 % - Bootstrap samples are drawn from entire runs.  (Bootstrapping individual
0166 % data points would be inappropriate due to temporal correlations in fMRI noise.)
0167 % For example, if there are 10 runs, each bootstrap sample consists of 10 runs
0168 % drawn with replacement from the 10 runs.
0169 % - In cases of unbalanced designs, it is possible that a bootstrap sample contains
0170 % no occurrences of a given condition; in this case, a warning is reported and
0171 % the beta weight estimated for that condition is set to zero.
0172 %
0173 % Notes on the estimation of a global HRF:
0174 % - When <hrfmodel> is 'optimize', we estimate a global HRF from the data.
0175 % This is achieved using an iterative fitting strategy:  First, the HRF is fixed
0176 % to the initial seed provided by <hrfknobs>, and we estimate the amplitudes
0177 % using OLS.  Then, the amplitudes are fixed (to the estimates obtained in
0178 % the previous step), and we estimate the HRF using OLS.  Next, the HRF is fixed
0179 % (to the estimate obtained in the previous step), and we re-estimate the
0180 % amplitudes using OLS.  This process is repeated until convergence.
0181 % - The reason for the iterative fitting strategy is that the entire model
0182 % cannot be estimated at once using linear fitting techniques (and nonlinear
0183 % techniques would be too costly).
0184 % - At the HRF-estimation steps of the fitting process, the entire dataset can
0185 % in theory be fit.  However, this is undesirable for two reasons.  One,
0186 % fitting the entire dataset may have exorbitant memory requirements.
0187 % Two, assuming that most voxels are unrelated to the experimental paradigm
0188 % (as is typically the case in an fMRI experiment), fitting the entire dataset
0189 % will result in a poor-quality (noisy) HRF.  To resolve these issues, we use
0190 % a strategy in which we determine the best voxels in terms of R^2 at a given
0191 % amplitude-estimation step and fit only these voxels in the subsequent
0192 % HRF-estimation step.  The number of voxels that are chosen is controlled
0193 % by opt.numforhrf, and the pool of chosen voxels is updated at each
0194 % amplitude-estimation step.
0195 % - In some cases, the fitted global HRF may diverge wildly from the initial
0196 % seed.  This may indicate extremely low SNR and/or a problem with the coding
0197 % of the experimental design and/or a poor initial seed for the HRF.  If the
0198 % R^2 between the initial seed and the fitted global HRF is less than opt.hrfthresh,
0199 % we issue a warning and simply use the initial seed as the HRF (instead of
0200 % relying on the fitted global HRF).  These cases should be inspected and
0201 % troubleshooted on a case-by-case basis.  (In GLMdenoisedata.m, a figure
0202 % named "HRF.png" is created --- if the initial and estimated HRF are
0203 % exactly overlapping on the figure, this indicates that the exception
0204 % case occured.)
0205 %
0206 % Additional information:
0207 % - In some circumstances (e.g. using a FIR model with insufficient data),
0208 % the design matrix may be singular and there is no unique solution.  Our
0209 % strategy for these cases is as follows: If MATLAB issues a warning during
0210 % the inversion of the autocorrelation matrix (i.e. X'*X), then program
0211 % execution halts.
0212 %
0213 % History:
0214 % - 2014/07/31: return rawdesign in cache; change cubic to pchip to avoid warnings
0215 % - 2013/12/11: now, after we are done using opt.seed, we reset the random number seed
0216 %               to something random (specifically, sum(100*clock)).
0217 % - 2013/11/18: add cache input/output; update documentation; new default for
0218 %               maxpolydeg (it used to always be 2); add opt.hrfthresh;
0219 %               add opt.suppressoutput; some speed-ups
0220 % - 2013/05/12: allow <design> to specify onset times
0221 % - 2013/05/12: update to indicate fractional values in design matrix are allowed.
0222 % - 2013/05/12 - regressors that are all zero now receive a 0 weight (instead of crashing)
0223 % - 2013/05/12 - fixed a bug regarding how the extraregressors were being handled.
0224 %   previously, the extraregressors and the polynomial regressors were being regressed
0225 %   out sequentially, which is improper.  now, the two regressors are being fit
0226 %   simultaneously, which is the correct way to do it.
0227 % - 2012/12/06: automatically convert data to single format
0228 % - 2012/12/03: *** Tag: Version 1.02 ***. Use faster OLS computation (less
0229 %   error-checking; program execution will halt if design matrix is singular);
0230 %   implement various speed-ups; minor bug fixes.
0231 % - 2012/11/24:
0232 %   - INPUTS: add stimdur and tr; hrfknobs is optional now; add opt.hrffitmask; add opt.wantpercentbold
0233 %   - OUTPUTS: add signal,noise,SNR; add hrffitvoxels; add meanvol; add inputs
0234 %   - add a speed-up (design2pre)
0235 % - 2012/11/02 - Initial version.
0236 % - 2012/10/30 - Automatic division of HRF. Ensure one complete round of fitting in optimize case.
0237 %                Add sanity check on HRF.
0238 
0239 % Internal input:
0240 % <mode> (optional) is
0241 %   1 means that only the 'R2' output is desired (to save computation time)
0242 %   2 means that hrfmodel is 'optimize', resampling is 0, and we only care
0243 %     about the hrf and hrffitvoxels outputs (to save time and memory)
0244 %   Default: 0.
0245 
0246 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DEAL WITH INPUTS, ETC.
0247 
0248 % input
0249 if ~exist('hrfknobs','var') || isempty(hrfknobs)
0250   if isequal(hrfmodel,'fir')
0251     hrfknobs = 20;
0252   else
0253     hrfknobs = normalizemax(getcanonicalhrf(stimdur,tr)');
0254   end
0255 end
0256 if ~exist('opt','var') || isempty(opt)
0257   opt = struct();
0258 end
0259 if ~exist('cache','var') || isempty(cache)
0260   cache = [];
0261 end
0262 if ~exist('mode','var') || isempty(mode)
0263   mode = 0;
0264 end
0265 
0266 % massage input
0267 if ~iscell(design)
0268   design = {design};
0269 end
0270 if ~iscell(data)
0271   data = {data};
0272 end
0273 for p=1:length(data)
0274   if ~isa(data{p},'single')
0275     data{p} = single(data{p});
0276   end
0277 end
0278 
0279 % calc
0280 numruns = length(design);
0281 if resampling == 0
0282   resamplecase = 'full';
0283 elseif resampling >= 1
0284   resamplecase = 'boot';
0285 else
0286   resamplecase = 'xval';
0287 end
0288 is3d = size(data{1},4) > 1;
0289 if is3d
0290   dimdata = 3;
0291   dimtime = 4;
0292   xyzsize = sizefull(data{1},3);
0293 else
0294   dimdata = 1;
0295   dimtime = 2;
0296   xyzsize = size(data{1},1);
0297 end
0298 numvoxels = prod(xyzsize);
0299 
0300 % deal with defaults
0301 if ~isfield(opt,'extraregressors') || isempty(opt.extraregressors)
0302   opt.extraregressors = cell(1,numruns);
0303 end
0304 if ~isfield(opt,'maxpolydeg') || isempty(opt.maxpolydeg)
0305   opt.maxpolydeg = zeros(1,numruns);
0306   for p=1:numruns
0307     opt.maxpolydeg(p) = round(((size(data{p},dimtime)*tr)/60)/2);
0308   end
0309 end
0310 if ~isfield(opt,'seed') || isempty(opt.seed)
0311   opt.seed = sum(100*clock);
0312 end
0313 if ~isfield(opt,'bootgroups') || isempty(opt.bootgroups)
0314   opt.bootgroups = ones(1,numruns);
0315 end
0316 if ~isfield(opt,'numforhrf') || isempty(opt.numforhrf)
0317   opt.numforhrf = 50;
0318 end
0319 if ~isfield(opt,'hrffitmask') || isempty(opt.hrffitmask)
0320   opt.hrffitmask = 1;
0321 end
0322 if ~isfield(opt,'wantpercentbold') || isempty(opt.wantpercentbold)
0323   opt.wantpercentbold = 1;
0324 end
0325 if ~isfield(opt,'hrfthresh') || isempty(opt.hrfthresh)
0326   opt.hrfthresh = 50;
0327 end
0328 if ~isfield(opt,'suppressoutput') || isempty(opt.suppressoutput)
0329   opt.suppressoutput = 0;
0330 end
0331 if isequal(hrfmodel,'assume') || isequal(hrfmodel,'optimize')
0332   hrfknobs = normalizemax(hrfknobs);
0333 end
0334 if length(opt.maxpolydeg) == 1
0335   opt.maxpolydeg = repmat(opt.maxpolydeg,[1 numruns]);
0336 end
0337 
0338 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CALCULATE MEAN VOLUME
0339 
0340 volcnt = cellfun(@(x) size(x,dimtime),data);
0341 meanvol = reshape(catcell(2,cellfun(@(x) squish(mean(x,dimtime),dimdata),data,'UniformOutput',0)) ...
0342                   * (volcnt' / sum(volcnt)),[xyzsize 1]);
0343 
0344 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DEAL WITH NUISANCE COMPONENTS
0345 
0346 % construct projection matrices for the nuisance components
0347 polymatrix = {};
0348 exmatrix = {};
0349 combinedmatrix = {};
0350 for p=1:numruns
0351 
0352   % this projects out polynomials
0353   pmatrix = constructpolynomialmatrix(size(data{p},dimtime),0:opt.maxpolydeg(p));
0354   polymatrix{p} = projectionmatrix(pmatrix);
0355 
0356   % this projects out the extra regressors
0357   if isempty(opt.extraregressors{p})
0358     exmatrix{p} = 1;
0359   else
0360     exmatrix{p} = projectionmatrix(opt.extraregressors{p});
0361   end
0362   
0363   % this projects out both of them
0364   combinedmatrix{p} = projectionmatrix(cat(2,pmatrix,opt.extraregressors{p}));
0365 
0366 end
0367 
0368 % project out nuisance components from the data.
0369 % after this step, data will have polynomials removed,
0370 % and data2 will have both polynomials and extra regressors removed.
0371 data2 = {};  % NOTE: data and data2 are big --- be careful of MEMORY usage.
0372 for p=1:numruns
0373   data{p} = squish(data{p},dimdata)';
0374   data2{p} = combinedmatrix{p}*data{p};
0375   data{p} = polymatrix{p}*data{p};
0376 end
0377 % note that data and data2 are now in flattened format (time x voxels)!!
0378 
0379 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% FIT MODELS
0380 
0381 % note: cache.rawdesign will exist for 'fir' and 'assume' but not 'optimize' and
0382 %       for 'full' and 'boot' but not 'xval'.
0383 
0384 switch resamplecase
0385 
0386 case 'full'
0387 
0388   % this is the full-fit case
0389   
0390   % fit the model to the entire dataset.  we obtain just one analysis result.
0391   if ~opt.suppressoutput, fprintf('fitting model...');, end
0392   results = {};
0393   [results{1},hrffitvoxels,cache] = ...
0394     fitmodel_helper(design,data2,tr,hrfmodel,hrfknobs, ...
0395                     opt,combinedmatrix,dimdata,dimtime,cache);
0396   if ~opt.suppressoutput, fprintf('done.\n');, end
0397 
0398 case 'boot'
0399 
0400   % this is the bootstrap case
0401   
0402   % set random seed
0403   setrandstate({opt.seed});
0404 
0405   % there are two reasons to call this line.  one is that in the case of
0406   % (bootstrap + optimize), we have to do a pre-call to get some cache.
0407   % another is that we may need the cache.rawdesign output.  so, let's just call it.
0408   [d,d,cache] = ...
0409     fitmodel_helper(design,data2,tr,hrfmodel,hrfknobs, ...
0410                     opt,combinedmatrix,dimdata,dimtime,cache);
0411 
0412   % loop over bootstraps and collect up the analysis results.
0413   results = {};
0414   if ~opt.suppressoutput, fprintf('bootstrapping model');, end
0415   for p=1:resampling
0416     statusdots(p,resampling);
0417 
0418     % figure out bootstrap sample
0419     ix = [];
0420     for q=1:max(opt.bootgroups)
0421       num = sum(opt.bootgroups==q);  % number in this group
0422       ix = [ix subscript(find(opt.bootgroups==q),ceil(rand(1,num)*num))];
0423     end
0424   
0425     % fit the model to the bootstrap sample
0426     if isequal(hrfmodel,'optimize')
0427       cache2 = struct('design2pre',{cache.design2pre(ix)});
0428     else
0429       cache2 = [];
0430     end
0431     [results{p},hrffitvoxels] = fitmodel_helper(design(ix),data2(ix), ...
0432                                    tr,hrfmodel,hrfknobs,opt,combinedmatrix(ix),dimdata,dimtime,cache2);
0433   
0434   end
0435   if ~opt.suppressoutput, fprintf('done.\n');, end
0436 
0437   % randomize the random seed
0438   setrandstate(1);
0439 
0440 case 'xval'
0441 
0442   % this is the cross-validation case
0443 
0444   % loop over cross-validation iterations.  in each iteration, we record
0445   % the analysis result and also record the time-series predictions.
0446   modelfit = {};
0447   results = {};
0448   if ~opt.suppressoutput, fprintf('cross-validating model');, end
0449   for p=1:numruns
0450     statusdots(p,numruns);
0451   
0452     % figure out resampling scheme
0453     ix = setdiff(1:numruns,p);
0454   
0455     % fit the model
0456     [results{p},hrffitvoxels] = fitmodel_helper(design(ix),data2(ix), ...
0457                                    tr,hrfmodel,hrfknobs,opt,combinedmatrix(ix),dimdata,dimtime,[]);  % NOTE: no cache
0458   
0459     % compute the prediction
0460     modelfit(p) = GLMpredictresponses(results{p},{design{p}},tr,size(data2{p},1),1);  % 1 because results{p} is in flattened format
0461     
0462     % massage format
0463     modelfit{p} = reshape(modelfit{p},[xyzsize size(modelfit{p},2)]);
0464     
0465   end
0466   if ~opt.suppressoutput, fprintf('done.\n');, end
0467 
0468 end
0469 
0470 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE MODEL ESTIMATES FOR OUTPUT
0471 
0472 % in this special case, we do not have to perform this section,
0473 % so let's skip it to save computational time.
0474 if isequal(resamplecase,'xval') && mode==1
0475   results = struct();
0476 
0477 % otherwise, do it as usual
0478 else
0479 
0480   if ~opt.suppressoutput, fprintf('preparing output...');, end
0481   switch hrfmodel
0482 
0483   case 'fir'
0484 
0485     results = struct('models',cat(4,results{:}));  % voxels x conditions x time x resamples
0486     if size(results.models,4) == 1
0487       results.modelmd = results.models;
0488       results.modelse = zeros(size(results.models),class(results.models));
0489     else
0490       temp = zeros([sizefull(results.models,3) 3],class(results.models));
0491       for p=1:size(results.models,3)  % ugly to avoid memory usage
0492         temp(:,:,p,:) = prctile(results.models(:,:,p,:),[16 50 84],4);
0493       end
0494       results.modelmd = temp(:,:,:,2);
0495       results.modelse = diff(temp(:,:,:,[1 3]),1,4)/2;
0496       clear temp;
0497     end
0498 
0499     % massage format
0500     sz = sizefull(results.models,4);
0501     results.models = reshape(results.models,[xyzsize sz(2:4)]);
0502     results.modelmd = reshape(results.modelmd,[xyzsize sz(2:3)]);
0503     results.modelse = reshape(results.modelse,[xyzsize sz(2:3)]);
0504 
0505   case {'assume' 'optimize'}
0506   
0507     temp = catcell(2,cellfun(@(x) x(1),results));
0508     results = catcell(3,cellfun(@(x) x(2),results));  % beware of MEMORY here
0509     results = struct('models',{{temp results}});  % ugly to avoid memory usage
0510   
0511     % deal with {1}
0512     if size(results.models{1},2) == 1
0513       results.modelmd{1} = results.models{1};
0514       results.modelse{1} = zeros(size(results.models{1}),class(results.models{1}));
0515     else
0516       temp = prctile(results.models{1},[16 50 84],2);
0517       results.modelmd{1} = temp(:,2);
0518       results.modelse{1} = diff(temp(:,[1 3]),1,2)/2;
0519     end
0520   
0521     % deal with {2}
0522     if size(results.models{2},3) == 1
0523       results.modelmd{2} = results.models{2};
0524       results.modelse{2} = zeros(size(results.models{2}),class(results.models{2}));
0525     else
0526       temp = zeros([sizefull(results.models{2},2) 3],class(results.models{2}));
0527       for p=1:size(results.models{2},2)  % ugly to avoid memory usage
0528         temp(:,p,:) = prctile(results.models{2}(:,p,:),[16 50 84],3);
0529       end
0530       results.modelmd{2} = temp(:,:,2);
0531       results.modelse{2} = diff(temp(:,:,[1 3]),1,3)/2;
0532       clear temp;
0533     end
0534 
0535     % massage format
0536     sz = sizefull(results.models{2},3);
0537     results.models{2} = reshape(results.models{2},[xyzsize sz(2:3)]);
0538     results.modelmd{2} = reshape(results.modelmd{2},[xyzsize sz(2)]);
0539     results.modelse{2} = reshape(results.modelse{2},[xyzsize sz(2)]);
0540 
0541   end
0542   if ~opt.suppressoutput, fprintf('done.\n');, end
0543 
0544 end
0545 
0546 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% COMPUTE MODEL FITS (IF NECESSARY)
0547 
0548 if ~(mode==2)
0549 
0550   if ~opt.suppressoutput, fprintf('computing model fits...');, end
0551   switch resamplecase
0552 
0553   case {'full' 'boot'}
0554 
0555     % compute the time-series fit corresponding to the final model estimate
0556     modelfit = GLMpredictresponses(results.modelmd,design,tr,cellfun(@(x) size(x,1),data),dimdata);
0557 
0558   case 'xval'
0559 
0560     % in the cross-validation case, we have already computed the cross-validated
0561     % predictions of the model and stored them in the variable 'modelfit'.
0562 
0563   end
0564   if ~opt.suppressoutput, fprintf('done.\n');, end
0565 
0566 end
0567 
0568 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% COMPUTE R^2
0569 
0570 if ~(mode==2 || mode==3)
0571 
0572   if ~opt.suppressoutput, fprintf('computing R^2...');, end
0573 
0574   % remove polynomials from the model fits (or predictions)
0575   modelfit = cellfun(@(a,b) a*squish(b,dimdata)',polymatrix,modelfit,'UniformOutput',0);  % result is time x voxels
0576 
0577   % calculate overall R^2 [beware: MEMORY]
0578   results.R2 = reshape(calccodcell(modelfit,data,1)',[xyzsize 1]);  % notice that we use 'data' not 'data2'
0579 
0580   % calculate R^2 on a per-run basis [beware: MEMORY]
0581   results.R2run = catcell(dimdata+1,cellfun(@(x,y) reshape(calccod(x,y,1,0,0)',[xyzsize 1]),modelfit,data,'UniformOutput',0));
0582 
0583   % clear
0584   clear modelfit;  % big memory usage
0585 
0586   if ~opt.suppressoutput, fprintf('done.\n');, end
0587 
0588 end
0589 
0590 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% COMPUTE SNR
0591 
0592 if ~opt.suppressoutput, fprintf('computing SNR...');, end
0593 
0594 if ~(isequal(resamplecase,'xval') && mode==1) && ~(mode==2)
0595 
0596   switch hrfmodel
0597   
0598   case 'fir'
0599     results.signal = max(max(abs(results.modelmd),[],dimdata+1),[],dimdata+2);
0600     results.noise = mean(mean(results.modelse,dimdata+1),dimdata+2);
0601     results.SNR = results.signal ./ results.noise;
0602   
0603   case {'assume' 'optimize'}
0604     results.signal = max(abs(results.modelmd{2}),[],dimdata+1);
0605     results.noise = mean(results.modelse{2},dimdata+1);
0606     results.SNR = results.signal ./ results.noise;
0607     
0608   end
0609 
0610 end
0611 
0612 if ~opt.suppressoutput, fprintf('done.\n');, end
0613 
0614 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE ADDITIONAL OUTPUTS
0615 
0616 % this is a special case
0617 if isempty(hrffitvoxels)
0618   results.hrffitvoxels = [];
0619 else
0620   results.hrffitvoxels = copymatrix(zeros([xyzsize 1]),hrffitvoxels,1);
0621 end
0622 results.meanvol = meanvol;
0623 
0624 % return all the inputs (except for the data) in the output.
0625 % also, include a new field 'datasize'.
0626 results.inputs.design = design;
0627 results.inputs.datasize = cellfun(@(x) size(x),data,'UniformOutput',0);
0628 results.inputs.stimdur = stimdur;
0629 results.inputs.tr = tr;
0630 results.inputs.hrfmodel = hrfmodel;
0631 results.inputs.hrfknobs = hrfknobs;
0632 results.inputs.resampling = resampling;
0633 results.inputs.opt = opt;
0634 
0635 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CONVERT TO % BOLD CHANGE
0636 
0637 if opt.wantpercentbold && ~(isequal(resamplecase,'xval') && mode==1)
0638   con = 1./abs(results.meanvol) * 100;
0639   switch hrfmodel
0640   case 'fir'
0641     results.models = bsxfun(@times,results.models,con);
0642     results.modelmd = bsxfun(@times,results.modelmd,con);
0643     results.modelse = bsxfun(@times,results.modelse,con);
0644   case {'assume' 'optimize'}
0645     results.models{2} = bsxfun(@times,results.models{2},con);
0646     results.modelmd{2} = bsxfun(@times,results.modelmd{2},con);
0647     results.modelse{2} = bsxfun(@times,results.modelse{2},con);
0648   end
0649 end
0650 
0651 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
0652 
0653 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% HELPER FUNCTION:
0654 
0655 function [f,hrffitvoxels,cache] = ...
0656   fitmodel_helper(design,data2,tr,hrfmodel,hrfknobs,opt,combinedmatrix,dimdata,dimtime,cache)
0657 
0658 % if hrfmodel is 'fir', then <f> will be voxels x conditions x time (flattened format)
0659 % if hrfmodel is 'assume' or 'optimize', then <f> will be {A B}
0660 %   where A is time x 1 and B is voxels x conditions (flattened format).
0661 % <hrffitvoxels> is [] unless hrfmodel is 'optimize', in which case it will be
0662 %   a column vector of voxel indices.
0663 %
0664 % note: cache.rawdesign will exist for 'fir' and 'assume' but not 'optimize'.
0665 
0666 % internal constants
0667 minR2 = 99;  % in 'optimize' mode, if R^2 between previous HRF and new HRF
0668              % is above this threshold (and we have at least gone through
0669              % one complete round of fitting (just so that we can change
0670              % a little from the initial seed)), then we stop fitting.
0671 
0672 % init
0673 hrffitvoxels = [];
0674 
0675 switch hrfmodel
0676 case 'fir'
0677   
0678   % since 'fir', we can assume design is not the onset case, but check it
0679   assert(~iscell(design{1}));
0680   
0681   % calc
0682   numconditions = size(design{1},2);
0683   
0684   % prepare design matrix
0685   for p=1:length(design)
0686     
0687     % expand original design matrix using delta basis functions.
0688     % the length of each timecourse is L.
0689     design{p} = constructstimulusmatrices(full(design{p})',0,hrfknobs,0);  % time x L*conditions
0690     
0691     % save a record of the raw design matrix
0692     cache.rawdesign{p} = design{p};
0693     
0694     % remove polynomials and extra regressors
0695     design{p} = combinedmatrix{p}*design{p};  % time x L*conditions
0696 
0697   end
0698   
0699   % fit model
0700   f = mtimescell(olsmatrix2(cat(1,design{:})),data2);  % L*conditions x voxels
0701   f = permute(reshape(f,hrfknobs+1,numconditions,[]),[3 2 1]);  % voxels x conditions x L
0702 
0703 case 'assume'
0704 
0705   % prepare design matrix
0706   for p=1:length(design)
0707   
0708     % if onset-time case
0709     if iscell(design{p})
0710     
0711       % calc
0712       alltimes = linspacefixeddiff(0,tr,size(data2{p},1));
0713       hrftimes = linspacefixeddiff(0,tr,length(hrfknobs));
0714   
0715       % loop over conditions
0716       temp = zeros(size(data2{p},1),length(design{p}));  % this will be time x conditions
0717       for q=1:length(design{p})
0718 
0719         % onset times for qth condition in run p
0720         otimes = design{p}{q};
0721     
0722         % intialize
0723         yvals = 0;
0724     
0725         % loop over onset times
0726         for r=1:length(otimes)
0727         
0728           % interpolate to find values at the data sampling time points
0729           yvals = yvals + interp1(otimes(r) + hrftimes,hrfknobs',alltimes,'pchip',0);
0730 
0731         end
0732 
0733         % record
0734         temp(:,q) = yvals;
0735 
0736       end
0737 
0738       % save a record of the raw design matrix
0739       cache.rawdesign{p} = temp;
0740       
0741       % remove polynomials and extra regressors
0742       design{p} = combinedmatrix{p}*temp;  % time x conditions
0743     
0744     % if regular matrix case
0745     else
0746     
0747       % convolve original design matrix with HRF
0748       ntime = size(design{p},1);                    % number of time points
0749       design{p} = conv2(full(design{p}),hrfknobs);  % convolve
0750       design{p} = design{p}(1:ntime,:);             % extract desired subset
0751 
0752       % save a record of the raw design matrix
0753       cache.rawdesign{p} = design{p};
0754     
0755       % remove polynomials and extra regressors
0756       design{p} = combinedmatrix{p}*design{p};  % time x conditions
0757 
0758     end
0759 
0760   end
0761   
0762   % fit model
0763   f = mtimescell(olsmatrix2(cat(1,design{:})),data2);  % conditions x voxels
0764   f = {hrfknobs f'};
0765 
0766 case 'optimize'
0767 
0768   % since 'optimize', we can assume design is not the onset case, but check it
0769   assert(~iscell(design{1}));
0770 
0771   % calc
0772   numinhrf = length(hrfknobs);
0773   numcond = size(design{1},2);
0774   
0775   % if cache is empty, fill it
0776   if isempty(cache)
0777 
0778     % precompute for speed
0779     design2pre = {};
0780     for p=1:length(design)
0781       
0782       % expand design matrix using delta functions
0783       ntime = size(design{p},1);              % number of time points
0784       design2pre{p} = constructstimulusmatrices(full(design{p})',0,numinhrf-1,0);  % time x L*conditions
0785       design2pre{p} = reshape(design2pre{p},[],numcond);  % time*L x conditions
0786   
0787     end
0788     
0789     % record it
0790     cache.design2pre = design2pre;
0791 
0792   % otherwise, use the cache
0793   else
0794     design2pre = cache.design2pre;
0795   end
0796 
0797   % loop until convergence
0798   currenthrf = hrfknobs;  % initialize
0799   cnt = 1;
0800   while 1
0801   
0802     % fix the HRF, estimate the amplitudes
0803     if mod(cnt,2)==1
0804 
0805       % prepare design matrix
0806       design2 = {};
0807       for p=1:length(design)
0808         
0809         % convolve original design matrix with HRF
0810         ntime = size(design{p},1);                       % number of time points
0811         design2{p} = conv2(full(design{p}),currenthrf);  % convolve
0812         design2{p} = design2{p}(1:ntime,:);              % extract desired subset
0813         
0814         % remove polynomials and extra regressors
0815         design2{p} = combinedmatrix{p}*design2{p};  % time x conditions
0816     
0817       end
0818 
0819       % estimate the amplitudes
0820       currentbeta = mtimescell(olsmatrix2(cat(1,design2{:})),data2);  % conditions x voxels
0821 
0822       % calculate R^2
0823       modelfit = cellfun(@(x) x*currentbeta,design2,'UniformOutput',0);
0824       R2 = calccodcell(modelfit,data2,1)';
0825       clear modelfit;
0826     
0827       % figure out indices of good voxels
0828       if isequal(opt.hrffitmask,1)
0829         temp = R2;
0830       else
0831         temp = copymatrix(R2,~opt.hrffitmask(:),-Inf);  % shove -Inf in where invalid
0832       end
0833       temp = nanreplace(temp,-Inf);
0834       [dd,ii] = sort(temp);
0835       iichosen = ii(max(1,end-opt.numforhrf+1):end);
0836       iichosen = setdiff(iichosen,iichosen(temp(iichosen)==-Inf));
0837       hrffitvoxels = iichosen;
0838    
0839     % fix the amplitudes, estimate the HRF
0840     else
0841 
0842       % prepare design matrix
0843       design2 = {};
0844       for p=1:length(design)
0845         
0846         % calc
0847         ntime = size(design{p},1);              % number of time points
0848         
0849         % weight and sum based on the current amplitude estimates.  only include the good voxels.
0850         design2{p} = design2pre{p} * currentbeta(:,hrffitvoxels);  % time*L x voxels
0851         
0852         % remove polynomials and extra regressors
0853         design2{p} = reshape(design2{p},ntime,[]);  % time x L*voxels
0854         design2{p} = combinedmatrix{p}*design2{p};  % time x L*voxels
0855         design2{p} = permute(reshape(design2{p},ntime,numinhrf,[]),[1 3 2]);  % time x voxels x L
0856     
0857       end
0858       
0859       % estimate the HRF
0860       previoushrf = currenthrf;
0861       datasubset = cellfun(@(x) x(:,hrffitvoxels),data2,'UniformOutput',0);
0862         prev = warning('query'); warning off;
0863       currenthrf = olsmatrix2(squish(cat(1,design2{:}),2)) * vflatten(cat(1,datasubset{:}));
0864         warning(prev);
0865 
0866       % if HRF is all zeros (this can happen when the data are all zeros), get out prematurely
0867       if all(currenthrf==0)
0868         break;
0869       end
0870 
0871       % check for convergence
0872       if calccod(previoushrf,currenthrf,[],0,0) >= minR2 && cnt > 2
0873         break;
0874       end
0875 
0876     end
0877     
0878     cnt = cnt + 1;
0879   
0880   end
0881   
0882   % sanity check
0883   if calccod(hrfknobs,previoushrf,[],0,0) < opt.hrfthresh
0884     warning('Global HRF estimate is far from the initial seed, probably indicating low SNR.  We are just going to use the initial seed as the HRF estimate.');
0885     [f,hrffitvoxels] = fitmodel_helper(design,data2,tr,'assume',hrfknobs,opt,combinedmatrix,dimdata,dimtime,[]);
0886     return;
0887   end
0888 
0889   % normalize results
0890   mx = max(previoushrf);
0891   previoushrf = previoushrf / mx;
0892   currentbeta = currentbeta * mx;
0893   
0894   % return
0895   f = {previoushrf currentbeta'};
0896 
0897 end

Generated on Fri 01-Aug-2014 12:03:17 by m2html © 2005