"""
.. _ex-tfr-erds:
===============================
Compute and visualize ERDS maps
===============================
This example calculates and displays ERDS maps of event-related EEG data.
ERDS (sometimes also written as ERD/ERS) is short for event-related
desynchronization (ERD) and event-related synchronization (ERS)
:footcite:`PfurtschellerLopesdaSilva1999`. Conceptually, ERD corresponds to a
decrease in power in a specific frequency band relative to a baseline.
Similarly, ERS corresponds to an increase in power. An ERDS map is a
time/frequency representation of ERD/ERS over a range of frequencies
:footcite:`GraimannEtAl2002`. ERDS maps are also known as ERSP (event-related
spectral perturbation) :footcite:`Makeig1993`.
In this example, we use an EEG BCI data set containing two different motor
imagery tasks (imagined hand and feet movement). Our goal is to generate ERDS
maps for each of the two tasks.
First, we load the data and create epochs of 5s length. The data set contains
multiple channels, but we will only consider C3, Cz, and C4. We compute maps
containing frequencies ranging from 2 to 35Hz. We map ERD to red color and ERS
to blue color, which is customary in many ERDS publications. Finally, we
perform cluster-based permutation tests to estimate significant ERDS values
(corrected for multiple comparisons within channels).
"""
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib.colors import TwoSlopeNorm
import mne
from mne.datasets import eegbci
from mne.io import concatenate_raws, read_raw_edf
from mne.stats import permutation_cluster_1samp_test as pcluster_test
fnames = eegbci.load_data(subjects=1, runs=(6, 10, 14))
raw = concatenate_raws([read_raw_edf(f, preload=True) for f in fnames])
raw.rename_channels(lambda x: x.strip("."))
raw.annotations.rename(dict(T1="hands", T2="feet"))
tmin, tmax = -1, 4
event_ids = dict(hands=2, feet=3)
epochs = mne.Epochs(
raw,
event_id=["hands", "feet"],
tmin=tmin - 0.5,
tmax=tmax + 0.5,
picks=("C3", "Cz", "C4"),
baseline=None,
preload=True,
)
freqs = np.arange(2, 36)
vmin, vmax = -1, 1.5
baseline = (-1, 0)
cnorm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)
kwargs = dict(
n_permutations=100, step_down_p=0.05, rng=1, buffer_size=None, out_type="mask"
)
tfr = epochs.compute_tfr(
method="multitaper",
freqs=freqs,
n_cycles=freqs,
use_fft=True,
return_itc=False,
average=False,
decim=2,
)
tfr.crop(tmin, tmax).apply_baseline(baseline, mode="percent")
for event in event_ids:
tfr_ev = tfr[event]
fig, axes = plt.subplots(
1, 4, figsize=(12, 4), gridspec_kw={"width_ratios": [10, 10, 10, 1]}
)
for ch, ax in enumerate(axes[:-1]):
_, c1, p1, _ = pcluster_test(tfr_ev.data[:, ch], tail=1, **kwargs)
_, c2, p2, _ = pcluster_test(tfr_ev.data[:, ch], tail=-1, **kwargs)
c = np.stack(c1 + c2, axis=2)
p = np.concatenate((p1, p2))
mask = c[..., p <= 0.05].any(axis=-1)
tfr_ev.average().plot(
[ch],
cmap="RdBu",
cnorm=cnorm,
axes=ax,
colorbar=False,
show=False,
mask=mask,
mask_style="mask",
)
ax.set_title(epochs.ch_names[ch], fontsize=10)
ax.axvline(0, linewidth=1, color="black", linestyle=":")
if ch != 0:
ax.set_ylabel("")
ax.set_yticklabels("")
fig.colorbar(axes[0].images[-1], cax=axes[-1]).ax.set_yscale("linear")
fig.suptitle(f"ERDS ({event})")
plt.show()
df = tfr.to_data_frame(time_format=None)
df.head()
df = tfr.to_data_frame(time_format=None, long_format=True)
freq_bounds = {"_": 0, "delta": 3, "theta": 7, "alpha": 13, "beta": 35, "gamma": 140}
df["band"] = pd.cut(
df["freq"], list(freq_bounds.values()), labels=list(freq_bounds)[1:]
)
freq_bands_of_interest = ["delta", "theta", "alpha", "beta"]
df = df[df.band.isin(freq_bands_of_interest)]
df["band"] = df["band"].cat.remove_unused_categories()
df["channel"] = df["channel"].cat.reorder_categories(("C3", "Cz", "C4"), ordered=True)
g = sns.FacetGrid(df, row="band", col="channel", margin_titles=True)
g.map(sns.lineplot, "time", "value", "condition", n_boot=10)
axline_kw = dict(color="black", linestyle="dashed", linewidth=0.5, alpha=0.5)
g.map(plt.axhline, y=0, **axline_kw)
g.map(plt.axvline, x=0, **axline_kw)
g.set(ylim=(None, 1.5))
g.set_axis_labels("Time (s)", "ERDS")
g.set_titles(col_template="{col_name}", row_template="{row_name}")
g.add_legend(ncol=2, loc="lower center")
g.fig.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.08)
df_mean = (
df.query("time > 1")
.groupby(["condition", "epoch", "band", "channel"], observed=False)[["value"]]
.mean()
.reset_index()
)
g = sns.FacetGrid(
df_mean, col="condition", col_order=["hands", "feet"], margin_titles=True
)
g = g.map(
sns.violinplot,
"channel",
"value",
"band",
cut=0,
palette="deep",
order=["C3", "Cz", "C4"],
hue_order=freq_bands_of_interest,
linewidth=0.5,
).add_legend(ncol=4, loc="lower center")
g.map(plt.axhline, **axline_kw)
g.set_axis_labels("", "ERDS")
g.set_titles(col_template="{col_name}", row_template="{row_name}")
g.fig.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.3)