Metrics + GQI

Seven QA metrics, one QC layer on top.

MEEGqc separates quality assessment (QA: continuous, descriptive measurements) from quality control (QC: criterion-based decisions). Each metric below contributes a QA layer that can be inspected in the reports, plus a QC layer that feeds the Global Quality Index.

QA vs QC, made concrete

Every metric on this page produces two complementary outputs layered on the same calculation. Keeping them separate is the single most important conceptual choice in MEEGqc.

QA · the measurement

Continuous, unthresholded descriptors of the signal: distributions, power spectra, time courses, spatial patterns. Stored as per-subject TSV and JSON derivatives and surfaced in the subject- and dataset-level QA reports.

QC · the decision

Threshold-derived flags: noisy / flat channel %, high-correlation channel %, muscle event rate, etc. Written during the same calculation step but as criterion-based summaries. They feed the dataset-level QC report and the Global Quality Index.

Why the separation matters QA describes what the signal looks like. QC says "this recording is good / bad according to these thresholds." Locking the QA output and varying only the QC thresholds is what makes sensitivity analyses honest. Every QC attempt also stores the exact parameters used, so the decision trail is auditable.

STD · Standard Deviation

Per-channel, per-epoch standard deviation. The simplest and most informative variability indicator. Captures amplitude dispersion independent of waveform shape.

QA: how the measurement works

For each channel c and epoch t, MEEGqc computes STD[c, t] = std(signal_{c, t}) on the preprocessed signal. The subject-level QA report shows three panels per channel type:

  • 3D topomap with sensors colour-coded by STD.
  • Distribution boxplot with one dot per channel.
  • Channel x epoch heatmap with marginal profiles. Horizontal bands = persistent issues (bad channel); vertical bands = transient bursts (bad epoch).

QC: how decisions are derived

  • Noisy channel: per-epoch STD > noisy_channel_multiplier × mean.
  • Flat channel: per-epoch STD < flat_multiplier × mean.
  • A channel is flagged only when more than allow_percent_noisy_flat_epochs of its epochs are in the same state (default 70 %).

The resulting noisy / flat channel percentages feed both the QC Group report and the ch family of the GQI.

Settings

See [STD]: std_lvl, noisy_channel_multiplier, flat_multiplier, allow_percent_noisy_flat_epochs.

STD topomap (subject report).
STD distribution.
STD heatmap with marginal profiles.

PtP · Peak-to-Peak amplitude

The total range of variation per epoch: max(signal) - min(signal). Catches transient bursts and outlier excursions that STD averages out. Ships in two flavours: PtP (manual), MEEGqc's own Numba-accelerated path, and PtP (auto), MNE's automatic epoch annotation (off by default).

QA

Same three-panel grammar as STD (topomap / distribution / channel x epoch heatmap) on the peak-to-peak amplitude instead of the standard deviation.

QC

Same noisy / flat logic as STD. Together with STD it drives the ch family of the GQI.

Settings

See [PTP_manual]: numba_version, max_pair_dist_sec, ptp_thresh_lvl, noisy_channel_multiplier, flat_multiplier.

PtP topomap.
PtP distribution.
PtP heatmap.

PSD · Power Spectral Density

Welch periodogram per channel. The most direct view of frequency-domain noise: mains harmonics (50 / 60 Hz and multiples), broadband contamination, movement-related spectral broadening.

QA

The subject-level QA report adds an SNR triage plot and a relative-band amplitude view per channel:

  • Narrow tall peaks at fixed frequencies = line noise.
  • Broad elevation across channels = environmental broadening or motion-related noise.
  • Channel-specific peaks = localised hardware issues.

QC

MEEGqc estimates a PSD noise percentage per recording (the fraction of total spectral power concentrated at mains frequencies and their harmonics). This feeds the psd family of the GQI.

Settings

See [PSD]: freq_min, freq_max, psd_step_size.

PSD SNR by channel
SNR by channel for quick triage.
Relative band amplitude
Relative-band amplitude per channel.
Welch PSD curves per channel.
PSD topomap; pick the frequency on the slider.

ECG · Cardiac contamination

Detects R-peaks in the dedicated ECG channel; falls back to reconstructing them from magnetometers if the ECG channel is judged unreliable. Then quantifies, per MEG / EEG sensor, how much of the cardiac signature leaks into that sensor by correlating each sensor's average artifact waveform against the mean R-wave.

QA · the four-step procedure

  1. Sanity-check the ECG channel. Before trusting the reference, MEEGqc applies three conditions (check_3_conditions in ECG_EOG_meg_qc.py):
    • Similar amplitudes: the std of detected peak amplitudes must stay below allowed_range_of_peaks_stds.
    • No long breaks: gaps between R-peaks must stay below 1.6 s (heart rate floor of ~40 bpm).
    • No bursts: gaps must stay above 0.6 s (ceiling of ~100 bpm).
    If the channel fails any condition the channel is flagged as noisy; the QC layer then uses a fixed neutral correlation quality (0.5) instead of an unfair penalty.
  2. Build the mean R-wave. Epoch the recording around each detected peak, average across epochs.
  3. Per-sensor average artifact. For each MAG / GRAD / EEG channel, average across the same epochs to get that channel's "average ECG artifact" trace.
  4. Correlate with the mean R-wave. Align each sensor's average artifact with the mean R-wave (best time-shift maximises Pearson correlation; absolute value of the coefficient is used, because some channels are flipped relative to the heart vector).

