Understanding the adaptive extension of multiband GEDAI🔗

This tutorial demonstrates how to use Adaptive Multiband GEDAI. The method tackles the limitations of the standard multiband GEDAI by automatically determining the optimal epoch duration for each band (i.e., wavelet level) based on the frequency content of the band. By doing so, it allows to capture both transient and sustained artifacts across different frequency ranges.

Note

The purpose of this tutorial is to explain the different parameters of the AdaptiveMultibandGedai model and help you better understand the underlying algorithm. If you want to learn how to use Adaptive Multiband GEDAI in a practical, end-to-end offline denoising workflow, please refer to the Practical Pipelines section.

from mne.io import read_raw

from gedai import AdaptiveMultibandGedai
from gedai.data import get_contaminated_eeg_set_path
from gedai.metrics import compute_enova_per_epoch, enova_summary
from gedai.viz import plot_mne_style_overlay_interactive

n_jobs = 1
raw = read_raw(str(get_contaminated_eeg_set_path()), preload=True)
raw.filter(l_freq=0.5, h_freq=None, n_jobs=n_jobs)
Filtering raw data in 1 contiguous segment
Setting up high-pass filter at 0.5 Hz

FIR filter parameters
---------------------
Designing a one-pass, zero-phase, non-causal highpass filter:
- Windowed time-domain design (firwin) method
- Hamming window with 0.0194 passband ripple and 53 dB stopband attenuation
- Lower passband edge: 0.50
- Lower transition bandwidth: 0.50 Hz (-6 dB cutoff frequency: 0.25 Hz)
- Filter length: 1321 samples (6.605 s)
General
Filename(s) SNR=0.35481 contamination=25 clean_EEG_dataset_2.set + EOG_EMG_NOISE_dataset_1.set
MNE object type RawEEGLAB
Measurement date Unknown
Participant Unknown
Experimenter Unknown
Acquisition
Duration 00:01:00 (HH:MM:SS)
Sampling frequency 200.00 Hz
Time points 12,000
Channels
EEG
Head & sensor digitization 30 points
Filters
Highpass 0.50 Hz
Lowpass 100.00 Hz


For simplicity, we will only use the first 30 seconds of the data in this tutorial. In practice, it is recommended to use the full recording for fitting the GEDAI model, as this allows the model to better capture the noise characteristics of the data.

raw.crop(0, 30)
General
Filename(s) SNR=0.35481 contamination=25 clean_EEG_dataset_2.set + EOG_EMG_NOISE_dataset_1.set
MNE object type RawEEGLAB
Measurement date Unknown
Participant Unknown
Experimenter Unknown
Acquisition
Duration 00:00:31 (HH:MM:SS)
Sampling frequency 200.00 Hz
Time points 6,001
Channels
EEG
Head & sensor digitization 30 points
Filters
Highpass 0.50 Hz
Lowpass 100.00 Hz


GEDAI Adaptive Multiband model🔗

The AdaptiveMultibandGedai model uses wavelet decomposition to separate the EEG data into different frequency bands and applies GEDAI separately to each band.

The wavelet decomposition is controlled by:

  • wavelet_type: The wavelet family (default: "haar").

  • wavelet_level: Number of decomposition levels (default: "auto").

  • broadband_pass: Whether to run an initial broadband GEDAI pass (default: True).

wavelet_type = "haar"

The wavelet level (wavelet_level) controls the number of frequency bands that the data is decomposed into. When set to "auto" (default), the optimal level is computed automatically based on the sampling frequency and the high-pass cutoff. For example, for a sampling frequency of 200 Hz, 9 wavelet levels provide wavelet bands covering classical EEG frequency bands:

  • (0.00 - 0.20 Hz)

  • (0.20 - 0.39 Hz)

  • (0.39 - 0.78 Hz)

  • (0.78 - 1.56 Hz)

  • (1.56 - 3.12 Hz) Delta

  • (3.12 - 6.25 Hz) Theta

  • (6.25 - 12.5 Hz) Alpha

  • (12.5 - 25 Hz) Beta

  • (25 - 50 Hz) Gamma

  • (50 - 100 Hz) High Gamma

  • (100 - 200 Hz)

wavelet_level = "auto"

