Contents

addpath('/home/bogdan/.matlab/spm/spm12');
addpath(genpath('/home/bogdan/.matlab/canlab/CanlabCore'));
addpath(genpath('/home/bogdan/.matlab/canlab/Neuroimaging_Pattern_Masks'));
addpath(genpath('/home/bogdan/.matlab/canlab/canlab_single_trials'));
addpath(genpath('/home/bogdan/.matlab/canlab/canlab_single_trials_private'));

Introduction

Let's assume you've run a volumetric analysis registered to a MNI template and you want to render this to a surface. This process is complex.

3D graphics are represented as teselations, a mixture of vertex coordintaes and edges connecting adjacent vertices. Consider the central sulcus for instance, you may have multiple vertices in close proximity on opposite sulcal banks but these should not be connected, while proximal vertices on the same sulcal bank should be. That's what the tesellation achieves. To render from a volumetric image to a surface then you need a tesellation model of that surface along with vertex coordinates that coincide with your volumetric data. This is what freesurfer produces when you run surface segmentation: it generates a subject specific surface tesselation where the surface vertices overlap with coordinates in the volumetric nifti file from which the surface was generated. This allows direct sampling of volumetric data (e.g. BOLD timeseries aligned to the T1 used for surface modeling) to the surface, +/- interpolation among neighboring voxels.

You can easily resample data from a T1 to its freesurfer surface model (although out of scope for this tutorial, look into the mri_surf2vol CLI), but once you've transformed your subject specific image into MNI space for group analysis you lose this possibility. There are group surface templates like the Conte69 template used by freesurfer and HCP, but these surfaces do not have vertices that correspond in any systematic way with those of any MNI template. In fact the radii aren't even the same as the MNI surface cortical ribbon. Most surface rendering options in canlab tools presume that the surface models used (most often BigBrain from the Julich institute) have vertices that overlap with MNI template gray matter ribbons, but this is not the case. Consequently the renderings are not veritical, not even in the assymptotic limit of infinite participants or infinite studies. They're systematically wrong. Thankfully there are principled ways to map from MNI space to a surface model which are built into canlab tools, if you know where to look.

Direct rendering to a MNI template surface segmentation

The simplest approach is to use freesurfer to segment a MNI template and then do a direct mapping from your MNI space brains to that template. As long as your segmentation is applied to the same template you used to do spatial normalization of your data this will work. Note however that there are multiple MNI templates, and they have different radii. Most often we're working with either MNI152NLin6Asym (the FSL standard, and widely used until the late 2010s) or MNI152NLin2009cAsym (the freesurfer default and now most widely used template). Let's see what this looks like. We're going to use paingen data because I (BP) know what template was used for this dataset during spatial normalization. Unfortunately this is still in canlab_single_trials_private, so if you may not be able to access it. If that's the case follow along using a different dataset from canlab_single_trials like bmrk3pain.

% load a dataset. If you don't have access to canlab_single_trials_private
% try this instead:
% img = load_image_set('bmrk3pain');
img = load_image_set('paingen_placebo');

% now let's get a mean image to work with.
mean_img = mean(img);

% We can plot it in traditional fashion, deleting the annoying color bar
% overlay. At the top you'll see a mapping to BigBrain
o2 = mean_img.montage('full2');
delete(o2.activation_maps{1}.legendhandle)
Loading: /mnt/external/MyDocuments/canlab/single_trials/_private/paingen_placebo_data.nii.gz
Using default mask: /home/bogdan/.matlab/canlab/CanlabCore/CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/brainmask_canlab.nii
Direct calls to spm_defauts are deprecated.
Please use spm('Defaults',modality) or spm_get_defaults instead.
iimg_get_data: loading mask. mapping volumes. 
checking that dimensions and voxel sizes of volumes are the same. 
Pre-allocating data array. Needed: 2833772340 bytes
Loading image number:  7077
Elapsed time is 27.126082 seconds.
Re-zipping image files.
fmri_data.create: Converting 325542 NaNs to 0s.Number of unique values in dataset: 132959407  Bit rate: 26.99 bits
Number of unique values in dataset: 100001  Bit rate: 16.61 bits
 
Source: Info about image source here
 
 
Summary of dataset
______________________________________________________
Images: 7077	Nonempty: 7077	Complete: 7076
  Images missing >50% of voxels:   0	
Number of unique values in dataset: 132959407  Bit rate: 26.99 bits
Warning: Number of unique values in dataset is low, indicating possible restriction of bit rate. For comparison, Int16 has 65,536 unique values
  Images missing >10% of voxels:   0	
