Note
Click here to download the full example code
Head model and forward computation¶
The aim of this tutorial is to be a getting started for forward computation.
For more extensive details and presentation of the general concepts for forward modeling. See The forward solution.
import os.path as op
import mne
from mne.datasets import sample
data_path = sample.data_path()
# the raw file containing the channel location + types
raw_fname = data_path + '/MEG/sample/sample_audvis_raw.fif'
# The paths to Freesurfer reconstructions
subjects_dir = data_path + '/subjects'
subject = 'sample'
Computing the forward operator¶
To compute a forward operator we need:
a
-trans.fif
file that contains the coregistration info.a source space
the BEM surfaces
Compute and visualize BEM surfaces¶
The BEM surfaces are the triangulations of the interfaces between different tissues needed for forward computation. These surfaces are for example the inner skull surface, the outer skull surface and the outer skin surface, a.k.a. scalp surface.
Computing the BEM surfaces requires FreeSurfer and makes use of either of the two following command line tools:
Or by calling in a Python script one of the functions
mne.bem.make_watershed_bem()
or mne.bem.make_flash_bem()
.
Here we’ll assume it’s already computed. It takes a few minutes per subject.
For EEG we use 3 layers (inner skull, outer skull, and skin) while for MEG 1 layer (inner skull) is enough.
Let’s look at these surfaces. The function mne.viz.plot_bem()
assumes that you have the the bem folder of your subject FreeSurfer
reconstruction the necessary files.
mne.viz.plot_bem(subject=subject, subjects_dir=subjects_dir,
brain_surfaces='white', orientation='coronal')

Out:
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/inner_skull.surf
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/outer_skull.surf
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/outer_skin.surf
Visualization the coregistration¶
The coregistration is operation that allows to position the head and the
sensors in a common coordinate system. In the MNE software the transformation
to align the head and the sensors in stored in a so-called trans file.
It is a FIF file that ends with -trans.fif
. It can be obtained with
mne.gui.coregistration()
(or its convenient command line
equivalent mne coreg), or mrilab if you’re using a Neuromag
system.
For the Python version see mne.gui.coregistration()
Here we assume the coregistration is done, so we just visually check the alignment with the following code.
# The transformation file obtained by coregistration
trans = data_path + '/MEG/sample/sample_audvis_raw-trans.fif'
info = mne.io.read_info(raw_fname)
# Here we look at the dense head, which isn't used for BEM computations but
# is useful for coregistration.
mne.viz.plot_alignment(info, trans, subject=subject, dig=True,
meg=['helmet', 'sensors'], subjects_dir=subjects_dir,
surfaces='head-dense')

Out:
Read a total of 3 projection items:
PCA-v1 (1 x 102) idle
PCA-v2 (1 x 102) idle
PCA-v3 (1 x 102) idle
Using lh.seghead for head surface.
Getting helmet for system 306m
Compute Source Space¶
The source space defines the position and orientation of the candidate source locations. There are two types of source spaces:
source-based source space when the candidates are confined to a surface.
volumetric or discrete source space when the candidates are discrete, arbitrarily located source points bounded by the surface.
Source-based source space is computed using
mne.setup_source_space()
, while volumetric source space is computed
using mne.setup_volume_source_space()
.
We will now compute a source-based source space with an OCT-6 resolution. See Setting up the source space for details on source space definition and spacing parameter.
src = mne.setup_source_space(subject, spacing='oct6',
subjects_dir=subjects_dir, add_dist=False)
print(src)
Out:
Setting up the source space with the following parameters:
SUBJECTS_DIR = /home/circleci/mne_data/MNE-sample-data/subjects
Subject = sample
Surface = white
Octahedron subdivision grade 6
>>> 1. Creating the source space...
Doing the octahedral vertex picking...
Loading /home/circleci/mne_data/MNE-sample-data/subjects/sample/surf/lh.white...
Mapping lh sample -> oct (6) ...
Triangle neighbors and vertex normals...
Loading geometry from /home/circleci/mne_data/MNE-sample-data/subjects/sample/surf/lh.sphere...
Setting up the triangulation for the decimated surface...
loaded lh.white 4098/155407 selected to source space (oct = 6)
Loading /home/circleci/mne_data/MNE-sample-data/subjects/sample/surf/rh.white...
Mapping rh sample -> oct (6) ...
Triangle neighbors and vertex normals...
Loading geometry from /home/circleci/mne_data/MNE-sample-data/subjects/sample/surf/rh.sphere...
Setting up the triangulation for the decimated surface...
loaded rh.white 4098/156866 selected to source space (oct = 6)
You are now one step closer to computing the gain matrix
<SourceSpaces: [<surface (lh), n_vertices=155407, n_used=4098, coordinate_frame=MRI (surface RAS)>, <surface (rh), n_vertices=156866, n_used=4098, coordinate_frame=MRI (surface RAS)>]>
The surface based source space src
contains two parts, one for the left
hemisphere (4098 locations) and one for the right hemisphere
(4098 locations). Sources can be visualized on top of the BEM surfaces
in purple.
mne.viz.plot_bem(subject=subject, subjects_dir=subjects_dir,
brain_surfaces='white', src=src, orientation='coronal')

