diff --git a/tutorials/preprocessing/14_quality_control_report.py b/tutorials/preprocessing/14_quality_control_report.py new file mode 100644 index 00000000000..1880ae014b2 --- /dev/null +++ b/tutorials/preprocessing/14_quality_control_report.py @@ -0,0 +1,267 @@ +""" +.. _tut-qc-report: + +============================================ +Quality control (QC) reports with mne.Report +============================================ + +Quality control (QC) is the process of systematically inspecting M/EEG data +**throughout all stages of an analysis pipeline**, including raw data, +intermediate preprocessing steps, and derived results. + +While QC often begins with an initial inspection of the raw recording, +it is equally important to verify that signals continue to "look reasonable" +after operations such as filtering, artifact correction, epoching, and +averaging. Issues introduced or missed at any stage can propagate downstream +and invalidate later analyses. + +This tutorial demonstrates how to create a **single, narrative QC report** +using :class:`mne.Report`, focusing on **what should be inspected and how the +results should be interpreted**, rather than exhaustively covering the API. + +For clarity and reproducibility, the examples below focus on common QC checks +applied at representative stages of an analysis pipeline. The same reporting +approach can—and should—be reused whenever new processing steps are applied. + +We use the MNE sample dataset for demonstration. Not all QC sections are +applicable to every dataset (e.g., continuous head-position tracking), and +this tutorial explicitly handles such cases. + +.. note:: For several additional examples of complete reports, see the + `MNE-BIDS-Pipeline QC reports `_. +""" # noqa: E501 + +# Authors: The MNE-Python contributors +# License: BSD-3-Clause +# Copyright the MNE-Python contributors. + +# %% + +from pathlib import Path + +import mne +from mne.preprocessing import ICA, create_eog_epochs + +# %% +# Load the sample dataset +# ----------------------- +# We load a pre-filtered MEG/EEG recording from the MNE sample dataset. +# Only channels relevant for QC (MEG, EEG, EOG, stimulus) are retained. + +data_path = Path(mne.datasets.sample.data_path(verbose=False)) +subject = "sample" +sample_dir = data_path / "MEG" / subject +subjects_dir = data_path / "subjects" + +raw_path = sample_dir / "sample_audvis_filt-0-40_raw.fif" + + +raw = mne.io.read_raw(raw_path) + +# We will also crop the dataset for speed +raw.crop(0, 60).load_data() + +# Retain only channels relevant for QC to simplify visualization and +# focus inspection on signals typically reviewed during data quality checks. +raw.pick(["meg", "eeg", "eog", "stim"]) + +sfreq = raw.info["sfreq"] # Sampling Frequency (Hz) + +# %% +# Create the QC report +# -------------------- +# The report acts as a container that collects figures, tables, and text +# into a single HTML document. + +report = mne.Report( + title="Sample dataset - Quality Control report", + subject=subject, + subjects_dir=subjects_dir, +) + +# %% +# Dataset overview +# ---------------- +# A brief overview helps the reviewer immediately understand the scale and +# basic properties of the dataset. + +html_overview = """ +This report presents a quality control (QC) overview of the MNE sample dataset.