Voxels: 100105	Nonempty (1+ images have valid data): 100059	Complete  (all images have data): 99945
Unique data values: 132959406
 
Min: -224.738	Max: 250.302	Mean: -0.015	Std: 1.949
 
    Percentiles     Values  
    ___________    _________

        0.1          -11.241
        0.5          -6.7189
          1          -5.3077
          5          -2.8238
         25         -0.91361
         50        -0.010353
         75           0.8902
         95            2.783
         99           5.2087
       99.5           6.5831
       99.9           11.079

 
Saved desc.coverage_obj_binned with maps of number of valid images,
and desc.coverage_obj_binned with values of 100=100% valid images, 80=80-99.9% valid, 50=50-80% valid, and 1=>50% valid images
Warning: Some voxels have data for 1+ images but not for all images
Intensity ratings in image_obj.Y
Additional metadata, including unpleasantness ratings and reaction times (RT), in image_obj.metadata_table
Loaded images:
Number of unique values in dataset: 100001  Bit rate: 16.61 bits
Setting up fmridisplay objects
Compressed NIfTI files are not supported.
    35

Warning: Connot determine orientation of image_metadata > is_timeseries > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_single_trial_series
> . Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_first_level_maps >
. Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_MNI_space > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_HP_filtered > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > covariates_removed > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > TR_in_sec > . Assuming
rows are trials.\n 
Warning: Connot determine orientation of image_metadata > HP_filter_cutoff_sec >
. Assuming rows are trials.\n 
sagittal montage: 21798 voxels displayed, 78261 not displayed on these slices
coronal montage: 21115 voxels displayed, 78944 not displayed on these slices
axial montage: 28573 voxels displayed, 71486 not displayed on these slices

We can instead reader to a MNI surface using a minimally documented feature (the lack of documentation is intentional) like so,

figure;
o2 = canlab_results_fmridisplay('MNI152NLin2009cAsym midthickness');
o2 = mean_img.montage(o2);
Setting up fmridisplay objects
Compressed NIfTI files are not supported.
Using existing fmridisplay object
Warning: Connot determine orientation of image_metadata > is_timeseries > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_single_trial_series
> . Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_first_level_maps >
. Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_MNI_space > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_HP_filtered > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > covariates_removed > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > TR_in_sec > . Assuming
rows are trials.\n 
Warning: Connot determine orientation of image_metadata > HP_filter_cutoff_sec >
. Assuming rows are trials.\n 

The plotting method used above is the same one used for BigBrain: direct sampling of volumetric voxels at the coordinates of the surface mech, but unlike BigBrain this is valid here because the volumetric data is registered to the same brain this surface is segmented from. The difference when comparing with BigBrain isn't immediately obvious. Check out what happens if we use the same approach with freesurfer surfaces though.

figure;
o2 = canlab_results_fmridisplay('freesurfer inflated');
o2 = mean_img.montage(o2);
Setting up fmridisplay objects
Compressed NIfTI files are not supported.
Using existing fmridisplay object
Warning: Connot determine orientation of image_metadata > is_timeseries > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_single_trial_series
> . Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_first_level_maps >
. Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_MNI_space > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_HP_filtered > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > covariates_removed > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > TR_in_sec > . Assuming
rows are trials.\n 
Warning: Connot determine orientation of image_metadata > HP_filter_cutoff_sec >
. Assuming rows are trials.\n 

MNISurf: Volume to Surface projection using default mappings

There are better ways to plot from MNI space to surfaces than direct vertex sampling based on the surface mesh coordinates. One generic method is known as MNISurf and is invoked in canlab core by specifying source spaces and target surfaces,

figure;
o2 = mean_img.montage(o2,'sourcespace','MNI152NLin2009cAsym','targetsurface','fsaverage_164k');

