Home > GLMdenoise > GLMdenoisedata.m

GLMdenoisedata

PURPOSE ^

function [results,denoiseddata] = GLMdenoisedata(design,data,stimdur,tr,hrfmodel,hrfknobs,opt,figuredir)

SYNOPSIS ^

function [results,denoiseddata] = GLMdenoisedata(design,data,stimdur,tr,hrfmodel,hrfknobs,opt,figuredir)

DESCRIPTION ^

 function [results,denoiseddata] = GLMdenoisedata(design,data,stimdur,tr,hrfmodel,hrfknobs,opt,figuredir)

 <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'.
   Because this function involves cross-validation across runs, there must 
   be at least two runs in <design>.  
 <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> (optional) 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
   Default: 'optimize'.
 <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>.
 <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.
   <brainthresh> (optional) [A B] where A is a percentile for voxel intensity 
     values and B is a fraction to apply to the percentile.  These parameters
     are used in the selection of the noise pool.  Default: [99 0.5].
   <brainR2> (optional) is an R^2 value (percentage).  Voxels whose 
     cross-validation accuracy is below this value are allowed to enter 
     the noise pool.  Default: 0.
   <brainexclude> (optional) is X x Y x Z with 1s indicating voxels to 
     exclude when selecting the noise pool.  Special case is 0 which means 
     all voxels can be potentially chosen.  Default: 0.
   <numpcstotry> (optional) is a non-negative integer indicating the maximum
     number of PCs to enter into the model.  Default: 20.
   <pcR2cutoff> (optional) is an R^2 value (percentage).  To decide the number
     of PCs to include, we examine a subset of the available voxels.
     Specifically, we examine voxels whose cross-validation accuracy is above 
     <pcR2cutoff> for any of the numbers of PCs.  Default: 0.
   <pcR2cutoffmask> (optional) is X x Y x Z with 1s indicating all possible
     voxels to consider when selecting the subset of voxels.  Special case is
     1 which means all voxels can be potentially selected.  Default: 1.
   <pcstop> (optional) is
     A: a number greater than or equal to 1 indicating when to stop adding PCs 
        into the model.  For example, 1.05 means that if the cross-validation 
        performance with the current number of PCs is within 5% of the maximum 
        observed, then use that number of PCs.  (Performance is measured 
        relative to the case of 0 PCs.)  When <pcstop> is 1, the selection 
        strategy reduces to simply choosing the PC number that achieves
        the maximum.  The advantage of stopping early is to achieve a selection
        strategy that is robust to noise and shallow performance curves and 
        that avoids overfitting.
    -B: where B is the number of PCs to use for the final model (thus, the user
        chooses).  B can be any integer between 0 and opt.numpcstotry.
     Default: 1.05.
   <pccontrolmode> (optional) is for testing purposes.  Default is 0 which means
     to do nothing special.  If 1, then after we are done constructing the global
     noise regressors, we scramble the phase spectra of these regressors (prior
     to entering them into the GLM).  If 2, then after we are done constructing the
     noise regressors, we shuffle the assignment of noise regressors
     to the runs, ensuring that each run is assigned a new set of regressors.  Note that
     in this shuffling, the grouping of regressors (at the run level) is maintained.
     The shuffling is performed prior to entering noise regressors into the GLM.
   <numboots> (optional) is a positive integer indicating the number of 
     bootstraps to perform for the final model.  Special case is 0 which
     indicates that the final model should just be fit to the complete
     set of data (and not bootstrapped). Default: 100.
   <denoisespec> (optional) is a binary string or cell vector of binary strings
     indicating the components of the data to return in <denoiseddata>.  The 
     format of each string should be 'ABCDE' where A indicates whether to include 
     the signal (estimated hemodynamic responses evoked by the experiment), B 
     indicates whether to include the polynomial drift, C indicates whether
     to include any extra regressors provided by the user, D indicates 
     whether to include the noise regressors, and E indicates whether
     to include the residuals of the model.  If multiple strings are provided,
     then separate copies of the data will be returned in the rows of 
     <denoiseddata>.  Default: '11101' which indicates that all components of 
     the data will be returned except for the component corresponding to the 
     estimate of the contribution of the noise regressors.
   <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.
   <noisepooldirect> (optional) is {A B} where A is X x Y x Z with 1s indicating the 
     voxels to use as the noise pool and B is a non-negative integer indicating the 
     number of PCs to use.  (B must be less than or equal to <numpcstotry>.)
     If <noisepooldirect> is supplied, this causes much of the standard GLMdenoise 
     procedure to be bypassed.  For example, cross-validation is no longer necessary 
     and therefore no longer performed.  Thus, one benefit of using <noisepooldirect>
     is that you can apply GLMdenoisedata.m to a single run of data.  Note that if 
     <noisepooldirect> is supplied, various inputs no longer have any effect 
     (e.g., <brainthresh>, <brainR2>, <brainexclude>, <pcR2cutoff>, <pcR2cutoffmask>, 
     and <pcstop>) and various outputs and figures are omittted.  Default is [] which 
     means to perform the usual GLMdenoise procedure.
   <wantparametric> (optional) is whether to compute parametric GLM fits and associated
     error estimates.  These are added as additional outputs in the <results> struct.
     Default: 0.
   <wantsanityfigures> (optional) is whether to write out figures that allow you
     to sanity-check the data.  You may want to set this to 0 to save computational
     time.  Default: 1.
 <figuredir> (optional) is a directory to which to write figures.  (If the
   directory does not exist, we create it; if the directory already exists,
   we delete its contents so we can start afresh.)  If [], no figures are
   written.  If not supplied, default to 'GLMdenoisefigures' (in the current 
   directory).

 Based on the experimental design (<design>, <stimdur>, <tr>) and the 
 model specification (<hrfmodel>, <hrfknobs>), fit a GLM model to the 
 data (<data>, <xyzsize>) using a denoising strategy.  Figures 
 illustrating the results are written to <figuredir>.

 Return <results> as a struct containing the following fields:
 <models>, <modelmd>, <modelse>, <R2>, <R2run>, <signal>, <noise>, 
   <SNR>, and <hrffitvoxels> are all like the output of GLMestimatemodel.m 
   (please see that function for details).
 <hrffitvoxels> is X x Y x Z with logical values indicating the voxels that
   were used to fit the global HRF.  (If <hrfmodel> is not 'optimize',
   this is returned as [].)
 <meanvol> is X x Y x Z with the mean of all volumes
 <noisepool> is X x Y x Z with logical values indicating the voxels that
   were selected for the noise pool.
 <pcregressors> indicates the noise regressors that were used
   to denoise the data.  The format is a cell vector of elements that 
   are each time x regressors.  The number of regressors will be equal 
   to opt.numpcstotry and they are in order (the first PC is the first
   regressor, the second PC is the second regressor, etc.).  Note that
   only the first <pcnum> PCs are actually used for the final model.
 <pcR2> is X x Y x Z x (1+opt.numpcstotry) with cross-validated R^2 values for
   different numbers of PCs.  The first slice corresponds to 0 PCs, the
   second slice corresponds to 1 PC, the third slice corresponds to
   2 PCs, etc.
 <pcR2final> is X x Y x Z with cross-validated R^2 values for the final
   model that is chosen.  Note that this is simply pcR2(:,:,:,1+pcnum).
 <pcvoxels> is X x Y x Z with logical values indicating the voxels that
   were used to select the number of PCs.
 <pcnum> is the number of PCs that were selected for the final model.
 <pcweights> is X x Y x Z x <pcnum> x R with the estimated weights on the 
   PCs for each voxel and run.
 <SNRbefore> is X x Y x Z with signal-to-noise ratios before denoising
   (using no noise regressors).
 <SNRafter> is X x Y x Z with signal-to-noise ratios after denoising
   (using the final number of noise regressors).
 <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>.
 <parametric> is a struct that is included if opt.wantparametric.  The fields are:
   <designmatrix> is time x regressors with the full design matrix of the GLM
   <parameters> is X x Y x Z x regressors with the beta weight estimate for
     each regressor
   <parametersse> is X x Y x Z x regressors with the standard error on the beta weights
   <noisevar> is X x Y x Z with the estimate of the noise variance (sigma squared)
   Note that these are in raw units and are not converted into % BOLD change.
 
 Also return <denoiseddata>, which is just like <data> except that the 
 component of the data that is estimated to be due to the noise regressors
 is subtracted off.  This may be useful in situations where one wishes to
 treat the denoising as a pre-processing step prior to other analyses 
 of the time-series data.  Further customization of the contents of
 <denoiseddata> is controlled by opt.denoisespec.  If you do not need
 <denoiseddata>, do not assign the <denoiseddata> output when you call 
 GLMdenoisedata.m (this allows us to save on memory usage).

 Description of the denoising procedure:
 1. Determine HRF.  If <hrfmodel> is 'assume', we just use the HRF
    specified by the user.  If <hrfmodel> is 'optimize', we perform
    a full fit of the GLM model to the data, optimizing the shape of
    the HRF.  If <hrfmodel> is 'fir', we do nothing (since full 
    flexibility in the HRF is allowed for each voxel and each condition).
 2. Determine cross-validated R^2 values.  We fix the HRF to what is
    obtained in step 1 and estimate the rest of the GLM model.  Leave-one-
    run-out cross-validation is performed, and we obtain an estimate of the
    amount of variance (R^2) that can be predicted by the deterministic 
    portion of the GLM model (the HRF and the amplitudes).
 3. Determine noise pool.  This is done by calculating a mean volume (the 
    mean across all volumes) and then determining the voxels that
    satisfy the following criteria:
    (1) The voxels must have sufficient MR signal, that is, the signal
        intensity in the mean volume must be above a certain threshold
        (see opt.brainthresh).
    (2) The voxels must have cross-validated R^2 values that are 
        below a certain threshold (see opt.brainR2).
    (3) The voxels must not be listed in opt.brainexclude (which is an
        optional input that can be specified by the user).
 4. Determine noise regressors.  This is done by extracting the 
    time-series data for the voxels in the noise pool, projecting out the
    polynomial nuisance functions from each time-series, normalizing each
    time-series to be unit length, and then performing PCA.  The top N
    PCs from each run (where N is equal to opt.numpcstotry) are selected
    as noise regressors.  Each regressor is scaled to have a standard
    deviation of 1; this makes it easier to interpret the weights estimated
    for the regressors.
 5. Evaluate different numbers of PCs using cross-validation.  We refit
    the GLM model to the data (keeping the HRF fixed), systematically varying 
    the number of PCs from 1 to N.  For each number of PCs, leave-one-run-out 
    cross-validation is performed.  (Recall that only the deterministic
    portion of the model is cross-validated; thus, any changes in R^2
    directly reflect changes in the quality of the amplitude estimates.)
 6. Choose optimal number of PCs.  To choose the optimal number of PCs,
    we select a subset of voxels (namely, any voxel that has a cross-validated
    R^2 value greater than opt.pcR2cutoff (default: 0%) in any of the cases
    being considered) and then compute the median cross-validated R^2 for these 
    voxels for different numbers of PCs.  Starting from 0 PCs, we select the 
    number of PCs that achieves a cross-validation accuracy within opt.pcstop of 
    the maximum.  (The default for opt.pcstop is 1.05, which means that the
    chosen number of PCs will be within 5% of the maximum.)
 7. Fit the final model.  We fit the final GLM model (with the HRF fixed to 
    that obtained in step 1 and with the number of PCs selected in step 6) 
    to the data.  Bootstrapping is used to estimate error bars on 
    amplitude estimates.  (We also fit the final model with no PCs so that
    we can compare the SNR before and after denoising.)
 8. Return the de-noised data.  We calculate the component of the data that 
    is due to the noise regressors and return the original time-series 
    data with this component subtracted off.  Note that the other components of
    the model (the hemodynamic responses evoked by the experiment, 
    the polynomial drift, any extra regressors provided by the user, 
    model residuals) remain in the de-noised data.  To change this behavior, 
    please see the input opt.denoisespec.

 Figures:
 - "CheckData/CheckMeanStd_runN.png" shows the mean and standard deviation across voxels
   of each volume in run N.  This allows one to quickly check the sanity of the data, 
   e.g., with regards to whether weird transient artifacts exist, whether initial 
   magnetization effects are present in the data (the first few volumes should have
   been dropped to avoid these effects), whether there are non-finite values in the 
   data (there should not be any), and with regards to the units of the data 
   (the data should consist primarily of positive values and in particular, should
   not be mean-subtracted).
 - "CheckData/CheckDVARS_runN.png" shows the DVARS metric (Power 2012).  Specifically,
   this is a time-series of the root-mean-square of the difference between pairs of
   successive volumes.
 - "HRF.png" shows the initial assumed HRF (provided by <hrfknobs>) and the
   final estimated HRF (as calculated in step 1).  If <hrfmodel> is 'assume',
   the two plots will be identical.  If <hrfmodel> is 'fir', this figure
   is not written.
 - "HRFfitmask.png" shows (in white) the mask restricting the voxels that
   can be chosen for fitting the global HRF.  This figure is written only
   if <hrfmodel> is 'optimize' and is not written if opt.hrffitmask is 1.
 - "HRFfitvoxels.png" shows (in white) the voxels used to fit the global HRF.
   This figure is written only if <hrfmodel> is 'optimize' and only if
   the fitted global HRF is actually chosen for use (in some cases, the
   initial HRF estimate is chosen; see GLMestimatemodel.m).
 - "PCselection.png" shows for different numbers of PCs, the median 
   cross-validated R^2 across a subset of voxels (namely, those voxels that 
   have greater than opt.pcR2cutoff (default: 0%) R^2 for at least one of 
   the models considered).  The selected number of PCs is circled and 
   indicated in the title of the figure.
 - "PCscatterN.png" shows a scatter plot of cross-validated R^2 values obtained
   with no PCs against values obtained with N PCs.  The range of the plot
   is set to the full range of all R^2 values (across all numbers of PCs).
   Two different sets of points are plotted.  The first set is shown in green,
   and this is a set of up to 20,000 voxels randomly selected from the
   entire pool of voxels.  The second set is shown in red, and this is a set of
   up to 20,000 voxels randomly selected from the set of voxels that
   were used to select the number of PC regressors.
 - "MeanVolume.png" shows the mean volume (mean of all volumes).
 - "NoisePool.png" shows (in white) the voxels selected for the noise pool.
 - "NoiseExclude.png" shows (in white) voxels that were excluded by the user
   from entering the noise pool.  This figure is not written if opt.brainexclude is 0.
 - "PCcrossvalidationN.png" shows cross-validated R^2 values obtained with N PCs.
   The range is 0% to 100%, and the colormap is nonlinearly scaled to enhance
   visibility.
 - "PCcrossvalidationscaledN.png" shows cross-validated R^2 values obtained with N PCs.
   This is just like the previous figures except that the minimum and maximum of the
   color range is set to the 1st and 99th percentile of R^2 values observed across all 
   PCs.  The point of this is to make it easier to visualize datasets where R^2 values
   are low.  The disadvantage is that these figures cannot be directly compared across 
   different datasets (since the color range may differ).
 - "PCmask.png" shows (in white) the mask restricting the voxels that
   can be selected for determining the optimal number of PCs.  This figure is 
   not written if opt.pcR2cutoffmask is 1.
 - "PCvoxels.png" shows (in white) the voxels used to determine the optimal
   number of PCs.
 - "FinalModel.png" shows R^2 values for the final model (as estimated in
   step 7).  Note that these R^2 values are not cross-validated.
 - "FinalModel_runN.png" shows R^2 values for the final model separated by 
   runs.  For example, FinalModel_run01.png indicates R^2 values calculated
   over the data in run 1.  This might be useful for deciding post-hoc to
   exclude certain runs from the analysis.
 - "SNRsignal.png" shows the maximum absolute amplitude obtained (the signal).
   The range is 0 to the 99th percentile of the values.
 - "SNRnoise.png" shows the average amplitude error (the noise).
   The range is 0 to the 99th percentile of the values.
 - "SNR.png" shows the signal-to-noise ratio.  The range is 0 to 10.
 - "SNRcomparebeforeandafter.png" shows a scatter plot of SNR values obtained
   with no denoising (0 PCs) against SNR values obtained with denoising (with
   the selected number of PCs).  The range of the plot is set to the full range
   of SNR values.  The green and red coloring of the dots is the same as in the
   "PCscatterN.png" figures.
 - "SNRamountofdatagained.png" is exactly the same as the previous figure except
   that the y-axis is now converted into the equivalent amount of data gained.  
   For example, if SNR is boosted from 4 to 8, this is equivalent to having 
   obtained 300% more data than was actually obtained.
 - "PCmap/PCmap_runN_numO.png" shows the estimated weights for the Oth PC
   for run N.  The range is -A to A where A is the 99th percentile of the
   absolute value of all weights across all runs.  The colormap proceeds
   from blue (negative) to black (0) to red (positive).

 Additional information:
 - For additional details on model estimation and quantification of model
 accuracy (R^2), please see the documentation provided in GLMestimatemodel.m.
 - To ensure that <SNRbefore> and <SNRafter> are directly comparable, we first
 average the estimated signal across the no-denoise and denoise cases
 and then divide by the estimated noise from each case.  This has the 
 consequence that <SNRafter> will not be exactly identical to <SNR>.  
 - With regards to the onset-time specification of the experimental design,
 this is implemented by laying down the HRF at the appropriate onset times
 and then using cubic interpolation to obtain the predicted HRF values at the
 times at which data are actually sampled.

 History:
 - 2014/08/01: add opt.wantparametric input (which enables parametric GLM fits).
               add opt.wantsanityfigures input (which allows user to turn off
               the sanity-check figures).  add new DVARS sanity-check figures.
               change the form of the polynomials used in the GLM: the
               polynomials are now constructed to be orthogonal and unit-length.
               note that this changes previous behavior and due to numerical
               issues, the outputs given by this function may be slightly
               different compared to previous results.
 - 2014/07/14: add <noisepooldirect> input; fix some file- and directory-related
               issues, making things more platform independent
 - 2013/12/11: rename "global noise regressors" to "noise regressors" in the
               documentation; perform a quick error-check for non-finite values
               in the first run of data; omit saving of some of the figures if 
               opt.numboots is 0; add some new sanity-check figures; fix minor bug
 - 2013/11/18: update documentation; add opt.pccontrolmode; add opt.hrfthresh;
               use fullfile for compatibility with different platforms;
               if pcvoxels is empty, we now fallback to using the top 100 voxels;
               change the colormap scaling for PCcrossvalidationscaled*.png;
               other various small tweaks;
 - 2013/05/12: allow <design> to specify onset times
 - 2013/05/12: add opt.brainexclude and associated figure
 - 2013/05/12: add SNRbefore and SNRafter fields and associated figures
 - 2013/05/12: add PCcrossvalidationscaledN.png figures
 - 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: add pcR2final as an output
 - 2012/12/06: automatically convert data to single format
 - 2012/12/05: fix minor bug (bad HRF estimate caused figure crash)
 - 2012/12/03: *** Tag: Version 1.02 ***. Use faster OLS computation (less
   error-checking; program execution will halt if design matrix is singular);
   documentation tweak; minor bug fix.
 - 2012/11/24:
   - INPUTS: add opt.hrffitmask; opt.pcR2cutoff; opt.pcR2cutoffmask; opt.pcstop; opt.denoisespec; opt.wantpercentbold;
   - OUTPUTS: add hrffitvoxels, pcvoxels, pcweights, inputs
   - FIGURES: HRFfitmask.png, HRFfitvoxels.png, PCmask.png, PCvoxels.png, Signal.png, Noise.png, SNR.png, PCmap*.png
   - hrfmodel can now be 'fir'!
   - change default of opt.numpcstotry to 20
   - PC regressors are now scaled to have standard deviation of 1
   - xval scatter plots now are divided into red and green dots (and up to 20,000 each)
   - pcselection figure uses a line now (not bar) and a selection circle is drawn
   - no more skipping of the denoiseddata computation
 - 2012/11/03 - add a speed-up
 - 2012/11/02 - Initial version.
 - 2012/10/31 - add meanvol and change that it is the mean of all
 - 2012/10/30 - Automatic division of HRF!

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SOURCE CODE ^

