Morph surface source estimate#

This example demonstrates how to morph an individual subject’s mne.SourceEstimate to a common reference space. We achieve this using mne.SourceMorph. Pre-computed data will be morphed based on a spherical representation of the cortex computed using the spherical registration of FreeSurfer (https://surfer.nmr.mgh.harvard.edu/fswiki/SurfaceRegAndTemplates) [1]. This transform will be used to morph the surface vertices of the subject towards the reference vertices. Here we will use ‘fsaverage’ as a reference space (see https://surfer.nmr.mgh.harvard.edu/fswiki/FsAverage).

The transformation will be applied to the surface source estimate. A plot depicting the successful morph will be created for the spherical and inflated surface representation of 'fsaverage', overlaid with the morphed surface source estimate.

Note

For background information about morphing see Morphing and averaging source estimates.

# Author: Tommy Clausner <tommy.clausner@gmail.com>
#
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.
import mne
from mne.datasets import sample

print(__doc__)

Setup paths

data_path = sample.data_path()
sample_dir = data_path / "MEG" / "sample"
subjects_dir = data_path / "subjects"
fname_src = subjects_dir / "sample" / "bem" / "sample-oct-6-src.fif"
fname_fwd = sample_dir / "sample_audvis-meg-oct-6-fwd.fif"
fname_fsaverage_src = subjects_dir / "fsaverage" / "bem" / "fsaverage-ico-5-src.fif"
fname_stc = sample_dir / "sample_audvis-meg"

Load example data

# Read stc from file
stc = mne.read_source_estimate(fname_stc, subject="sample")

Setting up SourceMorph for SourceEstimate#

In MNE, surface source estimates represent the source space simply as lists of vertices (see The SourceEstimate data structure). This list can either be obtained from mne.SourceSpaces (src) or from the stc itself. If you use the source space, be sure to use the source space from the forward or inverse operator, because vertices can be excluded during forward computation due to proximity to the BEM inner skull surface:

src_orig = mne.read_source_spaces(fname_src)
print(src_orig)  # n_used=4098, 4098
fwd = mne.read_forward_solution(fname_fwd)
print(fwd["src"])  # n_used=3732, 3766
print([len(v) for v in stc.vertices])
    Reading a source space...
    Computing patch statistics...
    Patch information added...
    Distance information added...
    [done]
    Reading a source space...
    Computing patch statistics...
    Patch information added...
    Distance information added...
    [done]
    2 source spaces read
<SourceSpaces: [<surface (lh), n_vertices=155407, n_used=4098>, <surface (rh), n_vertices=156866, n_used=4098>] MRI (surface RAS) coords, subject 'sample', ~27.5 MiB>
Reading forward solution from /home/circleci/mne_data/MNE-sample-data/MEG/sample/sample_audvis-meg-oct-6-fwd.fif...
    Reading a source space...
    Computing patch statistics...
    Patch information added...
    Distance information added...
    [done]
    Reading a source space...
    Computing patch statistics...
    Patch information added...
    Distance information added...
    [done]
    2 source spaces read
    Desired named matrix (kind = 3523 (FIFF_MNE_FORWARD_SOLUTION_GRAD)) not available
    Read MEG forward solution (7498 sources, 306 channels, free orientations)
    Source spaces transformed to the forward solution coordinate frame
<SourceSpaces: [<surface (lh), n_vertices=155407, n_used=3732>, <surface (rh), n_vertices=156866, n_used=3766>] head coords, subject 'sample', ~31.0 MiB>
[3732, 3766]

We also need to specify the set of vertices to morph to. This can be done using the spacing parameter, but for consistency it’s better to pass the src_to parameter.

Note

Since the default values of mne.compute_source_morph() are spacing=5, subject_to='fsaverage', in this example we could actually omit the src_to and subject_to arguments below. The ico-5 fsaverage source space contains the special values [np.arange(10242)] * 2, but in general this will not be true for other spacings or other subjects. Thus it is recommended to always pass the destination src for consistency.

Initialize SourceMorph for SourceEstimate

src_to = mne.read_source_spaces(fname_fsaverage_src)
print(src_to[0]["vertno"])  # special, np.arange(10242)
morph = mne.compute_source_morph(
    stc,
    subject_from="sample",
    subject_to="fsaverage",
    src_to=src_to,
    subjects_dir=subjects_dir,
)
    Reading a source space...
    [done]
    Reading a source space...
    [done]
    2 source spaces read
[    0     1     2 ... 10239 10240 10241]
surface source space present ...
Computing morph matrix...
    Left-hemisphere map read.
    Right-hemisphere map read.
    17 smooth iterations done.
    14 smooth iterations done.
[done]
[done]

Apply morph to (Vector) SourceEstimate#

The morph will be applied to the source estimate data, by giving it as the first argument to the morph we computed above.

Plot results#

# Define plotting parameters
surfer_kwargs = dict(
    hemi="lh",
    subjects_dir=subjects_dir,
    clim=dict(kind="value", lims=[8, 12, 15]),
    views="lateral",
    initial_time=0.09,
    time_unit="s",
    size=(800, 800),
    smoothing_steps=5,
)

# As spherical surface
brain = stc_fsaverage.plot(surface="sphere", **surfer_kwargs)

# Add title
brain.add_text(0.1, 0.9, "Morphed to fsaverage (spherical)", "title", font_size=16)
morph surface stc
True

As inflated surface

brain_inf = stc_fsaverage.plot(surface="inflated", **surfer_kwargs)

# Add title
brain_inf.add_text(0.1, 0.9, "Morphed to fsaverage (inflated)", "title", font_size=16)
morph surface stc
True

Reading and writing SourceMorph from and to disk#

An instance of SourceMorph can be saved, by calling morph.save.

This method allows for specification of a filename under which the morph will be save in “.h5” format. If no file extension is provided, “-morph.h5” will be appended to the respective defined filename:

>>> morph.save('my-file-name')

Reading a saved source morph can be achieved by using mne.read_source_morph():

>>> morph = mne.read_source_morph('my-file-name-morph.h5')

Once the environment is set up correctly, no information such as subject_from or subjects_dir must be provided, since it can be inferred from the data and use morph to ‘fsaverage’ by default. SourceMorph can further be used without creating an instance and assigning it to a variable. Instead mne.compute_source_morph() and mne.SourceMorph.apply() can be easily chained into a handy one-liner. Taking this together the shortest possible way to morph data directly would be:

surface source space present ...
Computing morph matrix...
    Left-hemisphere map read.
    Right-hemisphere map read.
    17 smooth iterations done.
    14 smooth iterations done.
[done]
[done]

For more examples, check out examples using SourceMorph.apply.

References#

Total running time of the script: (0 minutes 15.381 seconds)

Gallery generated by Sphinx-Gallery