% The projection above was obtained by getting a voxelwise mapping from
% MNI152NLin2009cAsym space to the segmented MNI surface, aligning the MNI
% surface to the Conte69 surface in freesurfer and concatenating the
% transformations. The result is saved in a file in CanlabCore and is used
% when appropriate target and source surfaces are designated. The mapping
% isn't based on the location of the vertices of the freesurfer surface,
% but rather on their index. This is important because the vertex locations
% differ for different levels of surface inflation, but indices do not.
% They always correspond 1-to-1 across the same surface at whatever level
% of inflation you pick.
%
% The documentation for this available in render_on_surface, which is
% invoked at the bottom of the stack by higher level functions like
% image_vector/montage.
help render_on_surface
Using existing fmridisplay object
Warning: Connot determine orientation of image_metadata > is_timeseries > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_single_trial_series
> . Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_first_level_maps >
. Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_MNI_space > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_HP_filtered > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > covariates_removed > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > TR_in_sec > . Assuming
rows are trials.\n 
Warning: Connot determine orientation of image_metadata > HP_filter_cutoff_sec >
. Assuming rows are trials.\n 
--- help for image_vector/render_on_surface ---

  Map voxels in obj to isosurface (patch) handles in han and change surface colors according to values in obj
 
  :Usage:
  ::
 
      cm = render_on_surface(obj, han, [optional inputs])
 
  - Object can be thresholded or unthresholded get_data_range_image, or other image_vector object
  - Uses only the first image in obj
 
  ..
      Author and copyright information:
 
      Copyright (C) 2020 Tor Wager
 
      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
 
      This program is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
 
      You should have received a copy of the GNU General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>.
  ..
 
  :Inputs:
 
    **obj:**
        - image_vector object or subclasses
        - e.g., fmri_data, thresholded or unthresholded statistic_image obj
        - Uses only the first image in obj
 
    **han:**
        - A vector of one or more handles to patch objects (from surfaces)
        - e.g., from patch(isosurface()), patch(isocaps()),
        fmri_data.surface(), fmri_data.isosurface()
        - Try: surface_handles = addbrain('left_cutaway');
        - Try: surface_handles = addbrain('coronal_slabs_4');
 
  :Optional Inputs:
    **'clim':**
         Followed by [lowerlimit upperlimit] values to define color limits
 
    **'colormap':**
         Followed by the name of a Matlab colormap string
         e.g., 'hot' (default), 'summer', 'winter'
         - Creates a split colormap with gray values where object values
         are zero, and colors where object values are nonzero
 
    **splitcolor':**
         Positive and negative values are mapped to different
         colormaps. Default is +=hot, -=cool colors.  Followed
         optionally by cell array with vectors of 4 colors defining
         max/min for +/- range, e.g., {[0 0 1] [.3 0 .8] [.8 .3 0] [1 1
         0]}. This is ignored if pos_colormap or neg_colormap are
         specified.
 
    **'indexmap':**
        Interpret data as indices into colormap. You must specify
        'colormap' argument as well, otherwise this does nothing and a
        warning is thrown. If specified, 'nearest' neighbor interpolation
        is used automatically for volume to surface projection and 'interp'
        option is ignored.
 
    **'labels':**
        Append cell-array or char/string labels to indexmap.
 
    **'scaledtransparency':**
        Transparency is a function of voxel value, lower values are more transparent. This will look very
        strange without an underlay. To create an underlay render a two duplicate surfaces and invoke
        render_on_surface only on the second duplicate.
    
     **'transcontrast':** 
        If scaledtransparency is used transparency alpha values are a linear function of the data. This 
        option makes them a sigmoidal function of the data with higher values disproportionately opaque and
        lower values disproportionately transparent. This argument is followed by a scalar value which 
        specifies how steep the sigmoidal part of the contrast curve should be. It works like a contrast 
        curve in photoshop, but only affects alpha transparency, not colormapping, and therefore does not 
        alter the data being shown.
 
     **'interp':**
        Interpolation method to use when projecting from volume to surface. Options: 'nearest', 'linear'. 
        If 'indexmap' is specified this option is ignored, and 'nearest' is used automatically.
 
     **'sourcespace':**
        If specified together with a targetsurface then nonlinear mapping between the source volume and the 
        target surface is performed according to the MNIsurf procedure described in Wu, Ngo, Greve et al. (2018)
        Neuroimage. This is something you absolutely want to do if your sourcespace and targetsurface are
        supported.
        Supported sourcespaces = {'MNI152NLin2009cAsym','MNI152NLin6Asym','colin27'}
 
     **'srcdepth':**
  	Requires 'sourcespace' specification, and allows further specify desired surface depth to sample volume 
        at. Typical options might be 'pial', 'white', or 'midthickness'. Requires that a file named 
        '<sourename>_<srcdepth>_lh.mat' and '<sourename>_<srcdepth>_rh.mat' be in your matlab path. The pial surface 
        and even midthickness will undersample from deep sulci, but the white surface may conversely suffer from 
        partial volume effects if you've masked white matter out of your volumetric data. srcdepth can also be a cell 
        array, in which case multiple depths are sampled and averaged (for linear interpolation) or the mode is taken 
        (for nearest neighbor interpolation). If there is a modal tie, the default is to use whichever belongs first 
        in your srcdepth list. Default: {'midthickness','pial','white'}, i.e. midthickness > pial > white for modal 
        tie breaks.
 
     **'targetsurface':**
        If specified together with a targetsurface then nonlinear mapping between the source volume and the 
        target surface is performed according to the MNIsurf procedure described in Wu, Ngo, Greve et al. (2018)
        Neuroimage. This is something you absolutely want to do if your sourcespace and targetsurface are
        supported.
        Supported targetsurface = {'fsLR_32k', 'fsaverage_164k'}
 
    
 
  :Outputs:
 
    **renders colors on surfaces:**
    Note that it is not always possible to reset the surfaces to their
    original colors completely; you may have to redraw them if you change
    the threshold/voxels that should be colored.
 
  :Examples:
  ::
 
  % Load a standard dataset (emotion regulation test data)
  % Perform a t-test, and render the results on a series of coronal slabs
 
  imgs = load_image_set('emotionreg', 'noverbose');
  t = ttest(imgs, .01, 'unc');
  figure; han = addbrain('coronal_slabs_4');
 
  % When the t map has positive and negative values, creates a special
  % bicolor split colormap that has warm colors for positive vals, and cool colors
  % for negative vals:
  render_on_surface(t, han);
 
  % You can set the color limits (here, in t-values)
  render_on_surface(t, han, 'clim', [-4 4]);
 
  % You can set the colormap to any Matlab colormap:
  render_on_surface(t, han, 'colormap', 'summer');
 
  % If your image is positive-valued only (or negative-valued only), a
  % bicolor split map will not be created:
 
  t = threshold(t, [2 Inf], 'raw-between');
  render_on_surface(t, han, 'colormap', 'winter', 'clim', [2 6]);
 
  Note:
  To erase rendered blobs, use:
  sh = addbrain('eraseblobs', sh);

