Quality control#

See also

Acquisition quality control runs the checks on real data and shows their figures.

meeg_utils.qc answers how well was this recording made?, so that problems can be fixed during the session or reported to whoever acquired the data. Checks never modify the data.

import meeg_utils as meu

report = meu.qc.inspect(raw)      # every check that applies to this recording
print(report)                     # verdicts and the findings that need attention
report.flags                      # findings that are "warn" or "fail", worst first
report.skipped                    # checks that did not apply, with the reason
figs = report.plot()              # {check: {kind: Figure}}

Each check is a small class: parameters (thresholds included) are set in the constructor, compute(raw) measures, and the results are in metrics_ and findings_ (one Finding per criterion, with a level and a message that states the threshold).

Verdicts and measurements#

Only some criteria have a threshold that holds for every dataset. The checks distinguish three kinds:

  • Problems by definition are judged ("warn" or "fail") by default: flat, clipped or NaN channels, SQUID jumps, bridged electrodes (and groups too large to interpolate), a flat EOG, unreliable heartbeat detection on an ECG channel, missing positions or head transform, event counts that differ from expected, BIDS sidecars that disagree with the data.

  • Comparisons within the recording are judged with the method’s own default: outlying channels (LOF), channels with excess line noise (robust z), muscle and jump z-scores, narrowband peaks.

  • Measurements whose acceptable range depends on the dataset are only reported, with level "info" and a commonly used value in the message: head movement, share of time with muscle activity, blink rate, heart rate, electrode impedance, cHPI coil SNR, days between the empty room and the recording. Children move more than adults, trained people have slow hearts, passive electrodes have higher impedances than active ones.

"info" findings are not flags and do not raise the level of a report. Two ways to judge them:

# 1. your lab's limits, for your population and equipment
meu.qc.inspect(raw, checks=[
    meu.qc.Impedance(warn_kohm=10),            # passive electrodes
    meu.qc.HeadMovement(warn_mm=5),
    meu.qc.Events(expected={"stimulus": 240}),
])

# 2. relative to the rest of the dataset: recordings that stand out
meu.qc.inspect_dataset("bids/").outliers   # modified z > 3.5 on any metric

Checks#

Every default comes from MNE’s defaults or the literature, and is documented with its source in the class docstring; where no standard exists, the docstring says so and the default is to report only.

Check

What it finds

Figures

Amplitude

flat channels, dropouts, clipping (amplifier saturation), NaN samples

channels

Bridging

EEG electrodes shorted by gel (MNE’s electrical-distance method)

topomap, distances

Impedance

electrodes above an impedance limit (BrainVision header or given values)

channels

OutlierChannels

channels unlike the others (Local Outlier Factor), any system

scores

NarrowbandNoise

line noise, other narrowband noise sources, channels with poor contact

psd, line_channels

Muscle

share of time with muscle activity

scores

Blinks

blink rate, i.e. whether the EOG works

blinks

HeartRate

heart rate, i.e. whether the ECG works

heartbeats

HeadMovement

MEG head movement (cHPI or a .pos file) and HPI coil fit quality

positions, displacement

SquidJumps

flux jumps of SQUID sensors (FieldTrip’s jump detection)

jumps

ChpiSNR

HPI coils much weaker than the others (Neuromag)

snr

EmptyRoom

sensor noise floor and noisy sensors in the session’s empty-room recording (found in BIDS), empty room from another day

psd

Digitization

missing electrode positions, implausible head shape, unmeasured head position, head-to-sensor distance

headshape

Events

event counts against what was expected, duplicated triggers

timeline

BidsMetadata

BIDS sidecar disagreeing with the data or channels.tsv (sampling rate, duration, channel counts), missing line frequency

Photodiode (not run by default)

delay and jitter between triggers and the photodiode, stimuli not shown: meu.qc.Photodiode("PD").compute(raw)

delays

Bridged electrodes can also be repaired during preprocessing with meeg_utils.steps.BridgedElectrodes.

A whole dataset#

result = meu.qc.inspect_dataset("bids/", n_jobs=8)   # or a list of recordings
result.outliers        # recordings that stand out on a metric (modified z > 3.5)
result.to_csv("qc/summary.csv")
result.plot()["levels"]                               # recordings x checks overview

Reports#

meeg_utils.report.build() collects QC findings and figures, and the metrics and figures of every step of a fitted pipeline, in an mne.Report:

rep = meu.report.build(pipe, inst=raw, qc=meu.qc.inspect(raw))
rep.save("sub-01_report.html")