QA · the report tabs

  • General subtab: raw ECG with detected peaks, plus the mean R-wave used for correlation.
  • MAG / GRAD / EEG subtabs: 3D topomap of abs(corr) across sensors, plus three ranked-and-grouped panels: most affected, moderately affected, and least affected. Channels are sorted by abs(corr_coef) and split into equal thirds (split_correlated_artifacts_into_3_groups). Each panel overlays the per-channel average artifact on the mean R-wave so you can read off how closely each group tracks the cardiac signature.

QC

Channels with abs(corr_coef) above a fixed operational threshold (0.8 in the current code) are counted. The resulting high-correlation channel % per recording feeds the corr family of the GQI. The same percentage is also shown on the dataset-level QC report's ECG tab.

Settings

See [ECG]: n_breaks_bursts_allowed_per_10min, allowed_range_of_peaks_stds, height_multiplier, and norm_lvl (the threshold multiplier above the mean artifact peak used to flag channels as affected).

What the report looks like

Raw ECG with detected R-peaks. The first sanity step above is computed on this trace.
Mean R-wave averaged across all detected peaks; the reference template for the per-channel correlation.
3D topomap of abs(corr_coef): brighter sensors carry more cardiac contamination.

Three buckets of affected channels

Channels are ranked by abs(corr_coef) and split into thirds. Each panel overlays the per-channel artifact (coloured) on the mean R-wave (reference).

Most affected: top third by abs(corr_coef). Tight tracking of the mean R-wave.
Moderately affected: middle third. Partial tracking.
Least affected: bottom third. Sensors here are essentially uncorrelated with the cardiac signature.

EOG · Ocular contamination

Same shape as the ECG pipeline, applied to EOG channels (blinks and saccades). EOG is noisier than ECG and blink intervals are far more variable, so MEEGqc relaxes the sanity-check tolerances and uses a larger gaussian_sigma for smoothing.

QA · the four-step procedure

  1. Sanity-check the EOG channel. Same three conditions as ECG, with blink-specific bounds:
    • Similar amplitudes: scaled std of detected peak amplitudes within allowed_range_of_peaks_stds.
    • No long breaks: gaps between blinks must stay below ~10 s (lowest plausible blink rate ~6/min).
    • No bursts: gaps must stay above ~1 s (ceiling of ~60 blinks/min, which is high but real in some recordings).
    If the channel fails any condition the QC layer uses the fixed neutral correlation quality (0.5).
  2. Build the mean blink wave. Epoch the EOG around each detected blink, average across epochs.
  3. Per-sensor average artifact. For each MAG / GRAD / EEG channel, average across the same epochs to get the channel's average blink-locked trace.
  4. Correlate with the mean blink. Best time-shifted Pearson correlation between sensor and mean blink; abs(corr_coef) because some channels are flipped.

QA · the report tabs

  • General subtab: raw EOG with detected blinks, and the mean blink wave used for correlation.
  • MAG / GRAD / EEG subtabs: 3D topomap of abs(corr) (frontal concentration is expected), plus the same three ranked-and-grouped panels as ECG: most affected, moderately affected, least affected.

QC

Same logic as ECG: channels with abs(corr_coef) above the operational threshold (0.8) are counted; the resulting high-correlation channel % feeds the corr family of the GQI. When both ECG and EOG are enabled, the corr weight is split between them; the dataset-level QC report shows the per-subject percentage on the EOG tab.

Settings

See [EOG]: same knobs as [ECG], with EOG-specific defaults (larger gaussian_sigma, looser amplitude tolerance).

What the report looks like

Raw EOG with detected blink events. The sanity-check step works on this trace.
Mean blink wave averaged across detected blinks; the reference template for the per-channel correlation.
3D topomap of abs(corr_coef); frontal concentration is the expected pattern.

Three buckets of affected channels

Channels are ranked by abs(corr_coef) and split into thirds. Each panel overlays the per-channel artifact (coloured) on the mean blink wave (reference).

Most affected: top third by abs(corr_coef). Tight tracking of the mean blink, usually frontal sensors.
Moderately affected: middle third. Partial tracking.
Least affected: bottom third. Sensors here are largely unaffected by ocular activity.

Muscle · High-frequency muscle noise

Power in the muscle band tracks jaw clenches, neck tension, and similar movement artifacts.

QA

MEEGqc applies a band filter and z-score thresholding to annotate high-frequency events. The band is modality-aware:

  • MEG: 110 - 140 Hz (default).
  • EEG: 20 - 100 Hz. EEG muscle artifacts sit at lower frequencies than MEG; configured via muscle_freqs_eeg.

QC

The muscle event ratio feeds the mus family of the GQI.

Settings

See [Muscle]: muscle_freqs, muscle_freqs_eeg, threshold_muscle, min_length_good.