as you can see, multiple source spaces are available, and two target surfaces. BigBrain does not yet exist because a surface segmentation of BigBrain is not available. There are some esoteric difficulties associated with this particular brain. You can also select what depth you want to sample your data from. Usually midthickness is the default in most packages, but for visualization here the default is to sample at 3 depths and average the results.

figure;
o2 = canlab_results_fmridisplay('freesurfer inflated');
o2 = mean_img.montage(o2,'sourcespace','MNI152NLin2009cAsym','targetsurface','fsaverage_164k','srcdepth','pial');


figure;
o2 = canlab_results_fmridisplay('freesurfer inflated');
o2 = mean_img.montage(o2,'sourcespace','MNI152NLin2009cAsym','targetsurface','fsaverage_164k','srcdepth','white');

% Generally the default srcdepth argument is adequate.
%
% There are better methods of volume to surface mapping though. The best
% method is known as registration fusion, and is available in the neuromaps
% python package, but is a bit more difficult to implement so it hasn't
% been added yet to canlab core. For more details on these methods,
% including registration fusion, refer to Wu et al. 2018 Human Brain
% Mapping. doi: 10.1002/hbm.24213
%
% It's important to note that this kind of projection of paingen ata to
% surfaces from volumetric space is not the same as doing your analysis in
% surface space to begin with. The latter results in better intersubject
% alignment, larger activation foci, and stronger effects in the final
% surface projection. Surface projection is simply a handy way of
% visualizing volumetric data, not a way to benefit from the analytic
% advantages of surface analysis.
Setting up fmridisplay objects
Compressed NIfTI files are not supported.
Warning: Unknown input string option:srcdepth 
Warning: Unknown input string option:pial 
Using existing fmridisplay object
Warning: Connot determine orientation of image_metadata > is_timeseries > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_single_trial_series
> . Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_first_level_maps >
. Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_MNI_space > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_HP_filtered > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > covariates_removed > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > TR_in_sec > . Assuming
rows are trials.\n 
Warning: Connot determine orientation of image_metadata > HP_filter_cutoff_sec >
. Assuming rows are trials.\n 
Setting up fmridisplay objects
Compressed NIfTI files are not supported.
Warning: Unknown input string option:srcdepth 
Warning: Unknown input string option:white 
Using existing fmridisplay object
Warning: Connot determine orientation of image_metadata > is_timeseries > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_single_trial_series
> . Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_first_level_maps >
. Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_MNI_space > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > is_HP_filtered > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > covariates_removed > .
Assuming rows are trials.\n 
Warning: Connot determine orientation of image_metadata > TR_in_sec > . Assuming
rows are trials.\n 
Warning: Connot determine orientation of image_metadata > HP_filter_cutoff_sec >
. Assuming rows are trials.\n 