0001 function [results,denoiseddata] = GLMdenoisedata(design,data,stimdur,tr,hrfmodel,hrfknobs,opt,figuredir)
0002 
0003 % function [results,denoiseddata] = GLMdenoisedata(design,data,stimdur,tr,hrfmodel,hrfknobs,opt,figuredir)
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 %   Because this function involves cross-validation across runs, there must
0017 %   be at least two runs in <design>.
0018 % <data> is the time-series data with dimensions X x Y x Z x time or a cell vector of
0019 %   elements that are each X x Y x Z x time.  XYZ can be collapsed such that the data
0020 %   are given as a 2D matrix (XYZ x time); however, if you do this, then several of the
0021 %   figures that are written out by this function will not be useful to look at.  The
0022 %   dimensions of <data> should mirror that of <design>.  (For example, <design> and
0023 %   <data> should have the same number of runs, the same number of time points, etc.
0024 %   Thus, <data> should have at least two runs since <design> must have at least two
0025 %   runs.)  <data> should not contain any NaNs. We automatically convert <data> to
0026 %   single format (if necessary).
0027 % <stimdur> is the duration of a trial in seconds
0028 % <tr> is the sampling rate in seconds
0029 % <hrfmodel> (optional) indicates the type of model to use for the HRF:
0030 %   'fir' indicates a finite impulse response model (a separate timecourse
0031 %     is estimated for every voxel and every condition)
0032 %   'assume' indicates that the HRF is provided (see <hrfknobs>)
0033 %   'optimize' indicates that we should estimate a global HRF from the data
0034 %   Default: 'optimize'.
0035 % <hrfknobs> (optional) is as follows:
0036 %   if <hrfmodel> is 'fir', then <hrfknobs> should be the number of
0037 %     time points in the future to model (N >= 0).  For example, if N is 10,
0038 %     then timecourses will consist of 11 points, with the first point
0039 %     coinciding with condition onset.
0040 %   if <hrfmodel> is 'assume', then <hrfknobs> should be time x 1 with
0041 %     the HRF to assume.
0042 %   if <hrfmodel> is 'optimize', then <hrfknobs> should be time x 1 with the
0043 %     initial seed for the HRF.  The length of this vector indicates the
0044 %     number of time points that we will attempt to estimate in the HRF.
0045 %   Note on normalization:  In the case that <hrfmodel> is 'assume' or
0046 %   'optimize', we automatically divide <hrfknobs> by the maximum value
0047 %   so that the peak is equal to 1.  And if <hrfmodel> is 'optimize',
0048 %   then after fitting the HRF, we again normalize the HRF to peak at 1
0049 %   (and adjust amplitudes accordingly).  Default in the case of 'fir' is
0050 %   20.  Default in the case of 'assume' and 'optimize' is to use a
0051 %   canonical HRF that is calculated based on <stimdur> and <tr>.
0052 % <opt> (optional) is a struct with the following fields:
0053 %   <extraregressors> (optional) is time x regressors or a cell vector
0054 %     of elements that are each time x regressors.  The dimensions of
0055 %     <extraregressors> should mirror that of <design> (i.e. same number of
0056 %     runs, same number of time points).  The number of extra regressors
0057 %     does not have to be the same across runs, and each run can have zero
0058 %     or more extra regressors.  If [] or not supplied, we do
0059 %     not use extra regressors in the model.
0060 %   <maxpolydeg> (optional) is a non-negative integer with the maximum
0061 %     polynomial degree to use for polynomial nuisance functions, which
0062 %     are used to capture low-frequency noise fluctuations in each run.
0063 %     Can be a vector with length equal to the number of runs (this
0064 %     allows you to specify different degrees for different runs).
0065 %     Default is to use round(L/2) for each run where L is the
0066 %     duration in minutes of a given run.
0067 %   <seed> (optional) is the random number seed to use (this affects
0068 %     the selection of bootstrap samples). Default: sum(100*clock).
0069 %   <bootgroups> (optional) is a vector of positive integers indicating
0070 %     the grouping of runs to use when bootstrapping.  For example,
0071 %     a grouping of [1 1 1 2 2 2] means that of the six samples that are
0072 %     drawn, three samples will be drawn (with replacement) from the first
0073 %     three runs and three samples will be drawn (with replacement) from
0074 %     the second three runs.  This functionality is useful in situations
0075 %     where different runs involve different conditions.  Default: ones(1,D)
0076 %     where D is the number of runs.
0077 %   <numforhrf> (optional) is a positive integer indicating the number
0078 %     of voxels (with the best R^2 values) to consider in fitting the
0079 %     global HRF.  This input matters only when <hrfmodel> is 'optimize'.
0080 %     Default: 50.  (If there are fewer than that number of voxels
0081 %     available, we just use the voxels that are available.)
0082 %   <hrffitmask> (optional) is X x Y x Z with 1s indicating all possible
0083 %     voxels to consider for fitting the global HRF.  This input matters
0084 %     only when <hrfmodel> is 'optimize'.  Special case is 1 which means
0085 %     all voxels can be potentially chosen.  Default: 1.
0086 %   <brainthresh> (optional) [A B] where A is a percentile for voxel intensity
0087 %     values and B is a fraction to apply to the percentile.  These parameters
0088 %     are used in the selection of the noise pool.  Default: [99 0.5].
0089 %   <brainR2> (optional) is an R^2 value (percentage).  Voxels whose
0090 %     cross-validation accuracy is below this value are allowed to enter
0091 %     the noise pool.  Default: 0.
0092 %   <brainexclude> (optional) is X x Y x Z with 1s indicating voxels to
0093 %     exclude when selecting the noise pool.  Special case is 0 which means
0094 %     all voxels can be potentially chosen.  Default: 0.
0095 %   <numpcstotry> (optional) is a non-negative integer indicating the maximum
0096 %     number of PCs to enter into the model.  Default: 20.
0097 %   <pcR2cutoff> (optional) is an R^2 value (percentage).  To decide the number
0098 %     of PCs to include, we examine a subset of the available voxels.
0099 %     Specifically, we examine voxels whose cross-validation accuracy is above
0100 %     <pcR2cutoff> for any of the numbers of PCs.  Default: 0.
0101 %   <pcR2cutoffmask> (optional) is X x Y x Z with 1s indicating all possible
0102 %     voxels to consider when selecting the subset of voxels.  Special case is
0103 %     1 which means all voxels can be potentially selected.  Default: 1.
0104 %   <pcstop> (optional) is
0105 %     A: a number greater than or equal to 1 indicating when to stop adding PCs
0106 %        into the model.  For example, 1.05 means that if the cross-validation
0107 %        performance with the current number of PCs is within 5% of the maximum
0108 %        observed, then use that number of PCs.  (Performance is measured
0109 %        relative to the case of 0 PCs.)  When <pcstop> is 1, the selection
0110 %        strategy reduces to simply choosing the PC number that achieves
0111 %        the maximum.  The advantage of stopping early is to achieve a selection
0112 %        strategy that is robust to noise and shallow performance curves and
0113 %        that avoids overfitting.
0114 %    -B: where B is the number of PCs to use for the final model (thus, the user
0115 %        chooses).  B can be any integer between 0 and opt.numpcstotry.
0116 %     Default: 1.05.
0117 %   <pccontrolmode> (optional) is for testing purposes.  Default is 0 which means
0118 %     to do nothing special.  If 1, then after we are done constructing the global
0119 %     noise regressors, we scramble the phase spectra of these regressors (prior
0120 %     to entering them into the GLM).  If 2, then after we are done constructing the
0121 %     noise regressors, we shuffle the assignment of noise regressors
0122 %     to the runs, ensuring that each run is assigned a new set of regressors.  Note that
0123 %     in this shuffling, the grouping of regressors (at the run level) is maintained.
0124 %     The shuffling is performed prior to entering noise regressors into the GLM.
0125 %   <numboots> (optional) is a positive integer indicating the number of
0126 %     bootstraps to perform for the final model.  Special case is 0 which
0127 %     indicates that the final model should just be fit to the complete
0128 %     set of data (and not bootstrapped). Default: 100.
0129 %   <denoisespec> (optional) is a binary string or cell vector of binary strings
0130 %     indicating the components of the data to return in <denoiseddata>.  The
0131 %     format of each string should be 'ABCDE' where A indicates whether to include
0132 %     the signal (estimated hemodynamic responses evoked by the experiment), B
0133 %     indicates whether to include the polynomial drift, C indicates whether
0134 %     to include any extra regressors provided by the user, D indicates
0135 %     whether to include the noise regressors, and E indicates whether
0136 %     to include the residuals of the model.  If multiple strings are provided,
0137 %     then separate copies of the data will be returned in the rows of
0138 %     <denoiseddata>.  Default: '11101' which indicates that all components of
0139 %     the data will be returned except for the component corresponding to the
0140 %     estimate of the contribution of the noise regressors.
0141 %   <wantpercentbold> (optional) is whether to convert the amplitude estimates
0142 %     in 'models', 'modelmd', and 'modelse' to percent BOLD change.  This is
0143 %     done as the very last step, and is accomplished by dividing by the
0144 %     absolute value of 'meanvol' and multiplying by 100.  (The absolute
0145 %     value prevents negative values in 'meanvol' from flipping the sign.)
0146 %     Default: 1.
0147 %   <hrfthresh> (optional) is an R^2 threshold.  If the R^2 between the estimated
0148 %     HRF and the initial HRF is less than <hrfthresh>, we decide to just use
0149 %     the initial HRF.  Set <hrfthresh> to -Inf if you never want to reject
0150 %     the estimated HRF.  Default: 50.
0151 %   <noisepooldirect> (optional) is {A B} where A is X x Y x Z with 1s indicating the
0152 %     voxels to use as the noise pool and B is a non-negative integer indicating the
0153 %     number of PCs to use.  (B must be less than or equal to <numpcstotry>.)
0154 %     If <noisepooldirect> is supplied, this causes much of the standard GLMdenoise
0155 %     procedure to be bypassed.  For example, cross-validation is no longer necessary
0156 %     and therefore no longer performed.  Thus, one benefit of using <noisepooldirect>
0157 %     is that you can apply GLMdenoisedata.m to a single run of data.  Note that if
0158 %     <noisepooldirect> is supplied, various inputs no longer have any effect
0159 %     (e.g., <brainthresh>, <brainR2>, <brainexclude>, <pcR2cutoff>, <pcR2cutoffmask>,
0160 %     and <pcstop>) and various outputs and figures are omittted.  Default is [] which
0161 %     means to perform the usual GLMdenoise procedure.
0162 %   <wantparametric> (optional) is whether to compute parametric GLM fits and associated
0163 %     error estimates.  These are added as additional outputs in the <results> struct.
0164 %     Default: 0.
0165 %   <wantsanityfigures> (optional) is whether to write out figures that allow you
0166 %     to sanity-check the data.  You may want to set this to 0 to save computational
0167 %     time.  Default: 1.
0168 % <figuredir> (optional) is a directory to which to write figures.  (If the
0169 %   directory does not exist, we create it; if the directory already exists,
0170 %   we delete its contents so we can start afresh.)  If [], no figures are
0171 %   written.  If not supplied, default to 'GLMdenoisefigures' (in the current
0172 %   directory).
0173 %
0174 % Based on the experimental design (<design>, <stimdur>, <tr>) and the
0175 % model specification (<hrfmodel>, <hrfknobs>), fit a GLM model to the
0176 % data (<data>, <xyzsize>) using a denoising strategy.  Figures
0177 % illustrating the results are written to <figuredir>.
0178 %
0179 % Return <results> as a struct containing the following fields:
0180 % <models>, <modelmd>, <modelse>, <R2>, <R2run>, <signal>, <noise>,
0181 %   <SNR>, and <hrffitvoxels> are all like the output of GLMestimatemodel.m
0182 %   (please see that function for details).
0183 % <hrffitvoxels> is X x Y x Z with logical values indicating the voxels that
0184 %   were used to fit the global HRF.  (If <hrfmodel> is not 'optimize',
0185 %   this is returned as [].)
0186 % <meanvol> is X x Y x Z with the mean of all volumes
0187 % <noisepool> is X x Y x Z with logical values indicating the voxels that
0188 %   were selected for the noise pool.
0189 % <pcregressors> indicates the noise regressors that were used
0190 %   to denoise the data.  The format is a cell vector of elements that
0191 %   are each time x regressors.  The number of regressors will be equal
0192 %   to opt.numpcstotry and they are in order (the first PC is the first
0193 %   regressor, the second PC is the second regressor, etc.).  Note that
0194 %   only the first <pcnum> PCs are actually used for the final model.
0195 % <pcR2> is X x Y x Z x (1+opt.numpcstotry) with cross-validated R^2 values for
0196 %   different numbers of PCs.  The first slice corresponds to 0 PCs, the
0197 %   second slice corresponds to 1 PC, the third slice corresponds to
0198 %   2 PCs, etc.
0199 % <pcR2final> is X x Y x Z with cross-validated R^2 values for the final
0200 %   model that is chosen.  Note that this is simply pcR2(:,:,:,1+pcnum).
0201 % <pcvoxels> is X x Y x Z with logical values indicating the voxels that
0202 %   were used to select the number of PCs.
0203 % <pcnum> is the number of PCs that were selected for the final model.
0204 % <pcweights> is X x Y x Z x <pcnum> x R with the estimated weights on the
0205 %   PCs for each voxel and run.
0206 % <SNRbefore> is X x Y x Z with signal-to-noise ratios before denoising
0207 %   (using no noise regressors).
0208 % <SNRafter> is X x Y x Z with signal-to-noise ratios after denoising
0209 %   (using the final number of noise regressors).
0210 % <inputs> is a struct containing all inputs used in the call to this
0211 %   function, excluding <data>.  We additionally include a field called
0212 %   'datasize' which contains the size of each element of <data>.
0213 % <parametric> is a struct that is included if opt.wantparametric.  The fields are:
0214 %   <designmatrix> is time x regressors with the full design matrix of the GLM
0215 %   <parameters> is X x Y x Z x regressors with the beta weight estimate for
0216 %     each regressor
0217 %   <parametersse> is X x Y x Z x regressors with the standard error on the beta weights
0218 %   <noisevar> is X x Y x Z with the estimate of the noise variance (sigma squared)
0219 %   Note that these are in raw units and are not converted into % BOLD change.
0220 %
0221 % Also return <denoiseddata>, which is just like <data> except that the
0222 % component of the data that is estimated to be due to the noise regressors
0223 % is subtracted off.  This may be useful in situations where one wishes to
0224 % treat the denoising as a pre-processing step prior to other analyses
0225 % of the time-series data.  Further customization of the contents of
0226 % <denoiseddata> is controlled by opt.denoisespec.  If you do not need
0227 % <denoiseddata>, do not assign the <denoiseddata> output when you call
0228 % GLMdenoisedata.m (this allows us to save on memory usage).
0229 %
0230 % Description of the denoising procedure:
0231 % 1. Determine HRF.  If <hrfmodel> is 'assume', we just use the HRF
0232 %    specified by the user.  If <hrfmodel> is 'optimize', we perform
0233 %    a full fit of the GLM model to the data, optimizing the shape of
0234 %    the HRF.  If <hrfmodel> is 'fir', we do nothing (since full
0235 %    flexibility in the HRF is allowed for each voxel and each condition).
0236 % 2. Determine cross-validated R^2 values.  We fix the HRF to what is
0237 %    obtained in step 1 and estimate the rest of the GLM model.  Leave-one-
0238 %    run-out cross-validation is performed, and we obtain an estimate of the
0239 %    amount of variance (R^2) that can be predicted by the deterministic
0240 %    portion of the GLM model (the HRF and the amplitudes).
0241 % 3. Determine noise pool.  This is done by calculating a mean volume (the
0242 %    mean across all volumes) and then determining the voxels that
0243 %    satisfy the following criteria:
0244 %    (1) The voxels must have sufficient MR signal, that is, the signal
0245 %        intensity in the mean volume must be above a certain threshold
0246 %        (see opt.brainthresh).
0247 %    (2) The voxels must have cross-validated R^2 values that are
0248 %        below a certain threshold (see opt.brainR2).
0249 %    (3) The voxels must not be listed in opt.brainexclude (which is an
0250 %        optional input that can be specified by the user).
0251 % 4. Determine noise regressors.  This is done by extracting the
0252 %    time-series data for the voxels in the noise pool, projecting out the
0253 %    polynomial nuisance functions from each time-series, normalizing each
0254 %    time-series to be unit length, and then performing PCA.  The top N
0255 %    PCs from each run (where N is equal to opt.numpcstotry) are selected
0256 %    as noise regressors.  Each regressor is scaled to have a standard
0257 %    deviation of 1; this makes it easier to interpret the weights estimated
0258 %    for the regressors.
0259 % 5. Evaluate different numbers of PCs using cross-validation.  We refit
0260 %    the GLM model to the data (keeping the HRF fixed), systematically varying
0261 %    the number of PCs from 1 to N.  For each number of PCs, leave-one-run-out
0262 %    cross-validation is performed.  (Recall that only the deterministic
0263 %    portion of the model is cross-validated; thus, any changes in R^2
0264 %    directly reflect changes in the quality of the amplitude estimates.)
0265 % 6. Choose optimal number of PCs.  To choose the optimal number of PCs,
0266 %    we select a subset of voxels (namely, any voxel that has a cross-validated
0267 %    R^2 value greater than opt.pcR2cutoff (default: 0%) in any of the cases
0268 %    being considered) and then compute the median cross-validated R^2 for these
0269 %    voxels for different numbers of PCs.  Starting from 0 PCs, we select the
0270 %    number of PCs that achieves a cross-validation accuracy within opt.pcstop of
0271 %    the maximum.  (The default for opt.pcstop is 1.05, which means that the
0272 %    chosen number of PCs will be within 5% of the maximum.)
0273 % 7. Fit the final model.  We fit the final GLM model (with the HRF fixed to
0274 %    that obtained in step 1 and with the number of PCs selected in step 6)
0275 %    to the data.  Bootstrapping is used to estimate error bars on
0276 %    amplitude estimates.  (We also fit the final model with no PCs so that
0277 %    we can compare the SNR before and after denoising.)
0278 % 8. Return the de-noised data.  We calculate the component of the data that
0279 %    is due to the noise regressors and return the original time-series
0280 %    data with this component subtracted off.  Note that the other components of
0281 %    the model (the hemodynamic responses evoked by the experiment,
0282 %    the polynomial drift, any extra regressors provided by the user,
0283 %    model residuals) remain in the de-noised data.  To change this behavior,
0284 %    please see the input opt.denoisespec.
0285 %
0286 % Figures:
0287 % - "CheckData/CheckMeanStd_runN.png" shows the mean and standard deviation across voxels
0288 %   of each volume in run N.  This allows one to quickly check the sanity of the data,
0289 %   e.g., with regards to whether weird transient artifacts exist, whether initial
0290 %   magnetization effects are present in the data (the first few volumes should have
0291 %   been dropped to avoid these effects), whether there are non-finite values in the
0292 %   data (there should not be any), and with regards to the units of the data
0293 %   (the data should consist primarily of positive values and in particular, should
0294 %   not be mean-subtracted).
0295 % - "CheckData/CheckDVARS_runN.png" shows the DVARS metric (Power 2012).  Specifically,
0296 %   this is a time-series of the root-mean-square of the difference between pairs of
0297 %   successive volumes.
0298 % - "HRF.png" shows the initial assumed HRF (provided by <hrfknobs>) and the
0299 %   final estimated HRF (as calculated in step 1).  If <hrfmodel> is 'assume',
0300 %   the two plots will be identical.  If <hrfmodel> is 'fir', this figure
0301 %   is not written.
0302 % - "HRFfitmask.png" shows (in white) the mask restricting the voxels that
0303 %   can be chosen for fitting the global HRF.  This figure is written only
0304 %   if <hrfmodel> is 'optimize' and is not written if opt.hrffitmask is 1.
0305 % - "HRFfitvoxels.png" shows (in white) the voxels used to fit the global HRF.
0306 %   This figure is written only if <hrfmodel> is 'optimize' and only if
0307 %   the fitted global HRF is actually chosen for use (in some cases, the
0308 %   initial HRF estimate is chosen; see GLMestimatemodel.m).
0309 % - "PCselection.png" shows for different numbers of PCs, the median
0310 %   cross-validated R^2 across a subset of voxels (namely, those voxels that
0311 %   have greater than opt.pcR2cutoff (default: 0%) R^2 for at least one of
0312 %   the models considered).  The selected number of PCs is circled and
0313 %   indicated in the title of the figure.
0314 % - "PCscatterN.png" shows a scatter plot of cross-validated R^2 values obtained
0315 %   with no PCs against values obtained with N PCs.  The range of the plot
0316 %   is set to the full range of all R^2 values (across all numbers of PCs).
0317 %   Two different sets of points are plotted.  The first set is shown in green,
0318 %   and this is a set of up to 20,000 voxels randomly selected from the
0319 %   entire pool of voxels.  The second set is shown in red, and this is a set of
0320 %   up to 20,000 voxels randomly selected from the set of voxels that
0321 %   were used to select the number of PC regressors.
0322 % - "MeanVolume.png" shows the mean volume (mean of all volumes).
0323 % - "NoisePool.png" shows (in white) the voxels selected for the noise pool.
0324 % - "NoiseExclude.png" shows (in white) voxels that were excluded by the user
0325 %   from entering the noise pool.  This figure is not written if opt.brainexclude is 0.
0326 % - "PCcrossvalidationN.png" shows cross-validated R^2 values obtained with N PCs.
0327 %   The range is 0% to 100%, and the colormap is nonlinearly scaled to enhance
0328 %   visibility.
0329 % - "PCcrossvalidationscaledN.png" shows cross-validated R^2 values obtained with N PCs.
0330 %   This is just like the previous figures except that the minimum and maximum of the
0331 %   color range is set to the 1st and 99th percentile of R^2 values observed across all
0332 %   PCs.  The point of this is to make it easier to visualize datasets where R^2 values
0333 %   are low.  The disadvantage is that these figures cannot be directly compared across
0334 %   different datasets (since the color range may differ).
0335 % - "PCmask.png" shows (in white) the mask restricting the voxels that
0336 %   can be selected for determining the optimal number of PCs.  This figure is
0337 %   not written if opt.pcR2cutoffmask is 1.
0338 % - "PCvoxels.png" shows (in white) the voxels used to determine the optimal
0339 %   number of PCs.
0340 % - "FinalModel.png" shows R^2 values for the final model (as estimated in
0341 %   step 7).  Note that these R^2 values are not cross-validated.
0342 % - "FinalModel_runN.png" shows R^2 values for the final model separated by
0343 %   runs.  For example, FinalModel_run01.png indicates R^2 values calculated
0344 %   over the data in run 1.  This might be useful for deciding post-hoc to
0345 %   exclude certain runs from the analysis.
0346 % - "SNRsignal.png" shows the maximum absolute amplitude obtained (the signal).
0347 %   The range is 0 to the 99th percentile of the values.
0348 % - "SNRnoise.png" shows the average amplitude error (the noise).
0349 %   The range is 0 to the 99th percentile of the values.
0350 % - "SNR.png" shows the signal-to-noise ratio.  The range is 0 to 10.
0351 % - "SNRcomparebeforeandafter.png" shows a scatter plot of SNR values obtained
0352 %   with no denoising (0 PCs) against SNR values obtained with denoising (with
0353 %   the selected number of PCs).  The range of the plot is set to the full range
0354 %   of SNR values.  The green and red coloring of the dots is the same as in the
0355 %   "PCscatterN.png" figures.
0356 % - "SNRamountofdatagained.png" is exactly the same as the previous figure except
0357 %   that the y-axis is now converted into the equivalent amount of data gained.
0358 %   For example, if SNR is boosted from 4 to 8, this is equivalent to having
0359 %   obtained 300% more data than was actually obtained.
0360 % - "PCmap/PCmap_runN_numO.png" shows the estimated weights for the Oth PC
0361 %   for run N.  The range is -A to A where A is the 99th percentile of the
0362 %   absolute value of all weights across all runs.  The colormap proceeds
0363 %   from blue (negative) to black (0) to red (positive).
0364 %
0365 % Additional information:
0366 % - For additional details on model estimation and quantification of model
0367 % accuracy (R^2), please see the documentation provided in GLMestimatemodel.m.
0368 % - To ensure that <SNRbefore> and <SNRafter> are directly comparable, we first
0369 % average the estimated signal across the no-denoise and denoise cases
0370 % and then divide by the estimated noise from each case.  This has the
0371 % consequence that <SNRafter> will not be exactly identical to <SNR>.
0372 % - With regards to the onset-time specification of the experimental design,
0373 % this is implemented by laying down the HRF at the appropriate onset times
0374 % and then using cubic interpolation to obtain the predicted HRF values at the
0375 % times at which data are actually sampled.
0376 %
0377 % History:
0378 % - 2014/08/01: add opt.wantparametric input (which enables parametric GLM fits).
0379 %               add opt.wantsanityfigures input (which allows user to turn off
0380 %               the sanity-check figures).  add new DVARS sanity-check figures.
0381 %               change the form of the polynomials used in the GLM: the
0382 %               polynomials are now constructed to be orthogonal and unit-length.
0383 %               note that this changes previous behavior and due to numerical
0384 %               issues, the outputs given by this function may be slightly
0385 %               different compared to previous results.
0386 % - 2014/07/14: add <noisepooldirect> input; fix some file- and directory-related
0387 %               issues, making things more platform independent
0388 % - 2013/12/11: rename "global noise regressors" to "noise regressors" in the
0389 %               documentation; perform a quick error-check for non-finite values
0390 %               in the first run of data; omit saving of some of the figures if
0391 %               opt.numboots is 0; add some new sanity-check figures; fix minor bug
0392 % - 2013/11/18: update documentation; add opt.pccontrolmode; add opt.hrfthresh;
0393 %               use fullfile for compatibility with different platforms;
0394 %               if pcvoxels is empty, we now fallback to using the top 100 voxels;
0395 %               change the colormap scaling for PCcrossvalidationscaled*.png;
0396 %               other various small tweaks;
0397 % - 2013/05/12: allow <design> to specify onset times
0398 % - 2013/05/12: add opt.brainexclude and associated figure
0399 % - 2013/05/12: add SNRbefore and SNRafter fields and associated figures
0400 % - 2013/05/12: add PCcrossvalidationscaledN.png figures
0401 % - 2013/05/12: update to indicate fractional values in design matrix are allowed.
0402 % - 2013/05/12: regressors that are all zero now receive a 0 weight (instead of crashing)
0403 % - 2013/05/12: add pcR2final as an output
0404 % - 2012/12/06: automatically convert data to single format
0405 % - 2012/12/05: fix minor bug (bad HRF estimate caused figure crash)
0406 % - 2012/12/03: *** Tag: Version 1.02 ***. Use faster OLS computation (less
0407 %   error-checking; program execution will halt if design matrix is singular);
0408 %   documentation tweak; minor bug fix.
0409 % - 2012/11/24:
0410 %   - INPUTS: add opt.hrffitmask; opt.pcR2cutoff; opt.pcR2cutoffmask; opt.pcstop; opt.denoisespec; opt.wantpercentbold;
0411 %   - OUTPUTS: add hrffitvoxels, pcvoxels, pcweights, inputs
0412 %   - FIGURES: HRFfitmask.png, HRFfitvoxels.png, PCmask.png, PCvoxels.png, Signal.png, Noise.png, SNR.png, PCmap*.png
0413 %   - hrfmodel can now be 'fir'!
0414 %   - change default of opt.numpcstotry to 20
0415 %   - PC regressors are now scaled to have standard deviation of 1
0416 %   - xval scatter plots now are divided into red and green dots (and up to 20,000 each)
0417 %   - pcselection figure uses a line now (not bar) and a selection circle is drawn
0418 %   - no more skipping of the denoiseddata computation
0419 % - 2012/11/03 - add a speed-up
0420 % - 2012/11/02 - Initial version.
0421 % - 2012/10/31 - add meanvol and change that it is the mean of all
0422 % - 2012/10/30 - Automatic division of HRF!
0423 
0424 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DEAL WITH INPUTS, ETC.
0425 
0426 % input
0427 if ~exist('hrfmodel','var') || isempty(hrfmodel)
0428   hrfmodel = 'optimize';
0429 end
0430 if ~exist('hrfknobs','var') || isempty(hrfknobs)
0431   if isequal(hrfmodel,'fir')
0432     hrfknobs = 20;
0433   else
0434     hrfknobs = normalizemax(getcanonicalhrf(stimdur,tr)');
0435   end
0436 end
0437 if ~exist('opt','var') || isempty(opt)
0438   opt = struct();
0439 end
0440 if ~exist('figuredir','var')
0441   figuredir = 'GLMdenoisefigures';
0442 end
0443 
0444 % massage input
0445 if ~iscell(design)
0446   design = {design};
0447 end
0448 if ~iscell(data)
0449   data = {data};
0450 end
0451 for p=1:length(data)
0452   if ~isa(data{p},'single')
0453     fprintf('*** GLMdenoisedata: converting data in run %d to single format (consider doing this before the function call to reduce memory usage). ***\n',p);
0454     data{p} = single(data{p});
0455   end
0456 end
0457 
0458 % do some error checking
0459 if any(flatten(~isfinite(data{1})))
0460   fprintf('*** GLMdenoisedata: WARNING: we checked the first run and found some non-finite values (e.g. NaN, Inf). unexpected results may occur due to non-finite values. please fix and re-run GLMdenoisedata. ***\n');
0461 end
0462 
0463 % calc
0464 numruns = length(design);
0465 dataclass = class(data{1});  % will always be 'single'
0466 is3d = size(data{1},4) > 1;
0467 if is3d
0468   dimdata = 3;
0469   dimtime = 4;
0470   xyzsize = sizefull(data{1},3);
0471 else
0472   dimdata = 1;
0473   dimtime = 2;
0474   xyzsize = size(data{1},1);
0475 end
0476 numvoxels = prod(xyzsize);
0477 
0478 % deal with defaults
0479 if ~isfield(opt,'extraregressors') || isempty(opt.extraregressors)
0480   opt.extraregressors = cell(1,numruns);
0481 end
0482 if ~isfield(opt,'maxpolydeg') || isempty(opt.maxpolydeg)
0483   opt.maxpolydeg = zeros(1,numruns);
0484   for p=1:numruns
0485     opt.maxpolydeg(p) = round(((size(data{p},dimtime)*tr)/60)/2);
0486   end
0487 end
0488 if ~isfield(opt,'seed') || isempty(opt.seed)
0489   opt.seed = sum(100*clock);
0490 end
0491 if ~isfield(opt,'bootgroups') || isempty(opt.bootgroups)
0492   opt.bootgroups = ones(1,numruns);
0493 end
0494 if ~isfield(opt,'numforhrf') || isempty(opt.numforhrf)
0495   opt.numforhrf = 50;
0496 end
0497 if ~isfield(opt,'hrffitmask') || isempty(opt.hrffitmask)
0498   opt.hrffitmask = 1;
0499 end
0500 if ~isfield(opt,'brainthresh') || isempty(opt.brainthresh)
0501   opt.brainthresh = [99 0.5];
0502 end
0503 if ~isfield(opt,'brainR2') || isempty(opt.brainR2)
0504   opt.brainR2 = 0;
0505 end
0506 if ~isfield(opt,'brainexclude') || isempty(opt.brainexclude)
0507   opt.brainexclude = 0;
0508 end
0509 if ~isfield(opt,'numpcstotry') || isempty(opt.numpcstotry)
0510   opt.numpcstotry = 20;
0511 end
0512 if ~isfield(opt,'pcR2cutoff') || isempty(opt.pcR2cutoff)
0513   opt.pcR2cutoff = 0;
0514 end
0515 if ~isfield(opt,'pcR2cutoffmask') || isempty(opt.pcR2cutoffmask)
0516   opt.pcR2cutoffmask = 1;
0517 end
0518 if ~isfield(opt,'pcstop') || isempty(opt.pcstop)
0519   opt.pcstop = 1.05;
0520 end
0521 if ~isfield(opt,'pccontrolmode') || isempty(opt.pccontrolmode)
0522   opt.pccontrolmode = 0;
0523 end
0524 if ~isfield(opt,'numboots') || isempty(opt.numboots)
0525   opt.numboots = 100;
0526 end
0527 if ~isfield(opt,'denoisespec') || (isempty(opt.denoisespec) && ~iscell(opt.denoisespec))
0528   opt.denoisespec = '11101';
0529 end
0530 if ~isfield(opt,'wantpercentbold') || isempty(opt.wantpercentbold)
0531   opt.wantpercentbold = 1;
0532 end
0533 if ~isfield(opt,'hrfthresh') || isempty(opt.hrfthresh)
0534   opt.hrfthresh = 50;
0535 end
0536 if ~isfield(opt,'noisepooldirect') || isempty(opt.noisepooldirect)
0537   opt.noisepooldirect = [];
0538 end
0539 if ~isequal(hrfmodel,'fir')
0540   hrfknobs = normalizemax(hrfknobs);
0541 end
0542 if ~isfield(opt,'wantparametric') || isempty(opt.wantparametric)
0543   opt.wantparametric = 0;
0544 end
0545 if ~isfield(opt,'wantsanityfigures') || isempty(opt.wantsanityfigures)
0546   opt.wantsanityfigures = 1;
0547 end
0548 if length(opt.maxpolydeg) == 1
0549   opt.maxpolydeg = repmat(opt.maxpolydeg,[1 numruns]);
0550 end
0551 if ~iscell(opt.extraregressors)
0552   opt.extraregressors = {opt.extraregressors};
0553 end
0554 if ~iscell(opt.denoisespec)
0555   opt.denoisespec = {opt.denoisespec};
0556 end
0557 
0558 % delete and/or make figuredir
0559 if ~isempty(figuredir)
0560   if exist(figuredir,'dir')
0561     assert(rmdir(figuredir,'s'));
0562   end
0563   assert(mkdir(figuredir));
0564   assert(mkdir(fullfile(figuredir,'PCmap')));
0565   if opt.wantsanityfigures
0566     assert(mkdir(fullfile(figuredir,'CheckData')));
0567   end
0568   figuredir = absolutepath(figuredir);
0569 end
0570 
0571 % whether to bypass a lot of the usual GLMdenoise routine
0572 % (since the noise pool and number of PCs are already supplied by the user)
0573 wantbypass = ~isempty(opt.noisepooldirect);
0574 
0575 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PRE-FLIGHT SANITY CHECK
0576 
0577 if ~isempty(figuredir) && opt.wantsanityfigures
0578   fprintf('*** GLMdenoisedata: generating sanity-check figures. ***\n');
0579   
0580   % make figure showing mean and std dev of each volume over time
0581   for p=1:length(data)
0582   
0583     % calc
0584     meants = mean(squish(data{p},dimdata),1);
0585     stdts = std(squish(data{p},dimdata),[],1);
0586     dvarts = sqrt(mean(diff(squish(data{p},dimdata),1,2).^2,1));  % RMS of frame-to-frame change
0587 
0588     % make mean+std figure
0589     figureprep([100 100 1000 300]); hold on;
0590     errorbar2(1:length(meants),meants,stdts,'v','r-');
0591     plot(1:length(meants),meants,'r-');
0592     ax = axis; axis([1-10 length(meants)+10 ax(3:4)]);
0593     xlabel('TR');
0594     ylabel('Signal (raw units)');
0595     title(sprintf('Run %d (mean + std dev of each volume)',p));
0596     figurewrite(sprintf('CheckMeanStd_run%02d',p),[],[],fullfile(figuredir,'CheckData'));
0597 
0598     % make dvars figure
0599     figureprep([100 100 1000 300]); hold on;
0600     plot(dvarts,'r-');
0601     ax = axis; axis([1-10 length(dvarts)+10 ax(3:4)]);
0602     xlabel('Volume number');
0603     ylabel('RMS of difference image (raw units)');
0604     title(sprintf('Run %d (DVARS)',p));
0605     figurewrite(sprintf('CheckDVARS_run%02d',p),[],[],fullfile(figuredir,'CheckData'));
0606 
0607   end
0608 
0609 end
0610 
0611 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DETERMINE HRF
0612 
0613 % if 'optimize', perform full-fit to determine HRF
0614 switch hrfmodel
0615 case 'optimize'
0616   fprintf('*** GLMdenoisedata: performing full fit to estimate global HRF. ***\n');
0617   fullfit = GLMestimatemodel(design,data,stimdur,tr,hrfmodel,hrfknobs,0,opt,[],2);
0618   hrf = fullfit.modelmd{1};
0619   hrffitvoxels = fullfit.hrffitvoxels;
0620   clear fullfit;
0621 
0622 % if 'assume', the HRF is provided by the user
0623 case 'assume'
0624   hrf = hrfknobs;
0625   hrffitvoxels = [];
0626 
0627 % if 'fir', do nothing
0628 case 'fir'
0629   hrffitvoxels = [];
0630   
0631 end
0632 
0633 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CALCULATE CROSS-VALIDATED R^2 VALUES
0634 
0635 if ~wantbypass
0636 
0637   % perform cross-validation to determine R^2 values
0638   fprintf('*** GLMdenoisedata: performing cross-validation to determine R^2 values. ***\n');
0639   switch hrfmodel
0640   case {'optimize' 'assume'}
0641     xvalfit = GLMestimatemodel(design,data,stimdur,tr,'assume',hrf,-1,opt,[],1);
0642   case 'fir'
0643     xvalfit = GLMestimatemodel(design,data,stimdur,tr,'fir',hrfknobs,-1,opt,[],1);
0644   end
0645   pcR2 = xvalfit.R2;
0646   clear xvalfit;
0647 
0648 end
0649 
0650 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DETERMINE NOISE POOL AND CALCULATE NOISE REGRESSORS
0651 
0652 % calculate mean volume
0653 volcnt = cellfun(@(x) size(x,dimtime),data);
0654 meanvol = reshape(catcell(2,cellfun(@(x) squish(mean(x,dimtime),dimdata),data,'UniformOutput',0)) ...
0655                   * (volcnt' / sum(volcnt)),[xyzsize 1]);
0656 
0657 if ~wantbypass
0658 
0659   % determine noise pool
0660   fprintf('*** GLMdenoisedata: determining noise pool. ***\n');
0661   thresh = prctile(meanvol(:),opt.brainthresh(1))*opt.brainthresh(2);  % threshold for non-brain voxels
0662   bright = meanvol > thresh;                                           % logical indicating voxels that are bright (brain voxels)
0663   badxval = pcR2 < opt.brainR2;                                        % logical indicating voxels with poor cross-validation accuracy
0664   noisepool = bright & badxval & ~opt.brainexclude;                    % logical indicating voxels that satisfy all criteria
0665 
0666 else
0667 
0668   % the noise pool is specified by the user
0669   noisepool = opt.noisepooldirect{1};
0670 
0671 end
0672   
0673 % determine noise regressors
0674 fprintf('*** GLMdenoisedata: calculating noise regressors. ***\n');
0675 pcregressors = {};
0676 for p=1:length(data)
0677 
0678   % extract the time-series data for the noise pool
0679   temp = subscript(squish(data{p},dimdata),{noisepool ':'})';  % time x voxels
0680 
0681   % project out polynomials from the data
0682   temp = projectionmatrix(constructpolynomialmatrix(size(temp,1),0:opt.maxpolydeg(p))) * temp;
0683 
0684   % unit-length normalize each time-series (ignoring any time-series that are all 0)
0685   [temp,len] = unitlengthfast(temp,1);
0686   temp = temp(:,len~=0);
0687 
0688   % perform SVD and select the top PCs
0689   [u,s,v] = svds(double(temp*temp'),opt.numpcstotry);
0690   u = bsxfun(@rdivide,u,std(u,[],1));  % scale so that std is 1
0691   pcregressors{p} = cast(u,dataclass);
0692 
0693 end
0694 clear temp len u s v;
0695 
0696 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PERTURB THE NOISE REGRESSORS (IF REQUESTED)
0697 
0698 switch opt.pccontrolmode
0699 
0700 % do nothing (this is the default)
0701 case 0
0702 
0703 % phase-scramble each regressor
0704 case 1
0705 
0706   % for each run
0707   for p=1:length(pcregressors)
0708   
0709     % for each regressor
0710     for q=1:size(pcregressors{p},2)
0711     
0712       % the original regressor
0713       temp = pcregressors{p}(:,q);
0714 
0715       % a sample of white noise
0716       temp2 = randn(size(temp));
0717 
0718       % new regressor has the amplitude spectrum of the original regressor,
0719       % but the phase spectrum of the white noise
0720       pcregressors{p}(:,q) = real(ifft(abs(fft(temp,[],1)) .* exp(j * angle(fft(temp2,[],1)))));
0721 
0722     end
0723 
0724   end
0725   clear temp temp2;
0726 
0727 % shuffle regressors across runs (ensuring none match up to the original assignment)
0728 case 2
0729 
0730   % repeat until we have a satisfactory assignment
0731   while 1
0732   
0733     % generate a random permutation
0734     temp = permutedim(1:length(pcregressors));
0735     
0736     % if none matched the original assignment, we are done
0737     if ~any(temp == (1:length(pcregressors)))
0738       break;
0739     end
0740 
0741   end
0742 
0743   % shuffle the regressors
0744   pcregressors = pcregressors(temp);
0745 
0746 end
0747 
0748 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ADD NOISE REGRESSORS INTO MODEL AND CHOOSE OPTIMAL NUMBER
0749 
0750 if ~wantbypass
0751 
0752   % perform cross-validation with increasing number of noise regressors
0753   for p=1:opt.numpcstotry
0754     fprintf('*** GLMdenoisedata: performing cross-validation with %d PCs. ***\n',p);
0755     opt2 = opt;
0756     for q=1:numruns
0757       opt2.extraregressors{q} = cat(2,opt2.extraregressors{q},pcregressors{q}(:,1:p));
0758     end
0759     opt2.wantpercentbold = 0;  % no need to do this, so optimize for speed
0760     switch hrfmodel
0761     case {'optimize' 'assume'}
0762       xvalfit = GLMestimatemodel(design,data,stimdur,tr,'assume',hrf,-1,opt2,[],1);
0763     case 'fir'
0764       xvalfit = GLMestimatemodel(design,data,stimdur,tr,'fir',hrfknobs,-1,opt2,[],1);
0765     end
0766     pcR2 = cat(dimdata+1,pcR2,xvalfit.R2);
0767   end
0768   clear xvalfit;
0769 
0770   % prepare to select optimal number of PCs
0771   temp = squish(pcR2,dimdata);  % voxels x 1+pcs
0772   pcvoxels = any(temp > opt.pcR2cutoff,2) & squish(opt.pcR2cutoffmask,dimdata);  % if pcR2cutoffmask is 1, this still works
0773   if ~any(pcvoxels)
0774     warning(['No voxels passed the threshold for the selection of the number of PCs. ' ...
0775              'We fallback to simply using the top 100 voxels (i.e. compute each voxel''s maximum ' ...
0776              'cross-validation accuracy under any number of PCs and then choose the top 100 voxels.']);
0777     if isequal(opt.pcR2cutoffmask,1)
0778       ix = 1:size(temp,1);
0779     else
0780       ix = find(squish(opt.pcR2cutoffmask,dimdata));
0781     end
0782     pcvoxels = logical(zeros(size(temp,1),1));
0783     temp2 = max(temp(ix,:),[],2);  % max cross-validation for each voxel (within mask)
0784     [d,ix2] = sort(temp2,'descend');
0785     pcvoxels(ix(ix2(1:min(100,length(ix2))))) = 1;
0786   end
0787   xvaltrend = median(temp(pcvoxels,:),1);
0788 
0789   % choose number of PCs
0790   chosen = 0;  % this is the fall-back
0791   if opt.pcstop <= 0
0792     chosen = -opt.pcstop;  % in this case, the user decides
0793   else
0794     curve = xvaltrend - xvaltrend(1);  % this is the performance curve that starts at 0 (corresponding to 0 PCs)
0795     mx = max(curve);                   % store the maximum of the curve
0796     best = -Inf;                       % initialize (this will hold the best performance observed thus far)
0797     for p=0:opt.numpcstotry
0798   
0799       % if better than best so far
0800       if curve(1+p) > best
0801     
0802         % record this number of PCs as the best
0803         chosen = p;
0804         best = curve(1+p);
0805       
0806         % if we are within opt.pcstop of the max, then we stop.
0807         if best*opt.pcstop >= mx
0808           break;
0809         end
0810       
0811       end
0812   
0813     end
0814   end
0815 
0816   % record the number of PCs
0817   pcnum = chosen;
0818   fprintf('*** GLMdenoisedata: selected number of PCs is %d. ***\n',pcnum);
0819 
0820 else
0821 
0822   % the number of PCs is specified by the user
0823   pcnum = opt.noisepooldirect{2};
0824   fprintf('*** GLMdenoisedata: user-specified number of PCs is %d. ***\n',pcnum);
0825   
0826 end
0827 
0828 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% FIT FINAL MODEL AND PREPARE OUTPUT
0829 
0830 % fit final model (NO DENOISING)
0831 opt2 = opt;
0832 opt2.wantpercentbold = 0;  % do not do the conversion yet.  we will do it ourselves below.
0833 fprintf('*** GLMdenoisedata: fitting final model (no denoising, for comparison purposes). ***\n');
0834 switch hrfmodel
0835 case {'optimize' 'assume'}
0836   resultsTEMP = GLMestimatemodel(design,data,stimdur,tr,'assume',hrf,opt.numboots,opt2);
0837 case 'fir'
0838   resultsTEMP = GLMestimatemodel(design,data,stimdur,tr,'fir',hrfknobs,opt.numboots,opt2);
0839 end
0840 
0841 % save some results
0842 signal_nodenoise = resultsTEMP.signal;
0843 noise_nodenoise = resultsTEMP.noise;
0844 clear resultsTEMP;
0845 
0846 % fit final model (WITH DENOISING)
0847 opt2 = opt;
0848 for q=1:numruns
0849   opt2.extraregressors{q} = cat(2,opt2.extraregressors{q},pcregressors{q}(:,1:pcnum));
0850 end
0851 opt2.wantpercentbold = 0;  % do not do the conversion yet.  we will do it ourselves below.
0852 fprintf('*** GLMdenoisedata: fitting final model (with denoising). ***\n');
0853 switch hrfmodel     % note that we will use the rawdesign field from the cache output for the parametric fits
0854 case {'optimize' 'assume'}
0855   [results,cache] = GLMestimatemodel(design,data,stimdur,tr,'assume',hrf,opt.numboots,opt2);
0856 case 'fir'
0857   [results,cache] = GLMestimatemodel(design,data,stimdur,tr,'fir',hrfknobs,opt.numboots,opt2);
0858 end
0859 
0860 % prepare additional outputs
0861 results.hrffitvoxels = hrffitvoxels;  % note that this overrides the existing entry in results
0862 results.meanvol = meanvol;
0863 results.noisepool = noisepool;
0864 results.pcregressors = pcregressors;
0865 if ~wantbypass
0866   results.pcR2 = pcR2;
0867   results.pcvoxels = reshape(pcvoxels,[xyzsize 1]);
0868 end
0869 results.pcnum = pcnum;
0870 clear meanvol noisepool pcregressors pcR2 pcnum;
0871 
0872 % prepare SNR comparison
0873 amp = (results.signal + signal_nodenoise)/2;
0874 results.SNRbefore = amp ./ noise_nodenoise;
0875 results.SNRafter = amp ./ results.noise;
0876 clear amp signal_nodenoise noise_nodenoise;
0877 
0878 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PARAMETRIC FITS AND ERROR ESTIMATES
0879 
0880 if opt.wantparametric
0881 
0882   fprintf('*** GLMdenoisedata: calculating parametric fits and error estimates. ***\n');
0883 
0884   % construct design matrix
0885   temp = {};
0886   for p=1:numruns
0887     numtime = size(data{p},dimtime);
0888     temp{p} = cat(2,constructpolynomialmatrix(numtime,0:opt.maxpolydeg(p)), ...
0889                     opt.extraregressors{p}, ...
0890                     results.pcregressors{p}(:,1:results.pcnum));
0891   end
0892   results.parametric.designmatrix = [catcell(1,cache.rawdesign) blkdiag(temp{:})];  % time x regressors
0893 
0894   % estimate parameters using OLS
0895   results.parametric.parameters = ...
0896     mtimescell(olsmatrix2(results.parametric.designmatrix), ...
0897                cellfun(@(x) squish(x,dimdata)',data,'UniformOutput',0));  % regressors x voxels
0898 
0899   % compute sum of the squares of the residuals (e.g. sum(resid.^2))
0900   sumsq = sum((results.parametric.designmatrix * results.parametric.parameters - ...
0901                catcell(1,cellfun(@(x) squish(x,dimdata)',data,'UniformOutput',0))).^2,1);  % 1 x voxels
0902 
0903   % estimate noise variance (e.g. sum(resid.^2) / (n - rank(X)))
0904   results.parametric.noisevar = ...  % 1 x voxels
0905     sumsq ./ (size(results.parametric.designmatrix,1) - rank(results.parametric.designmatrix));
0906 
0907   % calculate standard error on parameters (e.g. sqrt(sigma^2 * inv(X'*X)))
0908   good = ~all(results.parametric.designmatrix==0,1);
0909   X = results.parametric.designmatrix(:,good);
0910   results.parametric.parametersse = ...  % regressors x voxels
0911     sqrt(bsxfun(@times,results.parametric.noisevar, ...
0912                 copymatrix(zeros(size(results.parametric.designmatrix,2),1),good,diag(inv(X'*X)))));
0913   
0914   % clean up
0915   clear temp sumsq good X;
0916   
0917   % prepare for output
0918   results.parametric.parameters =   reshape(results.parametric.parameters',  [xyzsize size(results.parametric.parameters,1)]);
0919   results.parametric.noisevar =     reshape(results.parametric.noisevar,     [xyzsize 1]);
0920   results.parametric.parametersse = reshape(results.parametric.parametersse',[xyzsize size(results.parametric.parametersse,1)]);
0921 
0922 end
0923 
0924 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CALCULATE DENOISED DATA AND PCWEIGHTS
0925 
0926 fprintf('*** GLMdenoisedata: calculating denoised data and PC weights. ***\n');
0927 
0928 % for each run, perform regression to figure out the various contributions
0929 denoiseddata = cell(length(opt.denoisespec),numruns);
0930 for p=1:numel(denoiseddata)
0931   denoiseddata{p} = cast(denoiseddata{p},dataclass);
0932 end
0933 results.pcweights = zeros([numvoxels results.pcnum numruns],dataclass);
0934 for p=1:numruns
0935 
0936   % calc
0937   numtime = size(data{p},dimtime);
0938 
0939   % calculate signal contribution
0940   modelcomponent = GLMpredictresponses(results.modelmd,{design{p}},tr,numtime,dimdata);
0941   modelcomponent = modelcomponent{1};  % X x Y x Z x T
0942 
0943   % prepare polynomial regressors
0944   polymatrix = constructpolynomialmatrix(numtime,0:opt.maxpolydeg(p));
0945   numpoly = size(polymatrix,2);
0946 
0947   % prepare other regressors
0948   exmatrix = opt.extraregressors{p};
0949   numex = size(exmatrix,2);
0950 
0951   % prepare noise regressors
0952   pcmatrix = results.pcregressors{p}(:,1:results.pcnum);
0953   numpc = size(pcmatrix,2);
0954 
0955   % estimate weights
0956   h = olsmatrix2(cat(2,polymatrix,exmatrix,pcmatrix))*squish(data{p} - modelcomponent,dimdata)';  % parameters x voxels
0957 
0958   % record weights on noise regressors
0959   results.pcweights(:,:,p) = h(numpoly+numex+(1:numpc),:)';
0960   
0961   % construct time-series
0962   polycomponent = reshape((polymatrix*h(1:numpoly,:))',[xyzsize numtime]);
0963   if numex == 0
0964     excomponent = zeros([xyzsize numtime],dataclass);
0965   else
0966     excomponent = reshape((exmatrix*h(numpoly+(1:numex),:))',[xyzsize numtime]);
0967   end
0968   if numpc == 0
0969     pccomponent = zeros([xyzsize numtime],dataclass);
0970   else
0971     pccomponent = reshape((pcmatrix*h(numpoly+numex+(1:numpc),:))',[xyzsize numtime]);
0972   end
0973   residcomponent = data{p} - (modelcomponent + polycomponent + excomponent + pccomponent);
0974   
0975   % construct denoised data (but don't bother if the user doesn't want it)
0976   if nargout > 1
0977     for q=1:length(opt.denoisespec)
0978       denoiseddata{q,p} = bitget(bin2dec(opt.denoisespec{q}),5) * modelcomponent + ...
0979                          bitget(bin2dec(opt.denoisespec{q}),4) * polycomponent + ...
0980                          bitget(bin2dec(opt.denoisespec{q}),3) * excomponent + ...
0981                          bitget(bin2dec(opt.denoisespec{q}),2) * pccomponent + ...
0982                          bitget(bin2dec(opt.denoisespec{q}),1) * residcomponent;
0983     end
0984   end
0985 
0986 end
0987 
0988 % clean up
0989 clear modelcomponent h polycomponent excomponent pccomponent residcomponent;
0990 
0991 % prepare for output
0992 results.pcweights = reshape(results.pcweights,[xyzsize results.pcnum numruns]);
0993 
0994 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PREPARE ADDITIONAL OUTPUTS
0995 
0996 % return all the inputs (except for the data) in the output.
0997 % also, include a new field 'datasize'.
0998 results.inputs.design = design;
0999 results.inputs.datasize = cellfun(@(x) size(x),data,'UniformOutput',0);
1000 results.inputs.stimdur = stimdur;
1001 results.inputs.tr = tr;
1002 results.inputs.hrfmodel = hrfmodel;
1003 results.inputs.hrfknobs = hrfknobs;
1004 results.inputs.opt = opt;
1005 results.inputs.figuredir = figuredir;
1006 
1007 if ~wantbypass
1008   % prepare pcR2final
1009   results.pcR2final = subscript(results.pcR2,[repmat({':'},[1 dimdata]) {1+results.pcnum}]);
1010 end
1011 
1012 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CONVERT TO % BOLD CHANGE
1013 
1014 if opt.wantpercentbold
1015   fprintf('*** GLMdenoisedata: converting to percent BOLD change. ***\n');
1016   con = 1./abs(results.meanvol) * 100;
1017   switch hrfmodel
1018   case 'fir'
1019     for p=1:size(results.models,dimdata+3)  % ugly to save MEMORY
1020       if dimdata==3
1021         results.models(:,:,:,:,:,p) = bsxfun(@times,results.models(:,:,:,:,:,p),con);
1022       else
1023         results.models(:,:,:,p) = bsxfun(@times,results.models(:,:,:,p),con);
1024       end
1025     end
1026     results.modelmd = bsxfun(@times,results.modelmd,con);
1027     results.modelse = bsxfun(@times,results.modelse,con);
1028   case {'assume' 'optimize'}
1029     for p=1:size(results.models{2},dimdata+2)  % ugly to save MEMORY
1030       if dimdata==3
1031         results.models{2}(:,:,:,:,p) = bsxfun(@times,results.models{2}(:,:,:,:,p),con);
1032       else
1033         results.models{2}(:,:,p) = bsxfun(@times,results.models{2}(:,:,p),con);
1034       end
1035     end
1036     results.modelmd{2} = bsxfun(@times,results.modelmd{2},con);
1037     results.modelse{2} = bsxfun(@times,results.modelse{2},con);
1038   end
1039 end
1040 
1041 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GENERATE FIGURES
1042 
1043 if ~isempty(figuredir)
1044   fprintf('*** GLMdenoisedata: generating figures. ***\n');
1045 
1046   % make figure showing HRF
1047   if ~isequal(hrfmodel,'fir') && length(hrfknobs) > 1
1048     figureprep([100 100 450 250]); hold on;
1049     numinhrf = length(hrfknobs);
1050     h1 = plot(0:tr:(numinhrf-1)*tr,hrfknobs,'ro-');
1051     h2 = plot(0:tr:(numinhrf-1)*tr,results.modelmd{1},'bo-');
1052     ax = axis; axis([0 (numinhrf-1)*tr ax(3) 1.2]);
1053     straightline(0,'h','k-');
1054     legend([h1 h2],{'Initial HRF' 'Estimated HRF'});
1055     xlabel('Time from condition onset (s)');
1056     ylabel('Response');
1057     figurewrite('HRF',[],[],figuredir);
1058   end
1059   
1060   % write out image showing HRF fit voxels
1061   if isequal(hrfmodel,'optimize') && ~isempty(results.hrffitvoxels)
1062     imwrite(uint8(255*makeimagestack(results.hrffitvoxels,[0 1])),gray(256),fullfile(figuredir,'HRFfitvoxels.png'));
1063   end
1064 
1065   if ~wantbypass
1066 
1067     % make figure illustrating selection of number of PCs
1068     figureprep([100 100 400 400]); hold on;
1069     plot(0:opt.numpcstotry,xvaltrend,'r.-');
1070     set(scatter(results.pcnum,xvaltrend(1+results.pcnum),100,'ro'),'LineWidth',2);
1071     set(gca,'XTick',0:opt.numpcstotry);
1072     xlabel('Number of PCs');
1073     ylabel('Cross-validated R^2 (median across voxels)');
1074     title(sprintf('Selected PC number = %d',results.pcnum));
1075     figurewrite('PCselection',[],[],figuredir);
1076   
1077     % make figure showing scatter plots of cross-validated R^2
1078     rng = [min(results.pcR2(:)) max(results.pcR2(:))];
1079     for p=1:opt.numpcstotry
1080       temp = squish(results.pcR2,dimdata);  % voxels x 1+pcs
1081       figureprep([100 100 500 500]); hold on;
1082       scattersparse(temp(:,1),temp(:,1+p),20000,0,36,'g','.');
1083       scattersparse(temp(pcvoxels,1),temp(pcvoxels,1+p),20000,0,36,'r','.');
1084       axis([rng rng]); axissquarify; axis([rng rng]); 
1085       straightline(0,'h','y-');
1086       straightline(0,'v','y-');
1087       xlabel('Cross-validated R^2 (0 PCs)');
1088       ylabel(sprintf('Cross-validated R^2 (%d PCs)',p));
1089       title(sprintf('Number of PCs = %d',p));
1090       figurewrite(sprintf('PCscatter%02d',p),[],[],figuredir);
1091     end
1092 
1093   end
1094 
1095   % write out image showing mean volume (of first run)
1096   imwrite(uint8(255*makeimagestack(results.meanvol,1)),gray(256),fullfile(figuredir,'MeanVolume.png'));
1097 
1098   % write out image showing noise pool
1099   imwrite(uint8(255*makeimagestack(results.noisepool,[0 1])),gray(256),fullfile(figuredir,'NoisePool.png'));
1100 
1101   % write out image showing voxels excluded from noise pool
1102   if ~isequal(opt.brainexclude,0)
1103     imwrite(uint8(255*makeimagestack(opt.brainexclude,[0 1])),gray(256),fullfile(figuredir,'NoiseExclude.png'));
1104   end
1105 
1106   % write out image showing voxel mask for HRF fitting
1107   if isequal(hrfmodel,'optimize') && ~isequal(opt.hrffitmask,1)
1108     imwrite(uint8(255*makeimagestack(opt.hrffitmask,[0 1])),gray(256),fullfile(figuredir,'HRFfitmask.png'));
1109   end
1110 
1111   % define a function that will write out R^2 values to an image file
1112   imfun = @(results,filename) ...
1113     imwrite(uint8(255*makeimagestack(signedarraypower(results/100,0.5),[0 1])),hot(256),filename);
1114 
1115   if ~wantbypass
1116 
1117     % write out image showing voxel mask for PC selection
1118     if ~isequal(opt.pcR2cutoffmask,1)
1119       imwrite(uint8(255*makeimagestack(opt.pcR2cutoffmask,[0 1])),gray(256),fullfile(figuredir,'PCmask.png'));
1120     end
1121   
1122     % write out image showing the actual voxels used for PC selection
1123     imwrite(uint8(255*makeimagestack(results.pcvoxels,[0 1])),gray(256),fullfile(figuredir,'PCvoxels.png'));
1124 
1125     % figure out bounds for the R^2 values
1126     bounds = prctile(results.pcR2(:),[1 99]);
1127     if bounds(1)==bounds(2)  % a hack to avoid errors in normalization
1128       bounds(2) = bounds(1) + 1;
1129     end
1130 
1131     % define another R^2 image-writing function
1132     imfunB = @(results,filename) ...
1133       imwrite(uint8(255*makeimagestack(signedarraypower(normalizerange(results,0,1,bounds(1),bounds(2)),0.5),[0 1])),hot(256),filename);
1134 
1135     % write out cross-validated R^2 for the various numbers of PCs
1136     for p=1:size(results.pcR2,dimdata+1)
1137       temp = subscript(results.pcR2,[repmat({':'},[1 dimdata]) {p}]);
1138       feval(imfun,temp,fullfile(figuredir,sprintf('PCcrossvalidation%02d.png',p-1)));
1139       feval(imfunB,temp,fullfile(figuredir,sprintf('PCcrossvalidationscaled%02d.png',p-1)));
1140     end
1141 
1142   end
1143 
1144   % write out overall R^2 for final model
1145   feval(imfun,results.R2,fullfile(figuredir,sprintf('FinalModel.png')));
1146 
1147   % write out R^2 separated by runs for final model
1148   for p=1:size(results.R2run,dimdata+1)
1149     temp = subscript(results.R2run,[repmat({':'},[1 dimdata]) {p}]);
1150     feval(imfun,temp,fullfile(figuredir,sprintf('FinalModel_run%02d.png',p)));
1151   end
1152   
1153   % write out signal, noise, and SNR
1154   imwrite(uint8(255*makeimagestack(results.signal,[0 prctile(results.signal(:),99)])),hot(256),fullfile(figuredir,'SNRsignal.png'));
1155   if opt.numboots ~= 0
1156     imwrite(uint8(255*makeimagestack(results.noise,[0 max(eps,prctile(results.noise(:),99))])),hot(256),fullfile(figuredir,'SNRnoise.png'));
1157     imwrite(uint8(255*makeimagestack(results.SNR,[0 10])),hot(256),fullfile(figuredir,'SNR.png'));
1158   end
1159   
1160   % write out SNR comparison figures (first figure)
1161   if opt.numboots ~= 0
1162     rng = [min([results.SNRbefore(:); results.SNRafter(:)]) max([results.SNRbefore(:); results.SNRafter(:)])];
1163     if ~all(isfinite(rng))  % hack to deal with cases of no noise estimate
1164       rng = [0 1];
1165     end
1166     figureprep([100 100 500 500]); hold on;
1167     scattersparse(results.SNRbefore(:),results.SNRafter(:),20000,0,36,'g','.');
1168     if ~wantbypass
1169       scattersparse(results.SNRbefore(pcvoxels),results.SNRafter(pcvoxels),20000,0,36,'r','.');
1170     end
1171     axis([rng rng]); axissquarify; axis([rng rng]);
1172     xlabel('SNR (before denoising)');
1173     ylabel('SNR (after denoising)');
1174     figurewrite(sprintf('SNRcomparebeforeandafter'),[],[],figuredir);
1175   end
1176   
1177   % write out SNR comparison figures (second figure)
1178   if opt.numboots ~= 0
1179     datagain = ((results.SNRafter./results.SNRbefore).^2 - 1) * 100;
1180     figureprep([100 100 500 500]); hold on;
1181     scattersparse(results.SNRbefore(:),datagain(:),20000,0,36,'g','.');
1182     if ~wantbypass
1183       scattersparse(results.SNRbefore(pcvoxels),datagain(pcvoxels),20000,0,36,'r','.');
1184     end
1185     ax = axis; axis([rng ax(3:4)]);
1186     xlabel('SNR (before denoising)');
1187     ylabel('Equivalent amount of data gained (%)');
1188     figurewrite(sprintf('SNRamountofdatagained'),[],[],figuredir);
1189   end
1190   
1191   % write out maps of pc weights
1192   thresh = prctile(abs(results.pcweights(:)),99);
1193   for p=1:size(results.pcweights,dimdata+1)
1194     for q=1:size(results.pcweights,dimdata+2)
1195       temp = subscript(results.pcweights,[repmat({':'},[1 dimdata]) {p} {q}]);
1196       imwrite(uint8(255*makeimagestack(temp,[-thresh thresh])),cmapsign(256), ...
1197               fullfile(figuredir,'PCmap',sprintf('PCmap_run%02d_num%02d.png',q,p)));
1198     end
1199   end
1200 
1201 end

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