Out:
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/inner_skull.surf
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/outer_skull.surf
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/outer_skin.surf
To compute a volume based source space defined with a grid of candidate dipoles inside a sphere of radius 90mm centered at (0.0, 0.0, 40.0) you can use the following code. Obviously here, the sphere is not perfect. It is not restricted to the brain and it can miss some parts of the cortex.
sphere = (0.0, 0.0, 40.0, 90.0)
vol_src = mne.setup_volume_source_space(subject, subjects_dir=subjects_dir,
sphere=sphere)
print(vol_src)
mne.viz.plot_bem(subject=subject, subjects_dir=subjects_dir,
brain_surfaces='white', src=vol_src, orientation='coronal')

Out:
Sphere : origin at (0.0 0.0 40.0) mm
radius : 90.0 mm
grid : 5.0 mm
mindist : 5.0 mm
MRI volume : /home/circleci/mne_data/MNE-sample-data/subjects/sample/mri/T1.mgz
Setting up the sphere...
Surface CM = ( 0.0 0.0 40.0) mm
Surface fits inside a sphere with radius 90.0 mm
Surface extent:
x = -90.0 ... 90.0 mm
y = -90.0 ... 90.0 mm
z = -50.0 ... 130.0 mm
Grid extent:
x = -95.0 ... 95.0 mm
y = -95.0 ... 95.0 mm
z = -50.0 ... 135.0 mm
57798 sources before omitting any.
24365 sources after omitting infeasible sources.
20377 sources remaining after excluding the sources outside the surface and less than 5.0 mm inside.
Adjusting the neighborhood info...
Reading /home/circleci/mne_data/MNE-sample-data/subjects/sample/mri/T1.mgz...
Source space : MRI voxel -> MRI (surface RAS)
0.005000 0.000000 0.000000 -95.00 mm
0.000000 0.005000 0.000000 -95.00 mm
0.000000 0.000000 0.005000 -50.00 mm
0.000000 0.000000 0.000000 1.00
MRI volume : MRI voxel -> MRI (surface RAS)
-0.001000 0.000000 0.000000 128.00 mm
0.000000 0.000000 0.001000 -128.00 mm
0.000000 -0.001000 0.000000 128.00 mm
0.000000 0.000000 0.000000 1.00
MRI volume : MRI (surface RAS) -> RAS (non-zero origin)
1.000000 -0.000000 -0.000000 -5.27 mm
-0.000000 1.000000 -0.000000 9.04 mm
-0.000000 0.000000 1.000000 -27.29 mm
0.000000 0.000000 0.000000 1.00
Setting up interpolation...
22953600/16777216 nonzero values [done]
<SourceSpaces: [<volume, shape=[39 39 38], n_used=20377, coordinate_frame=MRI (surface RAS)>]>
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/inner_skull.surf
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/outer_skull.surf
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/outer_skin.surf
To compute a volume based source space defined with a grid of candidate dipoles inside the brain (requires the BEM surfaces) you can use the following.
surface = op.join(subjects_dir, subject, 'bem', 'inner_skull.surf')
vol_src = mne.setup_volume_source_space(subject, subjects_dir=subjects_dir,
surface=surface)
print(vol_src)
mne.viz.plot_bem(subject=subject, subjects_dir=subjects_dir,
brain_surfaces='white', src=vol_src, orientation='coronal')