Atlas mapping to surface

Several atlases have been defined in volumetric space with particular care for alignment. These atlas have predefined sourcespaces, and there's some built in awareness of target surfaces, so these will automatically render correctlly. Consider canlab2024,

canlab2024 = load_atlas('canlab2024_fsl6');
disp(canlab2024.space_description)
Loading atlas: CANLab2024_MNI152NLin6Asym_coarse_2mm_atlas_object.mat
MNI152NLin6Asym
canlab2024 = load_atlas('canlab2024');
disp(canlab2024.space_description)
Loading atlas: CANLab2024_MNI152NLin2009cAsym_coarse_2mm_atlas_object.mat
MNI152NLin2009cAsym
canlab2024 = canlab2024.threshold(0.2); % let's threshold for a better gray matter ribbon

% It's available in multiple spaces, but you don't need to worry about
% wihch for rendering purposes because it's recorded internally.
figure;
canlab2024.montage('freesurfer inflated')
Keeping probability_map values above 0.20
Compressed NIfTI files are not supported.

ans = 

  fmridisplay with properties:

            overlay: '/home/bogdan/.matlab/canlab/CanlabCore/CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/fmriprep20_template.nii.gz'
              SPACE: [1×1 struct]
    activation_maps: {[1×1 struct]}
            montage: {}
            surface: {[1×1 struct]  [1×1 struct]  [1×1 struct]  [1×1 struct]}
          orthviews: {}
            history: {}
    history_descrip: []
    additional_info: ''

It's available in multiple spaces, but you don't need to worry about wihch for rendering purposes because it's recorded internally. However, we can overwrite it, which is useful to illustrate the kinds of subtle problems that arise when you're missampling surfaces, ones that are much easier to identify by eye in discrete parcellations than continuous activation maps.

figure;
canlab2024.montage('sourcespace',[], 'freesurfer inflated');
Compressed NIfTI files are not supported.
Warning: target surface specified without source space. Please specify a
sourcespace argument to perform accurate surface projection. You have these
options:, 
Warning: sourcespace = colin27
sourcespace = MNI152NLin6Asym
sourcespace = MNI152NLin2009cAsym
 
Warning: target surface specified without source space. Please specify a
sourcespace argument to perform accurate surface projection. You have these
options:, 
Warning: sourcespace = colin27
sourcespace = MNI152NLin6Asym
sourcespace = MNI152NLin2009cAsym
 
Warning: target surface specified without source space. Please specify a
sourcespace argument to perform accurate surface projection. You have these
options:, 
Warning: sourcespace = colin27
sourcespace = MNI152NLin6Asym
sourcespace = MNI152NLin2009cAsym
 
Warning: target surface specified without source space. Please specify a
sourcespace argument to perform accurate surface projection. You have these
options:, 
Warning: sourcespace = colin27
sourcespace = MNI152NLin6Asym
sourcespace = MNI152NLin2009cAsym
 

even on big brain, which in the case of our contrast map above looked decent, the atlas does not look good without proper surface projection

figure;
canlab2024.montage('full2')
Compressed NIfTI files are not supported.
    35

Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
sagittal montage: 21561 voxels displayed, 121672 not displayed on these slices
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
coronal montage: 21448 voxels displayed, 121785 not displayed on these slices
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
axial montage: 29756 voxels displayed, 113477 not displayed on these slices
Warning: Source space specified without target surface. Please specify a
targetsurface argument to perform accurate surface projection. You have these
options, 
Warning: targetsurface = fsaverage_164k
targetsurface = fsLR_32k
 
Warning: Source space specified without target surface. Please specify a
targetsurface argument to perform accurate surface projection. You have these
options, 
Warning: targetsurface = fsaverage_164k
targetsurface = fsLR_32k
 
Warning: Source space specified without target surface. Please specify a
targetsurface argument to perform accurate surface projection. You have these
options, 
Warning: targetsurface = fsaverage_164k
targetsurface = fsLR_32k
 
Warning: Source space specified without target surface. Please specify a
targetsurface argument to perform accurate surface projection. You have these
options, 
Warning: targetsurface = fsaverage_164k
targetsurface = fsLR_32k
 

