Demo: fMRI surface-based searchlights with LDA classifier

The data used here is available from http://cosmomvpa.org/datadb.zip

This example uses the following dataset: + 'digit' A participant made finger pressed with the index and middle finger of the right hand during 4 runs in an fMRI study. Each run was divided in 4 blocks with presses of each finger and analyzed with the GLM, resulting in 2*4*4=32 t-values

A searchlight is run with a 100 voxel searchlight, using a disc for which the metric radius varies from node to node.

This example requires the surfing toolbox, github.com/nno/surfing

This example may take quite some time to run. For faster execution but lower spatial precision, set ld=16 below; for slower execution use ld=64.

If you use this code for a publication, please cite: Oosterhof, N.N., Wiestler, T, Downing, P.E., & Diedrichsen, J. (2011) A comparison of volume-based and surface-based information mapping. Neuroimage. DOI:10.1016/j.neuroimage.2010.04.270

  1. For CoSMoMVPA's copyright information and license terms, #
  2. see the COPYING file distributed with CoSMoMVPA. #

Contents

Check externals

cosmo_check_external('surfing');
cosmo_check_external('afni');

Set data paths

The function cosmo_config() returns a struct containing paths to tutorial data. (Alternatively the paths can be set manually without using cosmo_config.)

config=cosmo_config();

digit_study_path=fullfile(config.tutorial_data_path,'digit');
readme_fn=fullfile(digit_study_path,'README');
cosmo_type(readme_fn);

output_path=config.output_data_path;
Overview
--------
fMRI responses to a human participant pressing buttons with the index and middle finger.

Contents
--------
- glm_T_stats_allruns+orig.{BRIK,HEAD}: 
    t-statistics associated with finger presses. 
    There are 4 runs, each with 4 blocks. 
    Each block has two t-statistics, one for each of button presses for the 
index and middle finger (in that order). 
- epi+orig.{HEAD,BRIK}:
    A single EPI image.
- icoXX_mh.YY.asc:
    Surface anatomy meshes of two hemispheres with standard topology, generated 
    using AFNI SUMA's MapIcosahedron. Left and right hemisphere surfaces were
    merged in the order left, right; the first half of the nodes and faces refer 
    to the left hemisphere, the second half to the right hemisphere.
    XX={16,64} is the number of linear divisions of MapIcosahedron; surfaces
    have (XX^2)*10+2 nodes and XX^2*20 faces in each hemisphere.
    YY={pial_al,white_al} are the outer and inner surfaces around the grey matter
    generated by FreeSurfer. YY=intermediate_al is the node-wise average of the
    pial and white surfaces; this surface can be used for single-surface
    analyses when surfaces are generated using Caret or BrainVoyager
    YY=inflated_alCoMmedial are inflated surfaces with the center of mass
    along the medial side of the two hemispheres. These surfaces are suitable
    for visualization purposes.

