A new class is born!

Hey together,

I’m proudly announcing that the MNE-Python class family got offspring!

After any API related discussions I can now proudly present the new SourceMorph class in it’s more or less final-ish version.

The class has basically the following features:

# computing the morph by calling the __init__ method 
instance_SM = SourceMorph(SourceSpace, 'brain_from', 'brain_to')

# morphing the data by calling the __call__ method 
morphed_source_estimate = instance_SM(source_estimate)

# output the data as volume by calling the as_volume method 
img = instance_SM.as_volume(morphed_source_estimate)

# saving the morph by calling the save method 
instance_SM.save('fname')

# readin the morph by calling respective IO function
instance_SM = read_source_morph('fname-morph.h5')

This is already pretty cool!

However, the class also infers automatically which type of source estimate should be morphed and is furthermore really flexible in input and output handling. For example as_volume can take an argument called mri_resolution, which can be boolean to morph to mri space or morph space (the resolution of the mris the morph was computed on), but further takes tuple to specify the respective voxel size in mm.

But let’s compute a proper example:

First download the necessary data for example 5 from GitHub. Replace the respective file in your mne root folder and run

python setup.py install to apply the changes and register the new class.

Now for the imports:

import matplotlib.pylab as plt
import nibabel as nib
import numpy as np
from mne import read_evokeds, SourceMorph
from mne.datasets import sample
from mne.minimum_norm import apply_inverse, read_inverse_operator
from nilearn.plotting import plot_anat

Next we load some pre-computed example data:

###############################################################################
# Setup paths
sample_dir = sample.data_path() + '/MEG/sample'
subjects_dir = sample.data_path() + '/subjects'

fname_evoked = sample_dir + '/sample_audvis-ave.fif'
fname_inv = sample_dir + '/sample_audvis-meg-vol-7-meg-inv.fif'

fname_t1_fsaverage = subjects_dir + '/fsaverage/mri/brain.mgz'

###############################################################################
# Compute example data. For reference see
# :ref:`<sphx_glr_auto_examples_inverse_plot_compute_mne_inverse_volume.py>`

# Load data
evoked = read_evokeds(fname_evoked, condition=0, baseline=(None, 0))
inverse_operator = read_inverse_operator(fname_inv)

# Apply inverse operator
stc = apply_inverse(evoked, inverse_operator, 1.0 / 3.0 ** 2, "dSPM")

# To save memory
stc.crop(0.087, 0.087)

If you would like to know more about the respective data and how it was computed, see the MNE Documentation.

… and now comes the magic:

source_morph = SourceMorph(inverse_operator['src'],
                           subject_from='sample',
                           subject_to='fsaverage',
                           subjects_dir=subjects_dir,
                           grid_spacing=(5., 5., 5.))

we set up our morpher by creating a  SourceMorph class. The inputs are our source spaces, the respective subject information, the subjects directory as used by mne and the grid spacing, that is the voxel size in mm that the MRIs of reference are resliced to. Mostly it’s not necessary to compute the morph on the full resolution MRI.

Interestingly the default values of SourceMorph.__init__ are chosen such, that the very same operation above can be achieved by:

source_morph = SourceMorph(inverse_operator['src'])

This is due to the fact, that when everything is set up correctly, subject_from can be inferred from src, subject_to is ‘fsaverage’, subjects_dir is specified in the environment and 5mm iso voxel size for grid_spacing turned out to be a rather good trade off between time and performance.

However, to allow for maximum flexibility and to give the user a wider range of choices for the respective morphing computation, morph type specific arguments can be passed. Those can be for instance the number of iterations or a smoothing factor.

Once we computed our morph, applying it is just as easy:

# Obtain absolute value for plotting
# To not copy the data into a new memory location, out=stc.data is set
np.abs(stc.data, out=stc.data)
# Morph data
stc_fsaverage = source_morph(stc)

That’s it!

And the cool thing: it works as easy for Surface and Vector source estimates!

stc_fsaverage contains now the morphed volume source space. If we would like to create a nifti from this data, we simply can use the as_volume function, in multiple ways:

# full mri resolution nifti (voxel size = voxel size of mri_to)
img = source_morph.as_volume(stc_fsaverage, mri_resolution=True)

# morph resolution nifti (voxel size = grid_spacing)
img = source_morph.as_volume(stc_fsaverage, mri_resolution=False)

# any morph resolution nifti - in that case 3 mm
img = source_morph.as_volume(stc_fsaverage, mri_resolution=(3., 3., 3.))

And as usual, if we plot the result:

# Load fsaverage anatomical image
t1_fsaverage = nib.load(fname_t1_fsaverage)

# Create mri-resolution volume of results
img_fsaverage = source_morph.as_volume(stc_fsaverage, mri_resolution=True)

fig, axes = plt.subplots()
fig.subplots_adjust(top=0.8, left=0.1, right=0.9, hspace=0.5)
fig.patch.set_facecolor('black')

display = plot_anat(t1_fsaverage, display_mode='ortho',
                    cut_coords=[0., 0., 0.],
                    draw_cross=False, axes=axes, figure=fig, annotate=False)

display.add_overlay(img_fsaverage, alpha=0.75)
display.annotate(size=8)
axes.set_title('subject results to fsaverage', color='white', fontsize=12)

plt.text(plt.xlim()[1], plt.ylim()[0], 't = 0.087s', color='white')
plt.show()

… we obtain:

So newly implemented class will as well come with a dedicated example, once the new MNE version is released.

Since now the bulk part is done, let’s go for the fine tuning 😉

Cheers,

Tommy

Leave a Reply

Your email address will not be published. Required fields are marked *