ans = 

  fmridisplay with properties:

            overlay: '/home/bogdan/.matlab/canlab/CanlabCore/CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/fmriprep20_template.nii.gz'
              SPACE: [1×1 struct]
    activation_maps: {[1×1 struct]}
            montage: {[1×1 struct]  [1×1 struct]  [1×1 struct]}
            surface: {[1×1 struct]  [1×1 struct]  [1×1 struct]  [1×1 struct]}
          orthviews: {}
            history: {}
    history_descrip: []
    additional_info: ''

it doesn't look any better on hcp surfaces, even though if you look at the volumetric data the atlas follows the gray matter ribbon very closely. You'd never want to publish a figure like this.

figure;
canlab2024.montage('sourcespace', [], 'full hcp')
Compressed NIfTI files are not supported.
    48

Warning: Unknown input string option:fsLR_32k 
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
sagittal montage: 21561 voxels displayed, 121672 not displayed on these slices
Warning: Unknown input string option:fsLR_32k 
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
coronal montage: 21448 voxels displayed, 121785 not displayed on these slices
Warning: Unknown input string option:fsLR_32k 
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
axial montage: 29756 voxels displayed, 113477 not displayed on these slices
Warning: Unknown input string option:fsLR_32k 
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
axial montage: 29576 voxels displayed, 113657 not displayed on these slices
Warning: target surface specified without source space. Please specify a
sourcespace argument to perform accurate surface projection. You have these
options:, 
Warning: sourcespace = colin27
sourcespace = MNI152NLin6Asym
sourcespace = MNI152NLin2009cAsym
 
Warning: target surface specified without source space. Please specify a
sourcespace argument to perform accurate surface projection. You have these
options:, 
Warning: sourcespace = colin27
sourcespace = MNI152NLin6Asym
sourcespace = MNI152NLin2009cAsym
 
Warning: target surface specified without source space. Please specify a
sourcespace argument to perform accurate surface projection. You have these
options:, 
Warning: sourcespace = colin27
sourcespace = MNI152NLin6Asym
sourcespace = MNI152NLin2009cAsym
 
Warning: target surface specified without source space. Please specify a
sourcespace argument to perform accurate surface projection. You have these
options:, 
Warning: sourcespace = colin27
sourcespace = MNI152NLin6Asym
sourcespace = MNI152NLin2009cAsym
 

ans = 

  fmridisplay with properties:

            overlay: '/home/bogdan/.matlab/canlab/CanlabCore/CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/fmriprep20_template.nii.gz'
              SPACE: [1×1 struct]
    activation_maps: {[1×1 struct]}
            montage: {[1×1 struct]  [1×1 struct]  [1×1 struct]  [1×1 struct]}
            surface: {[1×1 struct]  [1×1 struct]  [1×1 struct]  [1×1 struct]}
          orthviews: {}
            history: {}
    history_descrip: []
    additional_info: ''

But with surface projection enabled, the holes in the map go away, and things are a bit closer to being publication ready.

figure;
canlab2024.montage('sourcespace', 'MNI152NLin2009cAsym', 'full hcp')

% These concerns about mismatches between templates, whether surface or
% volumetric, affect atlas parcel definitions. If you look carefully at
% load_atlas' help you'll see that many atlases have options for selecting
% a source space, either fmriprep20, which is MNI152NLin2009cAsym, or fsl6,
% which MNI152NLin6Asym. This is to facilitate working with different data
% types. Atlases that were defined in surface space (e.g. Glasser) were
% projected to their target spaces using registration fusion, so they're a
% bit more accurate than these renderings you see here. For atlases defined
% in surface space you're best off rendering the surface data directly
% rather than taking the volumetric version of the surface data and trying
% to back project it. Something will always be lost in translation.
Compressed NIfTI files are not supported.
    48

Warning: Unknown input string option:fsLR_32k 
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
sagittal montage: 21561 voxels displayed, 121672 not displayed on these slices
Warning: Unknown input string option:fsLR_32k 
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
coronal montage: 21448 voxels displayed, 121785 not displayed on these slices
Warning: Unknown input string option:fsLR_32k 
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
axial montage: 29756 voxels displayed, 113477 not displayed on these slices
Warning: Unknown input string option:fsLR_32k 
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
Warning: More indices were specified than colormap values. Looping colormap 
axial montage: 29576 voxels displayed, 113657 not displayed on these slices
Warning: Size of surface 2 does not match size of target surface. Falling back
to naive mesh interpolation. 
Warning: Size of surface 2 does not match size of target surface. Falling back
to naive mesh interpolation. 
Warning: Size of surface 2 does not match size of target surface. Falling back
to naive mesh interpolation. 
Warning: Size of surface 2 does not match size of target surface. Falling back
to naive mesh interpolation. 