For each wavelet level, AdaptiveMultibandGedai automatically determines the optimal epoch duration. Slower frequency bands are estimated on longer epochs, while faster frequency bands are estimated on shorter epochs. This adaptive approach allows both transient and sustained artifacts to be captured across different frequency ranges. The cycles_per_wavelet parameter controls the number of cycles of the wavelet included in each epoch (default: 10).

With these parameters defined, we instantiate the model. Enabling broadband_pass=True (default) runs an initial broadband GEDAI pass to eliminate gross artifacts before wavelet decomposition.

Model Fitting🔗

The fitting process of AdaptiveMultibandGedai is performed separately for each wavelet level using continuous SENSAI optimization (sensai_method="optimize").

The wavelet_low_cutoff parameter controls which low-frequency bands are ignored (e.g. below high-pass filtering). Setting wavelet_low_cutoff="auto" uses raw.info['highpass'] to automatically exclude sub-cutoff levels.

Fit the model directly on raw data:

    343 x 343 full covariance (kind = 1) found.
[adaptive.fit_raw] INFO: Applying wavelet HP pre-filter (sub-0.50 Hz) and running broadband GEDAI pass...

The different wavelet parameters are stored in the adaptive_multiband_gedai._wavelets_fits attribute. The ignore key indicates which wavelet levels were ignored based on the wavelet_low_cutoff setting. The duration key indicates the epoch duration used to estimate the GEDAI model of the corresponding wavelet level.