+For information about the paradigm, see +the MNE docs. +""" + +report.add_html( + title="Overview", + html=html_overview, + tags=("overview"), +) + +# %% +# Raw data inspection +# ------------------- +# Visual inspection of raw data is the single most important QC step. +# Here we inspect both the time series and the power spectral density (PSD). +# +# - Look for channels with unusually large amplitudes or flat signals. +# - In the PSD, check for excessive low-frequency drift, strong line noise, +# or abnormal spectral shapes compared to neighboring channels. + +report.add_raw( + raw, + title="Raw data overview", + psd=False, # omit just for speed here +) + +# %% +# Events and stimulus timing +# -------------------------- +# Correct event detection is crucial for all subsequent epoch-based analyses. +# +# - Verify that the number of events matches expectations. +# - Check that event timing is plausible and evenly distributed. +# - Missing or duplicated events often indicate trigger channel issues. + +events = mne.find_events(raw) + +report.add_events( + events, + sfreq=sfreq, + title="Detected events", +) + +# %% +# Epoching and rejection statistics +# --------------------------------- +# Epoching allows inspection of data segments time-locked to events, along +# with automated rejection based on amplitude thresholds. + +event_id = { + "auditory/left": 1, + "auditory/right": 2, + "visual/left": 3, + "visual/right": 4, +} + +epochs = mne.Epochs( + raw, + events, + event_id=event_id, + tmin=-0.2, + tmax=0.5, + baseline=(None, 0), + reject=dict(eeg=150e-6), + preload=True, +) + +report.add_epochs( + epochs, + title="Epochs and rejection statistics", +) + +# %% +# Evoked responses +# ---------------- +# Averaged responses should show physiologically plausible waveforms and +# reasonable signal-to-noise ratios. +# +# - Check that evoked responses have the expected polarity and timing. +# - Absence of clear evoked structure may indicate poor data quality or +# incorrect event definitions. + +cov_path = sample_dir / "sample_audvis-cov.fif" +evoked = mne.read_evokeds( + sample_dir / "sample_audvis-ave.fif", + baseline=(None, 0), +)[0] # just one for speed +evoked.decimate(4) # also for speed + +report.add_evokeds( + evokeds=evoked, + noise_cov=cov_path, + n_time_points=5, +) + + +# %% +# ICA for artifact inspection +# --------------------------- +# Independent Component Analysis (ICA) can be used during QC to identify +# stereotypical artifacts such as eye blinks and eye movements. +# +# For QC purposes, ICA is typically run with a lightweight configuration +# (e.g., fewer components or temporal decimation) to provide rapid feedback +# on data quality, rather than an optimized decomposition for final analysis. +# +# - Use the topographic maps to identify spatial patterns characteristic +# of artifacts (e.g., frontal patterns for eye blinks). +# - The component property viewer is intended for detailed inspection of +# individual components and is most informative when combined with +# epoched data or explicit artifact scoring. +# - Components correlated with EOG should show frontal topographies and +# stereotyped time courses. +# - Only components clearly associated with artifacts should be excluded. + +ica = ICA( + n_components=15, + random_state=97, + max_iter=50, # just for speed! +) + +# Fit ICA using a decimated signal for speed +ica.fit(raw, picks=("meg", "eeg"), decim=10, verbose="error") + + +# Identify EOG-related components +eog_epochs = create_eog_epochs(raw) +eog_inds, eog_scores = ica.find_bads_eog(eog_epochs) +ica.exclude = eog_inds + +report.add_ica( + ica=ica, + inst=epochs, + eog_evoked=eog_epochs.average(), + eog_scores=eog_scores, + title="ICA components (artifact inspection)", +) + +# %% +# MEG–MRI coregistration +# ---------------------- +# Accurate coregistration is critical for source localization. +# +# - Head shape points should align well with the MRI scalp surface. +# - Systematic misalignment indicates digitization or transformation errors. + + +trans = sample_dir / "sample_audvis_raw-trans.fif" +report.add_trans( + trans, + info=raw.info, + title="MEG–MRI-head coregistration", + subject=subject, + subjects_dir=subjects_dir, +) + +# %% +# MRI and BEM surfaces +# -------------------- +# Boundary Element Method (BEM) surfaces define the head model used for +# forward and inverse solutions. +# +# - Surfaces should be smooth, closed, and non-intersecting. +# - Poorly formed surfaces can severely degrade source estimates. + +report.add_bem( + subject, + subjects_dir=subjects_dir, + title="BEM surfaces", + decim=20, # for speed +) + +# %% +# View the final report +# --------------------- +# You can set ``open_browser=True`` to have it pop open a browser tab if you want: + +report.save("qc_report.html", overwrite=True, open_browser=False)