ans = 

  fmridisplay with properties:

            overlay: '/home/bogdan/.matlab/canlab/CanlabCore/CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/fmriprep20_template.nii.gz'
              SPACE: [1×1 struct]
    activation_maps: {[1×1 struct]}
            montage: {[1×1 struct]  [1×1 struct]  [1×1 struct]  [1×1 struct]}
            surface: {[1×1 struct]  [1×1 struct]  [1×1 struct]  [1×1 struct]}
          orthviews: {}
            history: {}
    history_descrip: []
    additional_info: ''

Plotting surface data to surfaces

For the most part surface analysis is not supported by canlabCore, but for the sake of basic visualization I (BP) have started a new repo that currently has a couple of low level functions that may help to do things like plot surface formatted atlases to surface meshes.

addpath(genpath('/home/bogdan/.matlab/canlab/canlabSurface'));

For the most part, Neuroimaging_Pattern_Masks (where all our atlases are), contains all the data needed to regenerate them from source materials. In the case of atlases defined in surface formats like Glasser et al's HCP MMP 1.0, that means the surface files are available, and you can render to surface meshes that match their vertex indicing.

This latter point is imporant. Vertices are not in the same order across all surfaces, even surfaces that sample the same space like fsaverage and HCP 32k. The corresponding surface should be clear from the filenames though. Let's start with glasser. Navigate to the Glasser atlas folder in Neuroimaging_Pattern_Masks and find the surface (gifti) files, Atlases_and_parcellations/2016_Glasser_Nature_HumanConnectomeParcellation/ You'll see they're called Glasser_2016.32k.[LR].label.gii. 32k indicates the HCP surface, so let's load it. Surfaces can be found in, CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/, and the gifti function comes from spm.

surfL = gifti(which('S1200.L.midthickness_MSMAll.32k_fs_LR.surf.gii'))
surfR = gifti(which('S1200.R.midthickness_MSMAll.32k_fs_LR.surf.gii'))

glasserL = gifti(which('Glasser_2016.32k.L.label.gii'))
glasserR = gifti(which('Glasser_2016.32k.L.label.gii'))
surfL = 

  struct with fields:

       faces: [64980×3 int32]
         mat: [4×4 double]
    vertices: [32492×3 single]


surfR = 

  struct with fields:

       faces: [64980×3 int32]
         mat: [4×4 double]
    vertices: [32492×3 single]


glasserL = 

  struct with fields:

     cdata: [32492×1 int32]
    labels: [1×1 struct]


glasserR = 

  struct with fields:

     cdata: [32492×1 int32]
    labels: [1×1 struct]

Notice how gifti files can either contain data or surface information. This separation is a feature because it allows you to sample the same data to multiple surfaces so long as they use the same coordinate system. Here we're sampling to an inflated surface, but you could just as easily sample to a midthickness surface, spherical surface, etc.

First thing we need to do is create an image and render some surfaces.

figure;
clf

ax1 = subplot(2,2,1);
s1 = render_surface(surfL.vertices, surfL.faces, ax1);

ax2 = subplot(2,2,2);
s2 = render_surface(surfR.vertices, surfR.faces, ax2, 'flip');

ax3 = subplot(2,2,3);
s3 = render_surface(surfL.vertices, surfL.faces, ax3, 'flip');

ax4 = subplot(2,2,4);
s4 = render_surface(surfR.vertices, surfR.faces, ax4);

% now let's get a colormap to use when plotting. Surface label files
% contain this information as metadata,

cmapL = glasserL.labels.rgba(:,1:3);
cmapR = glasserR.labels.rgba(:,1:3);

% Now we need to plot data to these surfaces. Note, this function was
% adapted from canlabCore's plot_to_surf. The documentation has not been
% updated, and may not be accurate, but it should look very similarly.
plot_surf_to_surf(glasserL.cdata, s1, 'colormap', cmapL, 'indexmap');
plot_surf_to_surf(glasserR.cdata, s2, 'colormap', cmapR, 'indexmap');
plot_surf_to_surf(glasserL.cdata, s3, 'colormap', cmapL, 'indexmap');
plot_surf_to_surf(glasserR.cdata, s4, 'colormap', cmapR, 'indexmap');

sgtitle('HCP MMP 1.0','FontWeight','bold','fontsize',16);

% You can get a nicer layout by using tiledlayout + nexttile() instead of
% subplots, but I used subplots because it's simpler.

Plotting grayordinate data