[{'band_index': 0, 'fmin': 50.0, 'fmax': 100.0, 'model': <Gedai (fitted)>, 'duration': 0.2, 'n_samples': 40, 'ignore': False, 'sensai_bounds': (0.0, 12.0), 'sensai': 36.73285285032292, 'enova': 0.0, 'band_data': array([[-1.09572043e-06,  3.25221944e-07,  7.94420418e-07, ...,
        -1.08130904e-07,  1.33772564e-07,  1.31297257e-07],
       [ 5.44052286e-08,  1.51500700e-07,  1.61772204e-07, ...,
        -3.45927627e-09, -1.97135864e-08, -2.63958402e-07],
       [-2.58964183e-07,  1.81415924e-07,  2.70810295e-07, ...,
         4.08327042e-08,  5.87490579e-08, -1.29650135e-07],
       ...,
       [-1.89092396e-07,  8.26537742e-08, -8.31172748e-08, ...,
         2.17560704e-08,  1.46501967e-07,  9.54601432e-08],
       [ 2.24027555e-07,  4.36244061e-09, -3.94490448e-07, ...,
         1.49829214e-07,  2.36462004e-07, -4.46289196e-08],
       [-1.06350226e-06,  3.29180060e-07,  7.85320112e-07, ...,
         1.57343345e-07,  3.59517962e-07, -1.46093997e-07]],
      shape=(27, 6001))}, {'band_index': 1, 'fmin': 25.0, 'fmax': 50.0, 'model': <Gedai (fitted)>, 'duration': 0.4, 'n_samples': 80, 'ignore': False, 'sensai_bounds': (-6.0, 12.0), 'sensai': 56.598527836957274, 'enova': 0.0, 'band_data': array([[-9.55013198e-07,  2.24978806e-07,  1.15518649e-06, ...,
        -1.81518522e-07, -9.65692717e-08, -7.10729220e-07],
       [ 4.66479464e-09,  3.54928275e-07,  2.92804300e-07, ...,
         6.87223182e-09, -2.60857636e-07, -3.24235606e-07],
       [-2.54290647e-07,  2.45693579e-07,  4.73616269e-07, ...,
         7.95575469e-08, -6.57730046e-08, -3.39128170e-07],
       ...,
       [-8.96785035e-08, -1.60196514e-07, -2.05242474e-07, ...,
         1.98064724e-07,  2.86518463e-07,  1.26702247e-07],
       [ 2.56267742e-07, -2.37382898e-07, -7.30590086e-07, ...,
         2.27938597e-07,  4.74428831e-07,  4.32094093e-07],
       [-1.12595781e-06,  1.76145484e-07,  1.09276276e-06, ...,
         2.38893927e-07,  2.15225299e-07, -8.01494442e-07]],
      shape=(27, 6001))}, {'band_index': 2, 'fmin': 12.5, 'fmax': 25.0, 'model': <Gedai (fitted)>, 'duration': 0.8, 'n_samples': 160, 'ignore': False, 'sensai_bounds': (-6.0, 12.0), 'sensai': 62.195880622833776, 'enova': 0.0, 'band_data': array([[-4.88917881e-07,  1.78425394e-07,  7.88535251e-07, ...,
        -1.01665255e-06, -1.10520905e-06, -9.16219899e-07],
       [ 5.33565176e-08,  2.32050847e-07,  3.48388404e-07, ...,
        -3.27451522e-07, -2.61262698e-07, -1.35927224e-07],
       [ 1.86546430e-08,  2.27687179e-07,  3.55138863e-07, ...,
        -2.52902109e-07, -2.45062007e-07, -1.53911834e-07],
       ...,
       [ 9.61135841e-09, -1.07340677e-07, -1.35675352e-07, ...,
         1.52672984e-07,  2.49125822e-07,  1.74994833e-07],
       [ 1.04177535e-07, -2.78488288e-07, -5.24375299e-07, ...,
         3.22183128e-07,  4.97225595e-07,  4.08800279e-07],
       [-5.48074194e-07, -7.31082116e-08,  4.51163588e-07, ...,
        -7.09345629e-07, -8.14255260e-07, -7.77807530e-07]],
      shape=(27, 6001))}, {'band_index': 3, 'fmin': 6.25, 'fmax': 12.5, 'model': <Gedai (fitted)>, 'duration': 1.6, 'n_samples': 320, 'ignore': False, 'sensai_bounds': (-6.0, 12.0), 'sensai': 64.01591213118714, 'enova': 0.0, 'band_data': array([[-3.65673675e-07,  2.09843335e-08,  3.68469187e-07, ...,
        -1.18112977e-06, -9.98847985e-07, -7.12978877e-07],
       [ 5.27859314e-08,  1.95237015e-07,  3.00634476e-07, ...,
        -3.00746973e-07, -2.17461863e-07, -9.72355237e-08],
       [ 2.10559521e-08,  1.36036903e-07,  2.32981286e-07, ...,
        -2.58148102e-07, -1.79523464e-07, -8.63189683e-08],
       ...,
       [-9.18536616e-08, -6.31199288e-08, -4.59767194e-08, ...,
        -2.00199700e-07, -1.64668845e-07, -1.24352386e-07],
       [-1.21814213e-07, -2.08857859e-07, -2.80219325e-07, ...,
         3.44554731e-08,  3.84770955e-09, -4.80323467e-08],
       [-4.00755926e-07, -6.27172383e-08,  2.36199292e-07, ...,
        -1.05078805e-06, -9.24519527e-07, -6.99914216e-07]],
      shape=(27, 6001))}, {'band_index': 4, 'fmin': 3.125, 'fmax': 6.25, 'model': <Gedai (fitted)>, 'duration': 3.2, 'n_samples': 640, 'ignore': False, 'sensai_bounds': (-6.0, 12.0), 'sensai': 67.47566478439519, 'enova': 0.0, 'band_data': array([[-2.27281571e-07, -2.78429532e-08,  1.71831920e-07, ...,
        -6.72822968e-07, -5.47006702e-07, -3.99909639e-07],
       [ 1.11589829e-07,  1.82116714e-07,  2.48325334e-07, ...,
        -7.73157013e-08, -1.99824445e-08,  4.25976760e-08],
       [ 4.21814715e-08,  1.00910287e-07,  1.60714653e-07, ...,
        -8.94385947e-08, -5.01245231e-08, -7.72064584e-09],
       ...,
       [ 7.27474847e-09,  3.55592779e-08,  6.89022290e-08, ...,
        -3.36806576e-08, -2.48136758e-08, -1.20957803e-08],
       [-2.33032090e-07, -2.69063374e-07, -2.99026925e-07, ...,
        -1.34918750e-07, -1.62928063e-07, -1.97077384e-07],
       [-2.27526024e-07, -2.64666081e-08,  1.71203219e-07, ...,
        -6.67292935e-07, -5.43686294e-07, -4.01056161e-07]],
      shape=(27, 6001))}, {'band_index': 5, 'fmin': 1.5625, 'fmax': 3.125, 'model': <Gedai (fitted)>, 'duration': 6.4, 'n_samples': 1280, 'ignore': False, 'sensai_bounds': (-6.0, 12.0), 'sensai': 60.395374864838296, 'enova': 0.0, 'band_data': array([[ 6.07220808e-08,  1.85195773e-07,  3.06859513e-07, ...,
        -2.93927451e-07, -1.79726731e-07, -6.09025588e-08],
       [ 3.98213290e-08,  8.22719996e-08,  1.22518401e-07, ...,
        -8.77946584e-08, -4.59165380e-08, -3.33530857e-09],
       [ 1.42450961e-07,  1.69634955e-07,  1.93384285e-07, ...,
         5.08471213e-08,  8.30123076e-08,  1.13538865e-07],
       ...,
       [ 1.40487242e-07,  1.57256190e-07,  1.74254993e-07, ...,
         9.45297181e-08,  1.09613352e-07,  1.24711493e-07],
       [-2.93616208e-07, -2.96092000e-07, -2.96426488e-07, ...,
        -2.77826784e-07, -2.84505733e-07, -2.89896179e-07],
       [ 6.22075123e-08,  1.76762236e-07,  2.90328491e-07, ...,
        -2.58639707e-07, -1.54838400e-07, -4.80852410e-08]],
      shape=(27, 6001))}, {'band_index': 6, 'fmin': 0.78125, 'fmax': 1.5625, 'model': <Gedai (fitted)>, 'duration': 12.8, 'n_samples': 2560, 'ignore': False, 'sensai_bounds': (-6.0, 12.0), 'sensai': 57.86511822646126, 'enova': 0.0, 'band_data': array([[-1.96520693e-07, -1.36975758e-07, -7.77838582e-08, ...,
        -3.68255439e-07, -3.12085391e-07, -2.54867074e-07],
       [-2.17696030e-07, -2.01158624e-07, -1.85602979e-07, ...,
        -2.68994673e-07, -2.52007823e-07, -2.34824327e-07],
       [-1.65487524e-07, -1.52411942e-07, -1.40070248e-07, ...,
        -2.06461256e-07, -1.92634267e-07, -1.78916765e-07],
       ...,
       [-1.08681267e-07, -9.79942609e-08, -8.82612626e-08, ...,
        -1.42023866e-07, -1.30958335e-07, -1.19826051e-07],
       [-2.56066603e-07, -2.64130593e-07, -2.71679795e-07, ...,
        -2.29171889e-07, -2.38727990e-07, -2.47744196e-07],
       [-6.86211801e-08, -1.94514467e-08,  2.92573781e-08, ...,
        -2.09086633e-07, -1.63343786e-07, -1.16684357e-07]],
      shape=(27, 6001))}, {'band_index': 7, 'fmin': 0.390625, 'fmax': 0.78125, 'model': <Gedai (fitted)>, 'duration': 25.6, 'n_samples': 5120, 'ignore': False, 'sensai_bounds': (-6.0, 12.0), 'sensai': 51.40474118991697, 'enova': 0.0, 'band_data': array([[ 1.34370320e-07,  1.60545141e-07,  1.86768948e-07, ...,
         5.76226181e-08,  8.29496504e-08,  1.08545044e-07],
       [-4.69336430e-07, -4.56480465e-07, -4.43486743e-07, ...,
        -5.06685079e-07, -4.94455251e-07, -4.82007714e-07],
       [-1.82998380e-07, -1.73747366e-07, -1.64378725e-07, ...,
        -2.09809632e-07, -2.00995531e-07, -1.92064672e-07],
       ...,
       [ 8.15515909e-10,  6.18269204e-09,  1.16238855e-08, ...,
        -1.50528500e-08, -9.72514512e-09, -4.44957426e-09],
       [ 2.03525423e-07,  2.04812366e-07,  2.06172949e-07, ...,
         1.98908041e-07,  2.00634509e-07,  2.02164746e-07],
       [ 2.84506841e-07,  3.11176721e-07,  3.37887249e-07, ...,
         2.05878769e-07,  2.31914393e-07,  2.58120542e-07]],
      shape=(27, 6001))}, {'band_index': 8, 'fmin': 0.0, 'fmax': 0.390625, 'model': None, 'duration': 51.2, 'n_samples': 10240, 'ignore': True, 'sensai_bounds': (0.0, 12.0), 'enova': 0.0, 'band_data': None}]

