Note
Go to the end to download the full example code.
Working with sensor locations#
This tutorial describes how to read and plot sensor locations, and how MNE-Python handles physical locations of sensors. As usual we’ll start by importing the modules we need:
# Authors: The MNE-Python contributors.
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import mne
About montages and layouts#
Montages
contain sensor positions in 3D (x, y, z
in meters), which can be assigned to existing EEG/MEG data. By specifying the
locations of sensors relative to the brain,
Montages
play an important role in computing the
forward solution and inverse estimates.
In contrast, Layouts
are idealized 2D
representations of sensor positions. They are primarily used for arranging
individual sensor subplots in a topoplot or for showing the approximate
relative arrangement of sensors as seen from above.
Note
If you’re working with EEG data exclusively, you’ll want to use
Montages
, not layouts. Idealized montages
(e.g., those provided by the manufacturer, or the ones shipping with
MNE-Python mentioned below) are typically referred to as
template montages.
Working with built-in montages#
The 3D coordinates of MEG sensors are included in the raw recordings from MEG
systems. They are automatically stored in the info
attribute of the
Raw
object upon loading. EEG electrode locations are much more
variable because of differences in head shape. Idealized montages
(”template montages”) for many EEG systems are
included in MNE-Python, and you can get an overview of them by using
mne.channels.get_builtin_montages()
:
builtin_montages = mne.channels.get_builtin_montages(descriptions=True)
for montage_name, montage_description in builtin_montages:
print(f"{montage_name}: {montage_description}")
These built-in EEG montages can be loaded with
mne.channels.make_standard_montage
:
easycap_montage = mne.channels.make_standard_montage("easycap-M1")
print(easycap_montage)
Montage
objects have a
plot
method for visualizing the sensor locations
in 2D or 3D:
easycap_montage.plot() # 2D
fig = easycap_montage.plot(kind="3d", show=False) # 3D
fig = fig.gca().view_init(azim=70, elev=15) # set view angle for tutorial
Once loaded, a montage can be applied to data with the
set_montage
method, for example
raw.set_montage()
,
epochs.set_montage()
, or
evoked.set_montage()
. This will only work with
data whose EEG channel names correspond to those in the montage.
(Therefore, we’re loading some EEG data below, and not the usual MNE “sample”
dataset.)
You can then visualize the sensor locations via the
plot_sensors()
method.
It is also possible to skip the manual montage loading step by passing the
montage name directly to the set_montage()
method.
ssvep_folder = mne.datasets.ssvep.data_path()
ssvep_data_raw_path = (
ssvep_folder / "sub-02" / "ses-01" / "eeg" / "sub-02_ses-01_task-ssvep_eeg.vhdr"
)
ssvep_raw = mne.io.read_raw_brainvision(ssvep_data_raw_path, verbose=False)
# Use the preloaded montage
ssvep_raw.set_montage(easycap_montage)
fig = ssvep_raw.plot_sensors(show_names=True)
# Apply a template montage directly, without preloading
ssvep_raw.set_montage("easycap-M1")
fig = ssvep_raw.plot_sensors(show_names=True)
Note
You may have noticed that the figures created via
plot_sensors()
contain fewer sensors than the result of
easycap_montage.plot()
. This is because
the montage contains all channels defined for that EEG system; but not
all recordings will necessarily use all possible channels. Thus when
applying a montage to an actual EEG dataset, information about sensors
that are not actually present in the data is removed.
Plotting 2D sensor locations like EEGLAB#
In MNE-Python, by default the head center is calculated using fiducial points. This means that the head circle represents the head circumference at the nasion and ear level, and not where it is commonly measured in the 10–20 EEG system (i.e., above the nasion at T4/T8, T3/T7, Oz, and Fpz).
If you prefer to draw the head circle using 10–20 conventions (which are also
used by EEGLAB), you can pass sphere='eeglab'
:
fig = ssvep_raw.plot_sensors(show_names=True, sphere="eeglab")
Because the data we’re using here doesn’t contain an Fpz channel, its putative location was approximated automatically.
Manually controlling 2D channel projection#
Channel positions in 2D space are obtained by projecting their actual 3D
positions onto a sphere, then projecting the sphere onto a plane.
By default, a sphere with origin at (0, 0, 0)
(x, y, z coordinates) and
radius of 0.095
meters (9.5 cm) is used. You can use a different sphere
radius by passing a single value as the sphere
argument in any function
that plots channels in 2D (like plot
that we use
here, but also for example mne.viz.plot_topomap
):
fig1 = easycap_montage.plot() # default radius of 0.095
fig2 = easycap_montage.plot(sphere=0.07)
To change not only the radius, but also the sphere origin, pass a
(x, y, z, radius)
tuple as the sphere
argument:
fig = easycap_montage.plot(sphere=(0.03, 0.02, 0.01, 0.075))
Reading sensor digitization files#
In the sample data, the sensor positions are already available in the
info
attribute of the Raw
object (see the documentation of the
reading functions and set_montage()
for details on how that
works). Therefore, we can plot sensor locations directly from the
Raw
object using plot_sensors()
, which provides
similar functionality to montage.plot()
. In
addition, plot_sensors()
supports channel selection by
type, color-coding channels in various ways (by default, channels listed in
raw.info['bads']
will be plotted in red), and drawing in an existing
Matplotlib Axes
object (so the channel positions can easily be added as a
subplot in a multi-panel figure):
sample_data_folder = mne.datasets.sample.data_path()
sample_data_raw_path = sample_data_folder / "MEG" / "sample" / "sample_audvis_raw.fif"
sample_raw = mne.io.read_raw_fif(sample_data_raw_path, preload=False, verbose=False)
fig = plt.figure()
ax2d = fig.add_subplot(121)
ax3d = fig.add_subplot(122, projection="3d")
sample_raw.plot_sensors(ch_type="eeg", axes=ax2d)
sample_raw.plot_sensors(ch_type="eeg", axes=ax3d, kind="3d")
ax3d.view_init(azim=70, elev=15)
The previous 2D topomap reveals irregularities in the EEG sensor positions in
the sample dataset — this is because the sensor
positions in that dataset are digitizations of actual sensor positions on the
head rather than idealized sensor positions based on a spherical head model.
Depending on the digitization device (e.g., a Polhemus Fastrak digitizer),
you need to use different montage reading functions (see Supported formats for digitized 3D locations).
The resulting montage
can then be added to
Raw
objects by passing it as an argument to the
set_montage()
method (just as we did before with the name
of the predefined 'standard_1020'
montage). Once loaded, locations can be
plotted with the plot()
method and saved with
the save()
method of the
montage
object.
Note
When setting a montage with set_montage()
, the
measurement info is updated in two places (both chs
and dig
entries are updated) – see The Info data structure for more details. Note
that dig
may contain HPI, fiducial, or head shape points in addition
to electrode locations.
Visualizing sensors in 3D surface renderings#
It is also possible to render an image of an MEG sensor helmet using 3D
surface rendering instead of matplotlib. This works by calling
mne.viz.plot_alignment()
:
fig = mne.viz.plot_alignment(
sample_raw.info,
dig=False,
eeg=False,
surfaces=[],
meg=["helmet", "sensors"],
coord_frame="meg",
)
mne.viz.set_3d_view(fig, azimuth=50, elevation=90, distance=0.5)
Note that plot_alignment()
requires an Info
object, and
can also render MRI surfaces of the scalp, skull, and brain (by passing a
dict with keys like 'head'
, 'outer_skull'
or 'brain'
to the
surfaces
parameter). This makes the function useful for
assessing coordinate frame transformations.
For examples of various uses of plot_alignment()
, see
Plotting sensor layouts of EEG systems, Plotting EEG sensors on the scalp, and Plotting sensor layouts of MEG systems.
Working with layout files#
Similar to montages, many layout files are included with MNE-Python. They are
stored in the mne/channels/data/layouts
folder:
layout_dir = Path(mne.__file__).parent / "channels" / "data" / "layouts"
layouts = sorted(path.name for path in layout_dir.iterdir())
print("\nBUILT-IN LAYOUTS\n================")
print("\n".join(layouts))
To load a layout file, use the mne.channels.read_layout
function.
You can then visualize the layout using its
plot
method:
biosemi_layout = mne.channels.read_layout("biosemi")
Similar to the picks
argument for selecting channels from Raw
objects, the plot()
method of
Layout
objects also has a picks
argument. However,
because layouts only contain information about sensor name and location (not
sensor type), the plot()
method only supports
picking channels by index (not by name or by type). In the following example,
we find the desired indices using numpy.where()
; selection by name or
type is possible with mne.pick_channels()
or mne.pick_types()
.
midline = np.where([name.endswith("z") for name in biosemi_layout.names])[0]
biosemi_layout.plot(picks=midline)
If you have a Raw
object that contains sensor positions, you can
create a Layout
object with either
mne.channels.make_eeg_layout()
or mne.channels.find_layout()
.
layout_from_raw = mne.channels.make_eeg_layout(sample_raw.info)
# same result as mne.channels.find_layout(raw.info, ch_type='eeg')
layout_from_raw.plot()
Note
There is no corresponding make_meg_layout()
function because sensor
locations are fixed in an MEG system (unlike in EEG, where sensor caps
deform to fit snugly on a specific head). Therefore, MEG layouts are
consistent (constant) for a given system and you can simply load them
with mne.channels.read_layout()
or use
mne.channels.find_layout()
with
the ch_type
parameter (as previously demonstrated for EEG).
All Layout
objects have a save
method
that writes layouts to disk as either .lout
or .lay
formats
(inferred from the file extension contained in the fname
argument). The
choice between .lout
and .lay
format only matters if you need
to load the layout file in some other application (MNE-Python can read both
formats).
Estimated memory usage: 0 MB