Z-scored high-frequency activity, with event thresholding.

Stimulus channels

Not a QA metric per se but a sanity-check view in the subject-level report. Reads events from BIDS _events.tsv first, then falls back to the raw stim channels. Confirms event count, timing, and trial-type distribution.

Stimulus tab header
Event count summary header.
BIDS event timeline
Event timeline from _events.tsv.
Stim channel timeline
Fallback: raw stim channel timeline.

Lobe palette

Lobe colours are shared across every plot and every report scope. For EEG, lobes are inferred from 10-20 / 10-10 / 10-05 channel-name conventions; for MEG, from the manufacturer's sensor location tables.

RegionLeftRight
Frontal#1f77b4#ff7f0e
Temporal#2ca02c#9467bd
Parietal#e377c2#d62728
Occipital#bcbd22#17becf
Central#8c564b-
Reference#7f7f7f-

Global Quality Index (GQI)

The Global Quality Index is the QC aggregation layer. It converts the per-metric QC outputs above into one composite 0-100 score per recording, with a transparent penalty decomposition that explains exactly which family dragged the score down.

Subject GQI summary card
Subject-level GQI score with its penalty decomposition.
Dataset-level GQI distribution across all subjects in the dataset.

Inputs: four penalty families

ch · Channel variability

From STD and PtP noisy / flat channel percentages.

corr · Correlation

From ECG / EOG high-correlation channel percentages.

mus · Muscle

From the muscle event ratio.

psd · PSD noise

From the estimated spectral noise contribution.

Default thresholds and weights

From [GlobalQualityIndex]:

FamilyParameter prefixStartEndWeight
ch (bad channels)bad_ch_*010035
corr (ECG / EOG)correlation_*010030
mus (muscle)muscle_*00.000115
psd (PSD noise)psd_noise_*010020

Weights are integers that sum to 100 and are internally normalised over the active families. The corr weight is split between ECG and EOG when both are enabled.

Per-family quality transform

For a per-family raw measurement M (e.g. noisy channel %, high-correlation channel %, muscle event count, PSD noise %) with start and end thresholds, the per-family quality is piecewise linear:

if M <= start:  q(M) = 1.0
if M >= end:    q(M) = 0.0
otherwise:      q(M) = 1 - (M - start) / (end - start)

Final GQI score

Let q_ch, q_corr_ecg, q_corr_eog, q_mus, q_psd be the per-family qualities and w_* their weights:

GQI = 100 * (w_ch*q_ch + w_ecg*q_corr_ecg + w_eog*q_corr_eog
            + w_mus*q_mus + w_psd*q_psd) / sum(weights_used)

Penalty terms are reported per family and proportional to (1 - q_family) · weight:

  • GQI_penalty_ch
  • GQI_penalty_corr
  • GQI_penalty_mus
  • GQI_penalty_psd

Special handling

  • If the ECG / EOG description marks the reference channels as noisy / invalid, correlation quality is clamped to a fallback value (q = 0.5) instead of computed from percentages.
  • If a family metric is missing, its default quality is 1.0 and only the active weights contribute to normalisation.
  • GQI can be disabled with compute_gqi = False; the per-metric summaries are still written.

Storage, attempts, and provenance

GQI is versioned by attempt. Each run of globalqualityindex writes a new per-modality TSV plus a frozen configuration snapshot:

Output columns

CategoryColumns
Identifierstask, subject, modality
Global scoreGQI
Penalty termsGQI_penalty_ch, GQI_penalty_corr, GQI_penalty_mus, GQI_penalty_psd
Component percentagesGQI_bad_pct, GQI_std_pct, GQI_ptp_pct, GQI_ecg_pct, GQI_eog_pct, GQI_muscle_pct, GQI_psd_noise_pct
Burden columnsSTD_ts_*, STD_ep_*, PTP_ts_*, PTP_ep_*, ECG_*_high_corr_*, EOG_*_high_corr_*, PSD_noise_*_percentage, Muscle_events_num
Parametersparam_*: the exact GQI thresholds and weights for the attempt

Sensitivity-analysis workflow

  1. Keep the calculation derivatives fixed.
  2. Edit only the [GlobalQualityIndex] section in your settings.
  3. Re-run globalqualityindex. A new attempt file is written alongside the previous ones.
  4. Compare attempts in the dataset-level QC report with --attempt <n>.

This isolates threshold effects without recomputing any raw metric.

Recompute commands

CLI
globalqualityindex --inputdata /path/to/dataset
globalqualityindex --inputdata /path/to/dataset \
                   --analysis_mode reuse \
                   --analysis_id qa_pass_01
GUI

QA/QC calculation tab → Run GQI. Use Stop GQI to interrupt mid-run.

Python
from meg_qc.test import run_gqi_dispatch

run_gqi_dispatch(
    dataset_paths=["/path/ds1", "/path/ds2"],
    default_config_file_path="./config/settings.ini",
    analysis_mode="reuse",
    analysis_id="qa_pass_01",
)

Next: the reports reference for how these metrics surface in the HTML output, or the settings reference to tune any of the knobs above.