ASR diagnostics and quality control.#

Every fitted ASR estimator records what it did, so you can audit the cleaning instead of trusting it blindly. This example tours the QC surface:

  • get_diagnostics() – the per-window reconstruction record;

  • variance_removed() – scalar before/after summaries;

  • to_annotations(kind=...) – repaired / rejected / calibration spans as mne.Annotations (mark, do not delete);

  • the three ASR-specific diagnostic plots.

Fit ASR on a synthetic Raw#

import matplotlib.pyplot as plt
import mne
import numpy as np

from mne_denoise.asr import ASR
from mne_denoise.qa import variance_removed
from mne_denoise.viz import (
    plot_asr_calibration_fraction,
    plot_asr_component_reconstruction,
    plot_asr_repair_timeline,
)

rng = np.random.default_rng(8)
sfreq = 250.0
n_channels, n_times = 12, 12000
t = np.arange(n_times) / sfreq
brain = np.vstack(
    [
        0.6 * np.sin(2 * np.pi * 10.0 * t + rng.uniform(0, 6.28))
        + 0.05 * rng.standard_normal(n_times)
        for _ in range(n_channels)
    ]
)
contaminated = brain.copy()
for start in np.linspace(500, n_times - 500, 9).astype(int):
    spatial = rng.standard_normal(n_channels)
    spatial /= np.linalg.norm(spatial)
    contaminated[:, start : start + 220] += 11.0 * np.outer(
        spatial, rng.standard_normal(220)
    )
info = mne.create_info([f"EEG{i:02d}" for i in range(n_channels)], sfreq, "eeg")
raw = mne.io.RawArray(contaminated, info, verbose="ERROR")

asr = ASR(cutoff=10.0, picks="eeg", window_criterion=0.3, verbose=False)
raw_clean = asr.fit_transform(raw)

Diagnostics record + QA metrics#

diag = asr.get_diagnostics()
print("get_diagnostics() keys:", sorted(diag))
print(
    f"  windows={diag['n_windows']}  "
    f"reconstructed windows={diag['fraction_reconstructed_windows']:.1%}  "
    f"samples modified={diag['fraction_reconstructed_samples']:.1%}"
)

from mne_denoise.qa import channel_variance_ratio, rms_change

print(
    f"QA: variance removed={variance_removed(raw.get_data(picks='eeg'), raw_clean.get_data(picks='eeg')):.1f}%  "
    f"rms change={rms_change(raw.get_data(picks='eeg'), raw_clean.get_data(picks='eeg')):.3g}  "
    f"median channel variance ratio={np.median(channel_variance_ratio(raw.get_data(picks='eeg'), raw_clean.get_data(picks='eeg'))):.2f}"
)
get_diagnostics() keys: ['chunk_samples', 'component_thresholds', 'component_variances', 'covariance_geometry', 'estimated_full_cov_bytes', 'fraction_reconstructed_samples', 'fraction_reconstructed_windows', 'fraction_rejected_after_window_rejection', 'fraction_retained_after_window_rejection', 'lookahead_samples', 'max_components_reconstructed', 'max_mem_mb', 'memory_mode', 'n_components_reconstructed', 'n_windows', 'peak_cov_buffer_bytes', 'rejection_sample_mask', 'rejection_window_keep_mask', 'rejection_window_remove_mask', 'rejection_window_starts', 'rejection_window_stops', 'sample_mask', 'stepsize_samples', 'used_memory_bound', 'window_length_samples', 'window_starts', 'window_stops']
  windows=194  reconstructed windows=28.9%  samples modified=34.4%
QA: variance removed=89.5%  rms change=1.24  median channel variance ratio=0.11

Annotation export (mark, don’t delete)#

for kind in ("repair", "rejection", "calibration"):
    try:
        ann = asr.to_annotations(kind=kind)
        total = float(np.sum(ann.duration))
        print(f"  to_annotations(kind={kind!r}): {len(ann)} spans, {total:.1f} s")
    except (ValueError, RuntimeError) as exc:
        print(f"  to_annotations(kind={kind!r}): not available ({exc})")
to_annotations(kind='repair'): 12 spans, 13.8 s
to_annotations(kind='rejection'): 0 spans, 0.0 s
to_annotations(kind='calibration'): not available (Calibration annotations are only available for sample-based reference selection (JugglerASR). This backend uses window-based calibration; use get_calibration_mask() instead.)

The three ASR-specific diagnostic plots#

  • ASR repair timeline (29% of windows modified)
  • ASR component reconstruction map
  • ASR calibration / reference fraction

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