Methods
-------
This dataset contains data from a fingerpress experiment where a participant 
(28y right-handed male) pressed buttons on a box with the index and middle 
finger of the right  hand while fMRI volumes were acquired. Over four runs, in 
total sixteen blocks of 72s finger presses were acquired; in each block the 
participant pressed the index and middle finger during different `mini-blocks' 
of 8 s each. Each block was preceded and followed by a 16s rest period. 
Functional data was preprocessed in AFNI with despiking, time slice correction, 
motion correction, and scaling to percent signal change by dividing the signal 
for each volume by the mean of the run. No spatial smoothing or interpolation 
was applied to the functional data, except for interpolation during motion 
correction. The preprocessed data was analyzed with a general linear model 
(GLM) with separate regressors for each finger and each block (and some 
regressors of no interest) to obtain t-values for each finger in each block. 

License
-------
The contents are made available by Nikolaas N. Oosterhof <nikolaas.oosterhof 
|at| unitn.it> under the Creative Commons CC0 1.0 Universal Public Domain 
Dedication ("CC0"). See the LICENSE file for details, or visit 
http://creativecommons.org/publicdomain/zero/1.0/deed.en.

Contact
-------
Nikolaas N. Oosterhof <nikolaas.oosterhof |at| unitn.it>


% resolution parameter for input surfaces
% 64 is for high-quality results; use 16 for fast execution
surface_ld=16;

% Define twin surface filenames (FreeSurfer)
pial_fn=fullfile(digit_study_path,...
                            sprintf('ico%d_mh.pial_al.asc', surface_ld));
white_fn=fullfile(digit_study_path,...
                            sprintf('ico%d_mh.smoothwm_al.asc', surface_ld));


% read the surface in pial_fn using surfing_read, and assign the vertices
% and faces to variables pial_v and pial_f, respectively
% >@@>
[pial_v, pial_f] = surfing_read(pial_fn);
% <@@<

fprintf('The pial surface has %d vertices, %d faces\n',...
            size(pial_v,1), size(pial_f,1))

% do the same for the white_fn, assign the faces and vertices to
% white_v and white_f
% >@@>
[white_v, white_f] = surfing_read(white_fn);
% <@@<

fprintf('The white surface has %d vertices, %d faces\n',...
            size(pial_v,1), size(pial_f,1))

% verify that the face information in pial_f and white_f are the same
assert(isequal(white_f, pial_f));

% show the content of the surfaces
fprintf('pial_v\n');
cosmo_disp(pial_v)

fprintf('pial_f\n');
cosmo_disp(pial_f)

fprintf('white_v\n');
cosmo_disp(white_v)

fprintf('white_f\n');
cosmo_disp(white_f)
The pial surface has 5124 vertices, 10240 faces
The white surface has 5124 vertices, 10240 faces
pial_v
[ -32.3      19.8      41.4         
  -33.9      37.9       114         
  -27.9     -16.3       112         
    :         :          :          
   59.2       1.4      64.1         
   50.2     0.804      72.3         
   56.1      5.38      70.6 ]@5124x3
pial_f
[       43         1        13          
        58        43        13          
        43        58        44          
      :         :         :             
   4.9e+03  5.12e+03  4.55e+03          
   4.9e+03   4.9e+03  5.12e+03          
  4.55e+03  2.57e+03   4.9e+03 ]@10240x3
white_v
[ -36.4      23.6      38.6         
  -33.5      36.9       112         
  -27.5     -14.4       111         
    :         :          :          
   56.3      1.56      63.7         
   48.9      2.18      69.1         
   53.9      5.02      67.9 ]@5124x3
white_f
[       43         1        13          
        58        43        13          
        43        58        44          
      :         :         :             
   4.9e+03  5.12e+03  4.55e+03          
   4.9e+03   4.9e+03  5.12e+03          
  4.55e+03  2.57e+03   4.9e+03 ]@10240x3

Part 1: compute thickness of the cortex

% compute the element-wise difference in coordinates between pial_v
% and white_v, and assign to a variable delta
% >@@>
delta = pial_v - white_v;
% <@@<

% square the differences element-wise, and assign to delta_squared.
% hint: use ".^2"
% >@@>
delta_squared = delta .^ 2;
% <@@<

% compute the thickness squared, by summing the elements in
% delta_squared along the second dimension. Assign to thickness_squared
% >@@>
thickness_squared = sum(delta_squared,2);
% <@@<

% finally compute the thickness by taking the square root, assign
% the result to thickness
% >@@>
thickness = sqrt(thickness_squared);
% <@@<

plot a histogram of the thickness values >@@>

hist(thickness,100);
xlabel('thickness (mm)');
% <@@<

For visualization purposes, read inflated surface

inflated_fn=fullfile(digit_study_path,...
                            sprintf('ico%d_mh.inflated_alCoMmedial.asc', surface_ld));
[infl_v,infl_f]=surfing_read(inflated_fn);
fprintf('The inflated surface has %d vertices, %d faces\n',...
            size(infl_v,1), size(infl_f,1))
The inflated surface has 5124 vertices, 10240 faces

visualize surface in Matlab using AFNI Matlab toolbox

nvertices=size(infl_v,1);

min_thickness=1;
max_thickness=4;
show_edge=false;

opt=struct();
opt.ShowEdge=show_edge;
opt.Zlim=[min_thickness,max_thickness]; % this does not seem to work
opt.Dim='3D';

if show_edge
    t='with edges';
else
    t='without edges';
end

desc='thickness';
header=strrep([desc ' ' t],'_',' ');

range_adj_thickness=max(min_thickness,...
                        min(thickness,max_thickness));

DispIVSurf(infl_v,infl_f,1:nvertices,...
                        range_adj_thickness,0,opt);
title(sprintf('Inflated %s', header));
DispIVSurf verbose: z-buffer mode.
DispIVSurf verbose: Scaling ITvect to fit colormap
DispIVSurf verbose: Set hold to off
DispIVSurf verbose: Drawing Patches
DispIVSurf verbose: Applied interp for Shade.
DispIVSurf verbose: Set Edgecolor to none
DispIVSurf verbose: Set view to 3D
DispIVSurf verbose: Displaying colormap
DispIVSurf verbose: Seting Axes properties
DispIVSurf verbose: Done
ds_thickness=struct();
ds_thickness.fa.node_indices=1:nvertices;
ds_thickness.samples=thickness(:)';
ds_thickness.a.fdim.labels={'node_indices'};
ds_thickness.a.fdim.values={(1:nvertices)'};

% July 2019: strangely enough these gifti files cannot be read by AFNI SUMA.
% https://github.com/CoSMoMVPA/CoSMoMVPA/issues/186
if cosmo_check_external('gifti',false)
    output_fn=fullfile(config.output_data_path,'thickness.gii');
    cosmo_map2surface(ds_thickness,output_fn,'encoding','ASCII');
end

% AFNI output
output_fn=fullfile(config.output_data_path,'thickness.niml.dset');
cosmo_map2surface(ds_thickness,output_fn);

Part 2: run surface-based searchlight

% Load volumetric functional data
data_path=digit_study_path;
data_fn=fullfile(data_path,'glm_T_stats_perblock+orig');

% set targets
targets=repmat(1:2,1,16)';    % class labels: 1 2 1 2 1 2 1 2 1 2 ... 1 2
chunks=floor(((1:32)-1)/8)+1; % run labels:   1 1 1 1 1 1 1 1 2 2 ... 4 4

% load functional data
ds = cosmo_fmri_dataset(data_fn,'targets',targets,'chunks',chunks);

% remove zero elements
zero_msk=all(ds.samples==0,1);
ds = cosmo_slice(ds, ~zero_msk, 2);

fprintf('Dataset has %d samples and %d features\n', size(ds.samples));

% print dataset
fprintf('Dataset input:\n');
cosmo_disp(ds);
Dataset has 32 samples and 168097 features
Dataset input:
.sa                                                                       
  .labels                                                                 
    { 'fi_i_R1_B01#0_Tstat'                                               
      'fi_m_R1_B01#0_Tstat'                                               
      'fi_i_R1_B02#0_Tstat'                                               
               :                                                          
      'fi_m_R4_B03#0_Tstat'                                               
      'fi_i_R4_B04#0_Tstat'                                               
      'fi_m_R4_B04#0_Tstat' }@32x1                                        
  .stats                                                                  
    { 'Ttest(168)'                                                        
      'Ttest(168)'                                                        
      'Ttest(168)'                                                        
           :                                                              
      'Ttest(168)'                                                        
      'Ttest(168)'                                                        
      'Ttest(168)' }@32x1                                                 
  .targets                                                                
    [ 1                                                                   
      2                                                                   
      1                                                                   
      :                                                                   
      2                                                                   
      1                                                                   
      2 ]@32x1                                                            
  .chunks                                                                 
    [ 1                                                                   
      1                                                                   
      1                                                                   
      :                                                                   
      4                                                                   
      4                                                                   
      4 ]@32x1                                                            
.a                                                                        
  .vol                                                                    
    .mat                                                                  
      [ -2.5         0         0      71.2                                
           0      -2.5         0       112                                
           0         0       2.5        43                                
           0         0         0         1 ]                              
    .dim                                                                  
      [ 61        84        41 ]                                          
    .xform                                                                
      'scanner_anat'                                                      
  .fdim                                                                   
    .labels                                                               
      { 'i'                                                               
        'j'                                                               
        'k' }                                                             
    .values                                                               
      { [ 1         2         3  ...  59        60        61 ]@1x61       
        [ 1         2         3  ...  82        83        84 ]@1x84       
        [ 1         2         3  ...  39        40        41 ]@1x41 }     
.samples                                                                  
  [     0         0         0  ...  -0.878     0.178      4.66            
        0         0         0  ...    2.48      -0.3      1.12            
        0         0         0  ...   -1.05    -0.707      1.04            
      :         :         :            :         :         :              
    -2.21    -0.243    -0.377  ...   -1.59         1     0.987            
    -1.97      1.06     0.673  ...   -1.23      1.41     0.999            
     -4.5      1.16      1.53  ...    1.86     -1.31     0.405 ]@32x168097
.fa                                                                       
  .i                                                                      
    [ 1         2         3  ...  59        60        61 ]@1x168097       
  .j                                                                      
    [ 1         1         1  ...  84        84        84 ]@1x168097       
  .k                                                                      
    [ 3         3         3  ...  36        36        36 ]@1x168097       

set measure arguments

Assign to measure a function handle to cosmo_cross_validation_measure >@@>

measure = @cosmo_crossvalidation_measure;
% <@@<

% use as arguments for the measure:
% - classifier: cosmo_classify_naive_bayes
% - partitions: odd-even partitions
% assign these to a struct measure_args
% >@@>
measure_args = struct();
measure_args.classifier = @cosmo_classify_naive_bayes;
measure_args.partitions = cosmo_oddeven_partitioner(ds);
% <@@<

Set neighborhood parameters Make a cell with the outer surface vertices (pial_v), the inner surface vertices (white_v), and the face indices (pial_f or white_f). Assign to a variable surface_def >@@>

surface_def={pial_v,white_v,pial_f};
% <@@<

% Define a surface-based neighborhood (using cosmo_surficial_neighborhood)
% with approximately 100 voxels per searchlight. Assign the result
% to nbrhood.

% >@@>
nbrhood=cosmo_surficial_neighborhood(ds,surface_def,'count',100);
% <@@<

% visualize the neighborhood using cosmo_disp.
% What is the feature attribute of the neighborhood?
Warning: found 911 / 5124 center nodes outside the
volume, these will be ignored. 
Using 168097 / 210084 voxels in functional volume mask
+00:00:28 [####################] -00:00:00  r=21.61, 100.0 vox

run surface-based searchlight using the variables define above Assign the result to ds_sl >@@>

ds_sl=cosmo_searchlight(ds,nbrhood,measure,measure_args);
% <@@<
+00:00:11 [####################] -00:00:00  

plot surface in Matlab using AFNI Matlab toolbox

nvertices=size(infl_v,1);
show_edge=false;

opt=struct();
opt.ShowEdge=show_edge;
opt.Dim='3D';

if show_edge
    t='with edges';
else
    t='without edges';
end

desc='classification accuracy';
header=strrep([desc ' ' t],'_',' ');

DispIVSurf(infl_v,infl_f,1:nvertices,...
                        ds_sl.samples',0,opt);
title(sprintf('Inflated %s', header));

% July 2019: strangely enough these gifti files cannot be read by AFNI SUMA.
% https://github.com/CoSMoMVPA/CoSMoMVPA/issues/186
if cosmo_check_external('gifti',false)
    output_fn=fullfile(config.output_data_path,'digit_accuracy.gii');
    cosmo_map2surface(ds_sl,output_fn,'encoding','ASCII');
end

% AFNI output
output_fn=fullfile(config.output_data_path,'digit_accuracy.niml.dset');
cosmo_map2surface(ds_sl,output_fn);

% Show citation information
cosmo_check_external('-cite');
DispIVSurf verbose: z-buffer mode.
DispIVSurf verbose: Scaling ITvect to fit colormap
DispIVSurf verbose: Set hold to off
DispIVSurf verbose: Drawing Patches
DispIVSurf verbose: Applied interp for Shade.
DispIVSurf verbose: Set Edgecolor to none
DispIVSurf verbose: Set view to 3D
DispIVSurf verbose: Displaying colormap
DispIVSurf verbose: Seting Axes properties
DispIVSurf verbose: Done
Warning: A value of class "char" was indexed with no
subscripts specified. Currently the result of this
operation is the indexed value itself, but in a future
release, it will be an error. 
Warning: A value of class "char" was indexed with no
subscripts specified. Currently the result of this
operation is the indexed value itself, but in a future
release, it will be an error. 
Warning: A value of class "char" was indexed with no
subscripts specified. Currently the result of this
operation is the indexed value itself, but in a future
release, it will be an error. 
Warning: A value of class "char" was indexed with no
subscripts specified. Currently the result of this
operation is the indexed value itself, but in a future
release, it will be an error. 
If you use CoSMoMVPA and/or some other toolboxes for a publication, please cite:

J. Shen. NIFTI toolbox. available online from http://www.mathworks.com/matlabcentral/fileexchange/8797-tools-for-nifti-and-analyze-image

N. N. Oosterhof, T. Wiestler, J. Diedrichsen (2011). A comparison of volume-based and surface-based multi-voxel pattern analysis. Neuroimage 56 (2), 593-600. Surfing toolbox available online from http://github.com/nno/surfing

Z. Saad, G. Chen. AFNI Matlab library. available online from https://github.com/afni/AFNI

G. Flandin. GIfTI library for matlab. available online from www.artefact.tk/software/matlab/gifti

Gabriel Peyre. Toolbox Fast Marching - A toolbox Fast Marching and level sets computations [https://www.ceremade.dauphine.fr/~peyre/matlab/fast-marching/content.html]. toolbox fast marching [included in surfing] available online from http://github.com/nno/surfing

N. N. Oosterhof, A. C. Connolly, J. V. Haxby (2016). CoSMoMVPA: multi-modal multivariate pattern analysis of neuroimaging data in Matlab / GNU Octave. Frontiers in Neuroinformatics, doi:10.3389/fninf.2016.00027.. CoSMoMVPA toolbox available online from http://cosmomvpa.org

The Mathworks, Natick, MA, United States. Matlab 9.1.0.441655 (R2016b) (September 7, 2016). available online from http://www.mathworks.com