Out:
Boundary surface file : /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/inner_skull.surf
grid : 5.0 mm
mindist : 5.0 mm
MRI volume : /home/circleci/mne_data/MNE-sample-data/subjects/sample/mri/T1.mgz
Loaded bounding surface from /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/inner_skull.surf (2562 nodes)
Surface CM = ( 0.7 -10.0 44.3) mm
Surface fits inside a sphere with radius 91.8 mm
Surface extent:
x = -66.7 ... 68.8 mm
y = -88.0 ... 79.0 mm
z = -44.5 ... 105.8 mm
Grid extent:
x = -70.0 ... 70.0 mm
y = -90.0 ... 80.0 mm
z = -45.0 ... 110.0 mm
32480 sources before omitting any.
22941 sources after omitting infeasible sources.
Source spaces are in MRI coordinates.
Checking that the sources are inside the bounding surface and at least 5.0 mm away (will take a few...)
10203 source space points omitted because they are outside the inner skull surface.
2362 source space points omitted because of the 5.0-mm distance limit.
Thank you for waiting.
10376 sources remaining after excluding the sources outside the surface and less than 5.0 mm inside.
Adjusting the neighborhood info...
Reading /home/circleci/mne_data/MNE-sample-data/subjects/sample/mri/T1.mgz...
Source space : MRI voxel -> MRI (surface RAS)
0.005000 0.000000 0.000000 -70.00 mm
0.000000 0.005000 0.000000 -90.00 mm
0.000000 0.000000 0.005000 -45.00 mm
0.000000 0.000000 0.000000 1.00
MRI volume : MRI voxel -> MRI (surface RAS)
-0.001000 0.000000 0.000000 128.00 mm
0.000000 0.000000 0.001000 -128.00 mm
0.000000 -0.001000 0.000000 128.00 mm
0.000000 0.000000 0.000000 1.00
MRI volume : MRI (surface RAS) -> RAS (non-zero origin)
1.000000 -0.000000 -0.000000 -5.27 mm
-0.000000 1.000000 -0.000000 9.04 mm
-0.000000 0.000000 1.000000 -27.29 mm
0.000000 0.000000 0.000000 1.00
Setting up interpolation...
12219600/16777216 nonzero values [done]
<SourceSpaces: [<volume, shape=[29 35 32], n_used=10376, coordinate_frame=MRI (surface RAS)>]>
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/inner_skull.surf
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/outer_skull.surf
Using surface: /home/circleci/mne_data/MNE-sample-data/subjects/sample/bem/outer_skin.surf
With the surface-based source space only sources that lie in the plotted MRI slices are shown. Let’s write a few lines of mayavi to see all sources in 3D.
import numpy as np # noqa
from mayavi import mlab # noqa
from surfer import Brain # noqa
brain = Brain('sample', 'lh', 'inflated', subjects_dir=subjects_dir)
surf = brain.geo['lh']
vertidx = np.where(src[0]['inuse'])[0]
mlab.points3d(surf.x[vertidx], surf.y[vertidx],
surf.z[vertidx], color=(1, 1, 0), scale_factor=1.5)