The wavelet model results can also be visualized using the plot_fit method:

  • Band 1: 50.00-100.00 Hz
  • Band 2: 25.00-50.00 Hz
  • Band 3: 12.50-25.00 Hz
  • Band 4: 6.25-12.50 Hz
  • Band 5: 3.12-6.25 Hz
  • Band 6: 1.56-3.12 Hz
  • Band 7: 0.78-1.56 Hz
  • Band 8: 0.39-0.78 Hz
[<Figure size 1200x500 with 2 Axes>, <Figure size 1200x500 with 2 Axes>, <Figure size 1200x500 with 2 Axes>, <Figure size 1200x500 with 2 Axes>, <Figure size 1200x500 with 2 Axes>, <Figure size 1200x500 with 2 Axes>, <Figure size 1200x500 with 2 Axes>, <Figure size 1200x500 with 2 Axes>]

%% Transform the Data (Denoising) —————————— Denoising is performed seamlessly using continuous dual-stream cosine overlap-add blending across each band:

adaptive_multiband_denoised_raw = adaptive_multiband_gedai.transform_raw(
    raw, n_jobs=n_jobs
)

Model Summary Table🔗

We can inspect the model fitting parameters and subband thresholds:

==================================================================================
  AdaptiveMultibandGedai Summary Table
