import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import mne
from mne.datasets import sample
import meeglet
from meeglet import compute_spectral_features, spectrum_from_features
EEG power
Compute, plot and manipulate EEG power spectra using Morlet Wavelets
In this example we will load the fmailiar MNE sample data and compute Morlet Wavelets.
Load data
Let’s read in the raw data and pick the EEG channel type
= sample.data_path()
data_path
= mne.io.read_raw_fif(data_path / 'MEG/sample/sample_audvis_raw.fif')
raw = raw.pick_types(meg=False, eeg=True, eog=False, ecg=False, stim=False,
raw =raw.info['bads']).load_data()
exclude=True).apply_proj() raw.set_eeg_reference(projection
General
Measurement date | December 03, 2002 19:01:10 GMT |
Experimenter | MEG |
Participant | Unknown |
Digitized points | 146 points |
Good channels | 59 EEG |
Bad channels | None |
EOG channels | Not available |
ECG channels | Not available |
Sampling frequency | 600.61 Hz |
Highpass | 0.10 Hz |
Lowpass | 172.18 Hz |
Projections | Average EEG reference : on |
Filenames | sample_audvis_raw.fif |
Duration | 00:04:38 (HH:MM:SS) |
Compute the features using the high-level API of meeglet.
This will return tow simple namespaces, one for the spectral features, one for the meta data.
= compute_spectral_features(
features, info =2, foi_end=32, bw_oct=0.5, density='Hz', features='pow') raw, foi_start
Use MNE high-level plotting API
To make use of MNE’s latest Spectum data container, we can use a little helper function from meeglet
Power spectrum plot (2D lines)
Now we can readily make use of MNE’s plotting API. Let’s first plot data on a linear scale
= meeglet.spectrum_from_features(
spectrum pow, info.foi, raw.info
features. )
= plt.figure()
fig =True, axes=plt.gca())
spectrum.plot(dB8, 4);
fig.set_size_inches(; fig
Let’s update the output to log scale with base = 2. Note that for a base 10 logarithm, we could have simply used the xscale
from the plot method. Some adjustments follow to reflect updated scaling
= plt.figure()
fig
=True, axes=plt.gca())
spectrum.plot(dB
0].set_xscale('log', base=2)
fig.axes[0].set_xticks(2 ** np.arange(1, 6), 2 ** np.arange(1, 6))
fig.axes[
8, 4);
fig.set_size_inches(; fig
Topographic plots
Using default settings, MNE will returns bands.
='viridis'); spectrum.plot_topomap(cmap
But we can simply pass frequency coordinates as tuples. Of note, due to the logarithmic frequency grid and the particular octave band width, one octave is reached every 8 indices if we use a band width of 0.5 and bw / 4 spaxcing (defaults).
8] info.foi[::
array([ 2., 4., 8., 16., 32.])
= info.foi[::8][2:] freqs
zip(freqs, freqs), cmap='viridis'); spectrum.plot_topomap(
Finally, we can normalize the output, such that the total power adds up to one.
zip(freqs, freqs), cmap='viridis', normalize=True); spectrum.plot_topomap(
Advanced Options
Octave scaling
To take into account a-periodic dynamics, we can integrate over octaves, i.e. \(log_2(Hz)\). As a result, the 1/f will be mitigated.
= compute_spectral_features(
features2, info =2, foi_end=32, bw_oct=0.5, density='oct', features='pow')
raw, foi_start
= spectrum_from_features(
spectrum_oct =features2.pow,
data=info.foi,
freqs=raw.info
inst_info )
= plt.figure();
fig =True, axes=plt.gca());
spectrum_oct.plot(dB8,4);
fig.set_size_inches(0].set_xscale('log', base=2);
fig.axes[0].set_xticks(2 ** np.arange(1, 6), 2 ** np.arange(1, 6));
fig.axes[for tt in fig.findobj(plt.Text):
if '/Hz' in tt.get_text():
'Hz', 'oct'));
tt.set_text(tt.get_text().replace(; fig
Frequency shifting
We can now explore shifting in log domain. We will apply a frequency shiftt to arbitrary reference=12Hz. Then we plot both results using the original frequency grid
= 12
reference = 8 # we know this subject has an 8 Hz peak.
peak = peak / reference
shift
= compute_spectral_features(
features3, info3 =2, foi_end=32, bw_oct=0.5, density='oct',
raw, foi_start='pow', freq_shift_factor=shift
features )
'all')
plt.close(= plt.figure()
fig
plt.ion()'shift VS original grid')
plt.title(pow.mean(0), label='original', color='orange',
plt.loglog(info.foi, features2.=3)
linewidth='black', linestyle='--')
plt.axvline(peak, colorpow.mean(0), label='shifted', color='blue',
plt.semilogy(info3.foi, features3.=3)
linewidth='black', linestyle='--')
plt.axvline(reference, color
plt.legend()'Frequency [Hz]')
plt.xlabel(r'${\mu}V^2/oct$')
plt.ylabel(0].set_xscale('log', base=2)
fig.axes[0].set_xticks(2 ** np.arange(1, 6), 2 ** np.arange(1, 6))
fig.axes[8, 4)
fig.set_size_inches(; fig
One can nicely see the up-shift while the x axes are identical.
We can now see that the default log-scaled smoothing leads to smoother PSD estimates in high frequencies. On the left-hand side, we see that log scaling VS linear scaling are more similar in low frequencies.