Compute forward solution¶
We can now compute the forward solution. To reduce computation we’ll just compute a single layer BEM (just inner skull) that can then be used for MEG (not EEG).
We specify if we want a one-layer or a three-layer BEM using the conductivity parameter.
The BEM solution requires a BEM model which describes the geometry of the head the conductivities of the different tissues.
conductivity = (0.3,) # for single layer
# conductivity = (0.3, 0.006, 0.3) # for three layers
model = mne.make_bem_model(subject='sample', ico=4,
conductivity=conductivity,
subjects_dir=subjects_dir)
bem = mne.make_bem_solution(model)
Out:
Creating the BEM geometry...
Going from 4th to 4th subdivision of an icosahedron (n_tri: 5120 -> 5120)
inner skull CM is 0.67 -10.01 44.26 mm
Surfaces passed the basic topology checks.
Complete.
Approximation method : Linear collocation
Homogeneous model surface loaded.
Computing the linear collocation solution...
Matrix coefficients...
inner skull (2562) -> inner skull (2562) ...
Inverting the coefficient matrix...
Solution ready.
BEM geometry computations complete.
Note that the BEM does not involve any use of the trans file. The BEM only depends on the head geometry and conductivities. It is therefore independent from the MEG data and the head position.
Let’s now compute the forward operator, commonly referred to as the gain or leadfield matrix.
See mne.make_forward_solution()
for details on parameters meaning.
fwd = mne.make_forward_solution(raw_fname, trans=trans, src=src, bem=bem,
meg=True, eeg=False, mindist=5.0, n_jobs=2)
print(fwd)
Out:
Source space : <SourceSpaces: [<surface (lh), n_vertices=155407, n_used=4098, coordinate_frame=MRI (surface RAS)>, <surface (rh), n_vertices=156866, n_used=4098, coordinate_frame=MRI (surface RAS)>]>
MRI -> head transform : /home/circleci/mne_data/MNE-sample-data/MEG/sample/sample_audvis_raw-trans.fif
Measurement data : sample_audvis_raw.fif
Conductor model : instance of ConductorModel
Accurate field computations
Do computations in head coordinates
Free source orientations
Read 2 source spaces a total of 8196 active source locations
Coordinate transformation: MRI (surface RAS) -> head
0.999310 0.009985 -0.035787 -3.17 mm
0.012759 0.812405 0.582954 6.86 mm
0.034894 -0.583008 0.811716 28.88 mm
0.000000 0.000000 0.000000 1.00
Read 306 MEG channels from info
84 coil definitions read
Coordinate transformation: MEG device -> head
0.991420 -0.039936 -0.124467 -6.13 mm
0.060661 0.984012 0.167456 0.06 mm
0.115790 -0.173570 0.977991 64.74 mm
0.000000 0.000000 0.000000 1.00
MEG coil definitions created in head coordinates.
Source spaces are now in head coordinates.
Employing the head->MRI coordinate transform with the BEM model.
BEM model instance of ConductorModel is now set up
Source spaces are in head coordinates.
Checking that the sources are inside the bounding surface and at least 5.0 mm away (will take a few...)
[Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 2.9s remaining: 0.0s
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 2.9s finished
2 source space points omitted because they are outside the inner skull surface.
364 source space points omitted because of the 5.0-mm distance limit.
[Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 2.2s remaining: 0.0s
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 2.2s finished
1 source space point omitted because it is outside the inner skull surface.
331 source space point omitted because of the 5.0-mm distance limit.
Thank you for waiting.
Setting up compensation data...
No compensation set. Nothing more to do.
Composing the field computation matrix...
[Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 2.4s remaining: 0.0s
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 2.4s finished
Computing MEG at 7498 source locations (free orientations)...
[Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 1.1s remaining: 0.0s
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 1.1s finished
[Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 0.9s remaining: 0.0s
[Parallel(n_jobs=2)]: Done 2 out of 2 | elapsed: 0.9s finished
Finished.
<Forward | MEG channels: 306 | EEG channels: 0 | Source space: Surface with 7498 vertices | Source orientation: Free>
We can explore the content of fwd to access the numpy array that contains the gain matrix.
leadfield = fwd['sol']['data']
print("Leadfield size : %d sensors x %d dipoles" % leadfield.shape)
Out:
Leadfield size : 306 sensors x 22494 dipoles
To extract the numpy array containing the forward operator corresponding to the source space fwd[‘src’] with cortical orientation constraint we can use the following:
fwd_fixed = mne.convert_forward_solution(fwd, surf_ori=True, force_fixed=True,
use_cps=True)
leadfield = fwd_fixed['sol']['data']
print("Leadfield size : %d sensors x %d dipoles" % leadfield.shape)
Out:
No patch info available. The standard source space normals will be employed in the rotation to the local surface coordinates....
Changing to fixed-orientation forward solution with surface-based source orientations...
[done]
Leadfield size : 306 sensors x 7498 dipoles
This is equivalent to the following code that explicitly applies the forward operator to a source estimate composed of the identity operator:
n_dipoles = leadfield.shape[1]
vertices = [src_hemi['vertno'] for src_hemi in fwd_fixed['src']]
stc = mne.SourceEstimate(1e-9 * np.eye(n_dipoles), vertices, tmin=0., tstep=1)
leadfield = mne.apply_forward(fwd_fixed, stc, info).data / 1e-9
Out:
Projecting source estimate to sensor space...
[done]
To save to disk a forward solution you can use
mne.write_forward_solution()
and to read it back from disk
mne.read_forward_solution()
. Don’t forget that FIF files containing
forward solution should end with -fwd.fif.
To get a fixed-orientation forward solution, use
mne.convert_forward_solution()
to convert the free-orientation
solution to (surface-oriented) fixed orientation.
Exercise¶
By looking at Display sensitivity maps for EEG and MEG sensors plot the sensitivity maps for EEG and compare it with the MEG, can you justify the claims that:
MEG is not sensitive to radial sources
EEG is more sensitive to deep sources
How will the MEG sensitivity maps and histograms change if you use a free instead if a fixed/surface oriented orientation?
Try this changing the mode parameter in mne.sensitivity_map()
accordingly. Why don’t we see any dipoles on the gyri?
Total running time of the script: ( 1 minutes 24.921 seconds)
Estimated memory usage: 1120 MB