==================================================================================
  Frequency Band             |  Epoch (s) |  Threshold |  SENSAI (%) |  ENOVA (%)
  ---------------------------+------------+------------+-------------+-----------
  Pass 1: Broadband          |     1.00 s |   2.41e-16 |     65.61 % |         --
  Band 0 (50.00-100.00 Hz)   |     0.20 s |   1.36e-18 |     36.73 % |         --
  Band 1 (25.00-50.00 Hz)    |     0.40 s |   4.63e-18 |     56.60 % |         --
  Band 2 (12.50-25.00 Hz)    |     0.80 s |  2.743e-16 |     62.20 % |         --
  Band 3 (6.25-12.50 Hz)     |     1.60 s |  2.742e-16 |     64.02 % |         --
  Band 4 (3.12-6.25 Hz)      |     3.20 s |  2.357e-18 |     67.48 % |         --
  Band 5 (1.56-3.12 Hz)      |     6.40 s |  2.794e-16 |     60.40 % |         --
  Band 6 (0.78-1.56 Hz)      |    12.80 s |  2.336e-16 |     57.87 % |         --
  Band 7 (0.39-0.78 Hz)      |    25.60 s |  1.033e-16 |     51.40 % |         --
  Band 8 (0.00-0.39 Hz)      |    51.20 s |    IGNORED |          -- |         --
==================================================================================
  Fitted SENSAI Optimization Score: 57.09 %
==================================================================================

Quality Evaluation: Explained Noise Variance (ENOVA)🔗

ENOVA (Explained Noise Variance) quantifies the proportion of signal variance removed as artifact:

ENOVA = var(noise) / var(original)

  • Clean EEG segments: ENOVA is near 0% (typically < 5-10%), indicating minimal alteration of genuine brain activity.

  • Artifact-contaminated segments: ENOVA spikes to 50-95%, showing selective rejection of high-power ocular, muscular, or movement artifacts.

We can compute ENOVA across epochs and channels using the gedai.metrics module:

original_data = raw.get_data()
clean_data = adaptive_multiband_denoised_raw.get_data()
noise_data = original_data - clean_data

sfreq = raw.info["sfreq"]
epoch_samples = max(1, round(sfreq * 1.0))

enova_epochs = compute_enova_per_epoch(clean_data, noise_data, epoch_samples)
enova_stats = enova_summary(enova_epochs)

print(f"Mean ENOVA across epochs: {enova_stats['mean'] * 100:.2f} %")
print(f"Median ENOVA:             {enova_stats['median'] * 100:.2f} %")
print(f"Max ENOVA (peak artifact): {enova_stats['max'] * 100:.2f} %")
Mean ENOVA across epochs: 34.58 %
Median ENOVA:             33.46 %
Max ENOVA (peak artifact): 84.88 %

Finally, we can visualize the before-and-after denoising overlay:

plot_mne_style_overlay_interactive(raw, adaptive_multiband_denoised_raw, duration=15.0)
30 gedai adaptive
(<Figure size 1200x1750 with 1 Axes>, <Axes: xlabel='Time (s)', ylabel='Channels'>)

Total running time of the script: (0 minutes 7.270 seconds)

Estimated memory usage: 321 MB

Gallery generated by Sphinx-Gallery