Authors:
MNE-Python is a software package for processing MEG/EEG data.
The first step to get started, ensure that mne-python is installed on your computer:
import mne # If this line returns an error, uncomment the following line
# !easy_install mne --upgrade
Let us make the plots inline and import numpy to access the array manipulation routines
# add plot inline in the page
%matplotlib inline
import numpy as np
We set the log-level to 'WARNING' so the output is less verbose
mne.set_log_level('WARNING')
Now we import the MNE sample dataset. If you don't already have it, it will be downloaded automatically (but be patient as it is approximately 2GB large)
from mne.datasets import sample
data_path = sample.data_path()
raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
Read data from file:
raw = mne.io.Raw(raw_fname, preload=False)
print(raw)
<RawFIF | n_channels x n_times : 376 x 41700>
The data gets stored in the Raw
object. If preload
is False
, only the header information is loaded into memory and the data is loaded on-demand, thus saving RAM.
The info
dictionary contains all measurement related information: the list of bad channels, channel locations, sampling frequency, subject information etc. The info
dictionary is also available to the Epochs
and Evoked
objects.
print(raw.info)
<Info | 20 non-empty fields bads : list | MEG 2443, EEG 053 buffer_size_sec : numpy.float64 | 13.3196808772 ch_names : list | MEG 0113, MEG 0112, MEG 0111, MEG 0122, MEG 0123, ... chs : list | 376 items (EOG: 1, EEG: 60, STIM: 9, GRAD: 204, MAG: 102) comps : list | 0 items custom_ref_applied : bool | False dev_head_t : dict | 3 items dig : list | 146 items events : list | 0 items file_id : dict | 4 items filename : unicode | /home/main.../sample_audvis_filt-0-40_raw.fif highpass : float | 0.10000000149 hpi_meas : list | 1 items hpi_results : list | 1 items lowpass : float | 40.0 meas_date : numpy.ndarray | 2002-12-03 20:01:10 meas_id : dict | 4 items nchan : int | 376 projs : list | PCA-v1: off, PCA-v2: off, PCA-v3: off, ... sfreq : float | 150.153747559 acq_pars : NoneType acq_stim : NoneType ctf_head_t : NoneType description : NoneType dev_ctf_t : NoneType experimenter : NoneType hpi_subsystem : NoneType line_freq : NoneType proj_id : NoneType proj_name : NoneType subject_info : NoneType >
Look at the channels in raw:
print(raw.ch_names[:5])
['MEG 0113', 'MEG 0112', 'MEG 0111', 'MEG 0122', 'MEG 0123']
The raw object returns a numpy array when sliced
data, times = raw[:, :10]
print(data.shape)
(376, 10)
Read and plot a segment of raw data
start, stop = raw.time_as_index([100, 115]) # 100 s to 115 s data segment
data, times = raw[:306, start:stop]
print(data.shape)
print(times.shape)
print(times.min(), times.max())
(306, 2252) (2252,) (99.997504185773124, 114.98880501309083)
MNE-Python provides a set of helper functions to select the channels by type (see here for a brief overview of channel types in an MEG system). For example, to select only the magnetometer channels, we do this:
picks = mne.pick_types(raw.info, meg='mag', exclude=[])
print(picks)
[ 2 5 8 11 14 17 20 23 26 29 32 35 38 41 44 47 50 53 56 59 62 65 68 71 74 77 80 83 86 89 92 95 98 101 104 107 110 113 116 119 122 125 128 131 134 137 140 143 146 149 152 155 158 161 164 167 170 173 176 179 182 185 188 191 194 197 200 203 206 209 212 215 218 221 224 227 230 233 236 239 242 245 248 251 254 257 260 263 266 269 272 275 278 281 284 287 290 293 296 299 302 305]
Similarly, mne.mne.pick_channels_regexp
lets you pick channels using an arbitrary regular expression and mne.pick_channels
allows you to pick channels by name. Bad channels are excluded from the selection by default.
Now, we can use picks to select magnetometer data and plot it. The matplotlib graph can be converted into an interactive one using Plotly with just one line of code:
picks = mne.pick_types(raw.info, meg='mag', exclude=[])
data, times = raw[picks[:10], start:stop]
import matplotlib.pyplot as plt
import plotly.plotly as py
plt.plot(times, data.T)
plt.xlabel('time (s)')
plt.ylabel('MEG data (T)')
update = dict(layout=dict(showlegend=True), data=[dict(name=raw.info['ch_names'][p]) for p in picks[:10]])
py.iplot_mpl(plt.gcf(), update=update)
But, we can also use MNE-Python's interactive data browser to get a better visualization:
raw.plot();
Let us do the same using Plotly. First, we import the required classes
from plotly import tools
from plotly.graph_objs import Layout, YAxis, Scatter, Annotation, Annotations, Data, Figure, Marker, Font
Now we get the data for the first 10 seconds in 20 gradiometer channels
picks = mne.pick_types(raw.info, meg='grad', exclude=[])
start, stop = raw.time_as_index([0, 10])
n_channels = 20
data, times = raw[picks[:n_channels], start:stop]
ch_names = [raw.info['ch_names'][p] for p in picks[:n_channels]]
Finally, we create the plotly graph by creating a separate subplot for each channel
step = 1. / n_channels
kwargs = dict(domain=[1 - step, 1], showticklabels=False, zeroline=False, showgrid=False)
# create objects for layout and traces
layout = Layout(yaxis=YAxis(kwargs), showlegend=False)
traces = [Scatter(x=times, y=data.T[:, 0])]
# loop over the channels
for ii in range(1, n_channels):
kwargs.update(domain=[1 - (ii + 1) * step, 1 - ii * step])
layout.update({'yaxis%d' % (ii + 1): YAxis(kwargs), 'showlegend': False})
traces.append(Scatter(x=times, y=data.T[:, ii], yaxis='y%d' % (ii + 1)))
# add channel names using Annotations
annotations = Annotations([Annotation(x=-0.06, y=0, xref='paper', yref='y%d' % (ii + 1),
text=ch_name, font=Font(size=9), showarrow=False)
for ii, ch_name in enumerate(ch_names)])
layout.update(annotations=annotations)
# set the size of the figure and plot it
layout.update(autosize=False, width=1000, height=600)
fig = Figure(data=Data(traces), layout=layout)
py.iplot(fig, filename='shared xaxis')
We can look at the list of bad channels from the info
dictionary
raw.info['bads']
[u'MEG 2443', u'EEG 053']
Save a segment of 150s of raw data (MEG only):
picks = mne.pick_types(raw.info, meg=True, eeg=False, stim=True, exclude=[])
raw.save('sample_audvis_meg_raw.fif', tmin=0., tmax=150., picks=picks, overwrite=True)
Filtering is as simple as providing the low and high cut-off frequencies. We can use the n_jobs
parameter to filter the channels in parallel.
raw_beta = mne.io.Raw(raw_fname, preload=True) # reload data with preload for filtering
# keep beta band
raw_beta.filter(13.0, 30.0, method='iir', n_jobs=-1)
# save the result
raw_beta.save('sample_audvis_beta_raw.fif', overwrite=True)
# check if the info dictionary got updated
print(raw_beta.info['highpass'], raw_beta.info['lowpass'])
(13.0, 30.0)
First extract events. Events are typically extracted from the trigger channel, which in our case is STI 014
. In the sample dataset, there are 5 possible event-ids: 1, 2, 3, 4, 5, and 32.
events = mne.find_events(raw, stim_channel='STI 014')
print(events[:5]) # events is a 2d array
[[6994 0 2] [7086 0 3] [7192 0 1] [7304 0 4] [7413 0 2]]
Events is a 2d array where the first column contains the sample index when the event occurred. The second column contains the value of the trigger channel immediately before the event occurred. The third column contains the event-id.
Therefore, there are around 73 occurences of the event with event-id 2.
len(events[events[:, 2] == 2])
73
And the total number of events in the dataset is 319
len(events)
319
We can index the channel name to find it's position among all the available channels
raw.ch_names.index('STI 014')
312
raw = mne.io.Raw(raw_fname, preload=True) # reload data with preload for filtering
raw.filter(1, 40, method='iir')
Let us plot the trigger channel as an interactive plot:
d, t = raw[raw.ch_names.index('STI 014'), :]
plt.plot(d[0,:1000])
py.iplot_mpl(plt.gcf())
We can also plot the events using the plot_events
function.
event_ids = ['aud_l', 'aud_r', 'vis_l', 'vis_r', 'smiley', 'button']
fig = mne.viz.plot_events(events, raw.info['sfreq'], raw.first_samp, show=False)
# convert plot to plotly
update = dict(layout=dict(showlegend=True), data=[dict(name=e) for e in event_ids])
py.iplot_mpl(plt.gcf(), update=update)
Define epochs parameters:
event_id = dict(aud_l=1, aud_r=2) # event trigger and conditions
tmin = -0.2 # start of each epoch (200ms before the trigger)
tmax = 0.5 # end of each epoch (500ms after the trigger)
event_id
{'aud_l': 1, 'aud_r': 2}
Mark two channels as bad:
raw.info['bads'] = ['MEG 2443', 'EEG 053']
print(raw.info['bads'])
['MEG 2443', 'EEG 053']
The variable raw.info[‘bads’] is just a python list.
Pick the good channels:
picks = mne.pick_types(raw.info, meg=True, eeg=True, eog=True,
stim=False, exclude='bads')
Alternatively one can restrict to magnetometers or gradiometers with:
mag_picks = mne.pick_types(raw.info, meg='mag', eog=True, exclude='bads')
grad_picks = mne.pick_types(raw.info, meg='grad', eog=True, exclude='bads')
Define the baseline period for baseline correction:
baseline = (None, 0) # means from the first instant to t = 0
Define peak-to-peak rejection parameters for gradiometers, magnetometers and EOG. If the data in any channel exceeds these thresholds, the corresponding epoch will be rejected:
reject = dict(grad=4000e-13, mag=4e-12, eog=150e-6)
Now we create epochs from the raw
object. The epochs object allows storing data of fixed length around the events which are supplied to the Epochs
constructor.
epochs = mne.Epochs(raw, events, event_id, tmin, tmax, proj=True,
picks=picks, baseline=baseline, reject=reject)
Now let us compute what channels contribute to epochs rejection. The drop log stores the epochs dropped and the reason they were dropped. Refer to the MNE-Python documentation for further details:
from mne.fixes import Counter
# drop bad epochs
epochs.drop_bad_epochs()
drop_log = epochs.drop_log
# calculate percentage of epochs dropped for each channel
perc = 100 * np.mean([len(d) > 0 for d in drop_log if not any(r in ['IGNORED'] for r in d)])
scores = Counter([ch for d in drop_log for ch in d if ch not in ['IGNORED']])
ch_names = np.array(list(scores.keys()))
counts = 100 * np.array(list(scores.values()), dtype=float) / len(drop_log)
order = np.flipud(np.argsort(counts))
And now we can use Plotly to show the statistics:
from plotly.graph_objs import Data, Layout, Bar, YAxis, Figure
data = Data([
Bar(
x=ch_names[order],
y=counts[order]
)
])
layout = Layout(title='Drop log statistics', yaxis=YAxis(title='% of epochs rejected'))
fig = Figure(data=data, layout=layout)
py.iplot(fig)
And if you want to keep all the information about the data you can save your epochs in a fif file:
epochs.save('sample-epo.fif')
evoked = epochs.average()
Now let's visualize our event-related potential / field:
fig = evoked.plot(show=False) # butterfly plots
update = dict(layout=dict(showlegend=False), data=[dict(name=raw.info['ch_names'][p]) for p in picks[:10]])
py.iplot_mpl(fig, update=update)
The draw time for this plot will be slow for clients without much RAM.
/home/mainak/anaconda/lib/python2.7/site-packages/plotly-1.6.17-py2.7.egg/plotly/plotly/plotly.py:1261: UserWarning: Estimated Draw Time Slow
# topography plots
evoked.plot_topomap(times=np.linspace(0.05, 0.15, 5), ch_type='mag');
evoked.plot_topomap(times=np.linspace(0.05, 0.15, 5), ch_type='grad');
evoked.plot_topomap(times=np.linspace(0.05, 0.15, 5), ch_type='eeg');
Syntax is epochs[condition]
epochs_data = epochs['aud_l'].get_data()
print(epochs_data.shape)
(55, 365, 106)
epochs_data is a 3D array of dimension (55 epochs, 365 channels, 106 time instants).
evokeds = [epochs[k].average() for k in event_id]
from mne.viz import plot_topo
layout = mne.find_layout(epochs.info)
plot_topo(evokeds, layout=layout, color=['blue', 'orange']);
noise_cov = mne.compute_covariance(epochs, tmax=0.)
print(noise_cov.data.shape)
(364, 364)
fig = mne.viz.plot_cov(noise_cov, raw.info)
/home/mainak/anaconda/lib/python2.7/site-packages/matplotlib/figure.py:1644: UserWarning: This figure includes Axes that are not compatible with tight_layout, so its results might be incorrect.
Inverse modeling can be used to estimate the source activations which explain the sensor-space data.
First, Import the required functions:
from mne.forward import read_forward_solution
from mne.minimum_norm import (make_inverse_operator, apply_inverse,
write_inverse_operator)
The forward solution describes how the currents inside the brain will manifest in sensor-space. This is required for computing the inverse operator which describes the transformation from sensor-space data to source space:
fname_fwd = data_path + '/MEG/sample/sample_audvis-meg-oct-6-fwd.fif'
fwd = mne.read_forward_solution(fname_fwd, surf_ori=True)
# Restrict forward solution as necessary for MEG
fwd = mne.pick_types_forward(fwd, meg=True, eeg=False)
# make an M/EEG, MEG-only, and EEG-only inverse operators
info = evoked.info
inverse_operator = make_inverse_operator(info, fwd, noise_cov,
loose=0.2, depth=0.8)
write_inverse_operator('sample_audvis-meg-oct-6-inv.fif',
inverse_operator)
Now we can use the inverse operator and apply to MEG data to get the inverse solution
method = "dSPM"
snr = 3.
lambda2 = 1. / snr ** 2
stc = apply_inverse(evoked, inverse_operator, lambda2,
method=method, pick_ori=None)
print(stc)
<SourceEstimate | 7498 vertices, subject : sample, tmin : -199.795213158 (ms), tmax : 499.488032896 (ms), tstep : 6.65984043861 (ms), data size : 7498 x 106>
stc.data.shape
(7498, 106)
Show the result:
import surfer
surfer.set_log_level('WARNING')
subjects_dir = data_path + '/subjects'
brain = stc.plot(surface='inflated', hemi='rh', subjects_dir=subjects_dir)
brain.set_data_time_index(45)
brain.scale_data_colormap(fmin=8, fmid=12, fmax=15, transparent=True)
brain.show_view('lateral')
WARNING:traits.has_traits:DEPRECATED: traits.has_traits.wrapped_class, 'the 'implements' class advisor has been deprecated. Use the 'provides' class decorator. /home/mainak/.local/lib/python2.7/site-packages/pysurfer-0.5.dev-py2.7.egg/surfer/viz.py:1563: FutureWarning: comparison to `None` will result in an elementwise object comparison in the future.
((-7.0167092985348768e-15, 90.0, 518.46453857421875, array([ 0., 0., 0.])), -90.0)
brain.save_image('dspm.jpg')
brain.close()
from IPython.display import Image
Image(filename='dspm.jpg', width=600)
from mne.time_frequency import tfr_morlet
freqs = np.arange(6, 30, 3) # define frequencies of interest
n_cycles = freqs / 4. # different number of cycle per frequency
power = tfr_morlet(epochs, freqs=freqs, n_cycles=n_cycles, use_fft=False,
return_itc=False, decim=3, n_jobs=1)
Now let''s look at the power plots
# Inspect power
power.plot_topo(baseline=(-0.5, 0), tmin=0, tmax=0.4, mode='logratio', title='Average power');
power.plot([82], baseline=(-0.5, 0), tmin=0, tmax=0.4, mode='logratio');
/home/mainak/anaconda/lib/python2.7/site-packages/matplotlib/figure.py:387: UserWarning: matplotlib is currently using a non-GUI backend, so cannot show the figure
from IPython.display import display, HTML
display(HTML('<link href="//fonts.googleapis.com/css?family=Open+Sans:600,400,300,200|Inconsolata|Ubuntu+Mono:400,700" rel="stylesheet" type="text/css" />'))
display(HTML('<link rel="stylesheet" type="text/css" href="http://help.plot.ly/documentation/all_static/css/ipython-notebook-custom.css">'))
! pip install publisher --upgrade
import publisher
publisher.publish(
'mne-tutorial.ipynb', 'ipython-notebooks/mne-tutorial/', 'Plotly visualizations for MNE-Python to process MEG/EEG data',
'Create interactive visualizations using MNE-Python and Plotly', name='Process MEG/EEG Data with Plotly',
redirect_from='ipython-notebooks/meeg-and-eeg-data-analysis/')
Requirement already up-to-date: publisher in /Users/chelsea/venv/venv2.7/lib/python2.7/site-packages
/Users/chelsea/venv/venv2.7/lib/python2.7/site-packages/IPython/nbconvert.py:13: ShimWarning: The `IPython.nbconvert` package has been deprecated since IPython 4.0. You should import from nbconvert instead. /Users/chelsea/venv/venv2.7/lib/python2.7/site-packages/publisher/publisher.py:53: UserWarning: Did you "Save" this notebook before running this command? Remember to save, always save.