Plotting grayordinate data from cifti files isn't all that different from plotting gifti data. The main difficulty arises from extracting subcortical data and plotting it, but we're also going to dispense with manual construction of our plotting layout and just use a convenience function here.

% You can look at the source code for this file you want to create some new
% custom layouts. o2 represents the surface objects while o3 represents the
% subcortical surfaces. This function used tiledlayout instead of subplots
% and t0 is a handle to this layout which lets you modify appearance.
figure;
[o2,o3, t0] = create_compact_hcp_grayordinate_figure('overlay', 'fsl6_hcp_template.nii.gz', 'nocolorbar');

% Now let's import a cifti atlas. If you don't have this atlas you need to
% run create_CANLab2024_atlas_prep.sh and create_CANLab2024_atlas_cifti.sh
% in Neuroimaging_Pattern_Masks/Atlases_and_parcellations/2024_CANLab_atlas/src
% (as well as load_atlas('canlab2024_fsl6'), but if you're following along
% you'll have already invoked that above). You also need cifti_read from
% https://github.com/Washington-University/cifti-matlab
addpath(genpath('/home/bogdan/.matlab/cifti-matlab'));
cifti_obj = cifti_read(which('CANLab2024_MNI152NLin6Asym_coarse_2mm.dlabel.nii'));

% cifti files are saved as xml files + a nifti component for volumetric
% data representation, and it can be a pain to map some components from the
% vectorized representation to their structures. I've created some utility
% functions to facilitate this.
canlab2024_cii = get_cifti_data(which('CANLab2024_MNI152NLin6Asym_coarse_2mm.dlabel.nii'));

% colormaps are stored in very similar format as gifti files
labels = cifti_obj.diminfo{2}.maps.table(2:end);
cmap = zeros(length(labels),3);
for i = 1:length(labels)
    cmap(i,:) = labels(i).rgba(1:3);
end

% extracting subcortical data is quite annoying, so I've created a function
% to facilitate it that returns an fmri_data object.
vol = extract_vol_from_cifti(cifti_obj);

% Now we can plot in similar manner to the gifti images. Note, input must
% be a column vector.
plot_surf_to_surf(canlab2024_cii.cortex_left', o2.surface{1}.object_handle, 'colormap', cmap, 'indexmap');
plot_surf_to_surf(canlab2024_cii.cortex_right', o2.surface{2}.object_handle, 'colormap', cmap, 'indexmap');
plot_surf_to_surf(canlab2024_cii.cortex_left', o2.surface{3}.object_handle, 'colormap', cmap, 'indexmap');
plot_surf_to_surf(canlab2024_cii.cortex_right', o2.surface{4}.object_handle, 'colormap', cmap, 'indexmap');

% now let's plot the subcortical data
n_ctx_rois = unique([canlab2024_cii.cortex_left, canlab2024_cii.cortex_right]);
vol.montage(o3,'indexmap',cmap)

% Now let's add a title that doesn't overlap the plots by manipulating the
% tile layouts y-axis length, and adjust the image width for a more compact
% display
t0.Position(4) = 0.85;

pos = get(gcf,'Position');
set(gcf,'Position',[pos(1:2), 420, 420]);

t = sgtitle('CANLab2024','FontSize',16,'fontweight','bold');
Compressed NIfTI files are not supported.
Compressed NIfTI files are not supported.
Warning: Converting dat field from double to single format 
Number of unique values in dataset: 161  Bit rate: 7.33 bits
Warning: Number of unique values in dataset is low, indicating possible restriction of bit rate. For comparison, Int16 has 65,536 unique values
Using existing fmridisplay object
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
sagittal montage: 726 voxels displayed, 30941 not displayed on these slices
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
sagittal montage: 1016 voxels displayed, 30651 not displayed on these slices
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
sagittal montage: 913 voxels displayed, 30754 not displayed on these slices
Warning: Indexmap requires 'interp','nearest' but these were not specified.
Adding them automatically 
axial montage: 1088 voxels displayed, 30579 not displayed on these slices
Warning: No labels input for indexmap 

ans = 

  fmridisplay with properties:

            overlay: '/home/bogdan/.matlab/canlab/CanlabCore/CanlabCore/canlab_canonical_brains/Canonical_brains_surfaces/fsl6_hcp_template.nii.gz'
              SPACE: [1×1 struct]
    activation_maps: {[1×1 struct]}
            montage: {[1×1 struct]  [1×1 struct]  [1×1 struct]  [1×1 struct]}
            surface: {}
          orthviews: {}
            history: {}
    history_descrip: []
    additional_info: ''