Study level quality control summary#

What the summary is for#

By the time a study has been through preprocessing, QuNex has written a great deal of quality control information about it. PreFreeSurfer leaves a nonlinear warp into MNI space, FreeSurfer leaves its statistics tables, PostFreeSurfer leaves myelin maps and surface distortion measures, compute_bold_stats leaves movement and scrubbing tables for every BOLD run, and the diffusion pipeline leaves an eddy QC report. All of it is genuinely useful, and all of it is somewhere else — one file at a time, one session at a time, several folders deep.

The run_qc_summary command gathers it into one place. It reads what the pipelines already wrote, arranges it into two tables and a set of per-frame traces, and — unless you ask it not to — builds an interactive report you can open in a browser. It is a reporting command in the strict sense: it computes no new imaging results and it changes nothing it reads, so it is always safe to run and safe to run again.

Three questions it is meant to answer:

  • What does this study look like as a whole? How much movement is typical, what temporal SNR to expect, how tightly the sessions register to one another.

  • Which sessions should I look at first? Rather than opening thirty visual QC scenes in order, start with the sessions that stand out from the rest of your own sample.

  • Which sessions have quietly not been through something? A session that never made it past FreeSurfer looks fine until you go looking for its BOLD data. The summary says so on its row.

This is quantitative, study level QC. It complements rather than replaces the per-session visual QC of run_qc, which renders images you look at; nothing here draws a brain.

What it reads#

Stage

Read from

Reported as

prefs

the MNI brain and the PreFreeSurfer warp

brain volume in MNI, T1w/T2w ratio, warp Jacobian, registration similarity

fs

aseg.stats, ?h.aparc.stats, the white–gray contrast overlays

intracranial and brain-segmentation volume, surface holes, mean thickness, WM/GM contrast

postfs

the fs_LR myelin, thickness and distortion maps

myelin mean and asymmetry, areal and edge distortion, thickness, vertex counts

bold

the .bstats and .scrub files, and the dense timeseries

frame displacement, DVARS, flagged frames, temporal SNR, cortical coverage

dwi

the FSL eddy qc.json

absolute and relative motion, outlier slices, CNR

Which of these are read is up to you, through --qc_summary_modules. A stage you do not ask for is not read, and a stage you ask for that a session has not reached simply comes back blank for that session.

Running the command#

qunex run_qc_summary \
    --batchfile=<path to batch file> \
    --sessionsfolder=<path to sessions folder>

That is the whole of a normal invocation. Everything else has a default that suits a study processed the usual way.

General parameters. The command takes the same session selection and path parameters as the rest of QuNex, so it sees exactly the sessions and files your other commands see:

--batchfile      ... The batch.txt file with all the session information.
--sessionsfolder ... The path to the study/sessions folder [.].
--sessions       ... An optional pipe separated list of id codes of sessions to
                     report on. All sessions in the batch file if not specified.
--filter         ... An optional "<key>:<value>|<key>:<value>" filter, to report
                     only on sessions matching all the specified key:value pairs.
--bolds          ... Which BOLD images to report on. A single type (e.g. 'task'),
                     a pipe separated list (e.g. 'WM|Control|rest'), or 'all' [all].
--boldname       ... The default name of the bold files in the images folder [bold].
--nifti_tail     ... The tail of NIfTI volume images to use. Selects the .bstats
                     and .scrub files the BOLD statistics are read from.
--cifti_tail     ... The tail of CIFTI images to use. Selects the dense timeseries
                     the temporal SNR and coverage are read from.
--bold_variant   ... Optional variant of bold preprocessing, if the BOLD images
                     live in images/functional<bold_variant>.
--img_suffix     ... Suffix for the 'images' folder, for parallel workflows.
--hcp_suffix     ... Suffix for the session's folder in the HCP folder.
--parelements    ... How many sessions to read at once [1].
--logfolder      ... Where to store the log, if other than default.

Note that --parelements, not --parsessions, is the parallelism knob here. The command runs once for the whole study rather than once per session, so it parallelises over the sessions inside itself.

Scrubbing parameters. The same four names the rest of QuNex uses, and they mean the same thing:

--mov_bad     ... Which criterion to report bad frames by: mov, dvars, dvarsme,
                  idvars, idvarsme, udvars or udvarsme [udvarsme].
--mov_fd      ... Frame displacement threshold, in mm [0.5].
--mov_dvars   ... The (mean normalized) dvars threshold [3.0].
--mov_dvarsme ... The (median normalized) dvarsm threshold [1.5].

See BOLD movement scrubbing for what each criterion means. How these are used here is worth reading carefully, and has a section of its own below.

QC summary specific options. These control what is compiled and how much of it:

--qc_summary_modules      ... Which processing stages to compile information for. A
                              space, comma or pipe separated list of prefs, fs,
                              postfs, bold and dwi [prefs fs postfs bold dwi].
--qc_summary_report       ... Whether to build the interactive HTML report (yes) or
                              to write the tables alone (no) [yes].
--qc_summary_tsnr         ... Whether to compute BOLD temporal SNR and cortical
                              coverage (yes) or not (no). This reads every dense
                              timeseries and is the slowest part of the command [yes].
--qc_summary_warp_jac     ... Whether to compute the Jacobian determinant of the
                              PreFreeSurfer warp (yes) or not (no). Requires FSL;
                              without it the columns are left blank [yes].
--qc_summary_mni_template ... Path to an MNI template to compute registration
                              similarity against. Without it, similarity is computed
                              against the study's own group mean alone [].
--qc_summary_diag_k       ... How many robust standard deviations from the study's
                              own middle a value has to be before it is called
                              atypical. Lower is more sensitive [3.0].

What it writes#

Each run writes a folder of its own into the study's QC folder, named after the moment it started:

<sessionsfolder>/QC/qc_summary_2026-08-13_14.32.21/
    qc_summary.tsv               one row per session
    qc_summary_bold.tsv          one row per session and BOLD run
    qc_summary_bold_traces.json  the per-frame measures
    qc_summary_report.html       the interactive report

Because the folder carries the time the run started, the results of several runs sit side by side and nothing is ever overwritten — which is also why --overwrite has nothing to do here. Run the command again with different thresholds and you can compare the two folders directly. (Two runs started inside the same second would name the same folder, so the second takes a _2 suffix, the third a _3.)

qc_summary.tsv has one row per session, tab separated, with a session column first and then the measures grouped by stage — registration, FreeSurfer, surfaces, BOLD rolled up to the session by its worst run, and diffusion. It ends with four columns that describe the session as a whole:

  • likely_failure_step — the earliest processing step that could explain what looks wrong, or OK.

  • diagnosis_evidence — why, in words, so you can judge the call rather than take it.

  • completeness — how many of the requested stages this session produced data for, as N/M.

  • stages_missing — which ones it did not.

A column that no session in the study has a value for is left out of the file entirely, rather than written as a screen's width of blanks. So if you ask for all five stages in a study that has not been through diffusion, you will not see the dwi_ columns at all.

qc_summary_bold.tsv has one row per session and run:

session  run  n_frames  fd_mean  fd_max  fd_pct_over  dvarsme_mean  dvarsme_max
dvarsme_pct_over  dvars_mean  n_bad  pct_bad  n_both  pct_both  frames_retained
tsnr_median  coverage_pct  criterion  flags  fdt  dvarsmet

The last four are the provenance of the three before them: which criterion the counts are for, whether they came from your pipeline or were recomputed, and the thresholds used.

qc_summary_bold_traces.json holds the per-frame measures — frame displacement, DVARS, DVARSme and the bad-frame mask — for every run, keyed <session>/<run>. It is what the report plots, and it is there if you want to plot something else.

Flagged frames are the ones your pipeline excluded#

This is the part most worth understanding, because it is what makes the numbers trustworthy.

When compute_bold_stats scrubbed your data it wrote a .scrub file for every run, holding one column per criterion with your study's own thresholds and your --mov_before / --mov_after padding already applied. run_qc_summary reads that column. So n_bad, pct_bad and frames_retained are the frames your preprocessing actually excluded — not an independent estimate that might disagree with it. The flags column reads pipeline to tell you so.

Sometimes you want the other thing: what would have been excluded at a different threshold. Give --mov_fd, --mov_dvars or --mov_dvarsme a value the run was not processed with, and the counts are recomputed from the per-frame traces instead. The row then says flags = recomputed, and the fdt and dvarsmet columns record what was used, so an exploratory number can never be mistaken for your pipeline's:

# what your pipeline actually did -- flags = pipeline
qunex run_qc_summary \
    --batchfile=processing/batch.txt \
    --sessionsfolder=sessions

# what a stricter threshold would have cost -- flags = recomputed
qunex run_qc_summary \
    --batchfile=processing/batch.txt \
    --sessionsfolder=sessions \
    --mov_fd=0.3

The report's BOLD runs tab lets you explore thresholds interactively in the same way, and marks the view clearly when what you are looking at is no longer your pipeline's scrubbing.

Sessions that have not been through everything#

A session missing a processing stage is not an error here, and it does not stop the run. It contributes blank columns for that stage, a line in the log saying what could not be found, and an entry in completeness and stages_missing. That is the summary doing its job: which sessions are missing what is one of the things you came to find out.

The same is true of a file that cannot be read. A truncated .bstats costs that one run its row and nothing else; the session keeps its other runs and the study keeps its other sessions.

Only one thing counts as a failure: being unable to build the report you asked for. If the template cannot be found — most often because the qx_library submodule is not checked out, or the QuNex environment is not sourced so QUNEXPATH is unset — the command reports a named error and a non-zero status. The tables it had already written stay on disk and are named in the log. If you do not need the report, --qc_summary_report=no writes the tables alone and the run succeeds.

The interactive report#

qc_summary_report.html is a single self-contained file. Everything it needs travels inside it, including the plotting library, so it opens from a file, over a share, or on a cluster node with no route to the internet. It is around 4 MB for that reason. It has four tabs:

  • By parameter — one metric across the whole study, with a cutoff you set and a list of the sessions beyond it.

  • Participant profiles — one session against the rest, every metric at once, with a composite deviation ranking to triage by.

  • BOLD runs — the per-frame traces for a run, with the flagged frames shaded and thresholds you can explore.

  • Diagnosis — the sessions grouped by the step their trouble most likely arose at, with the evidence for each.

Making a run faster#

Reading every dense timeseries is by a wide margin the slowest part of the command, and it is the only part that touches the BOLD image data. Two switches matter:

qunex run_qc_summary \
    --batchfile=processing/batch.txt \
    --sessionsfolder=sessions \
    --parelements=8 \        # read eight sessions at once
    --qc_summary_tsnr=no     # skip temporal SNR and cortical coverage

With --qc_summary_tsnr=no the BOLD table still carries everything from the movement and scrubbing files; only tsnr_median and coverage_pct are left blank. It is a good first pass on a large study.

Example#

A complete run over a study, with the report:

qunex run_qc_summary \
    --batchfile=processing/batch.txt \
    --sessionsfolder=sessions \
    --parelements=8

The command reports as it works, one block per session as that session is finished, so you can follow a long run rather than wait on it:

------------------------------------------------------------
Study QC summary
[started on Thursday, 13. August 2026 14:32:21]
Compiling study QC summary ...
---> Using parameters
     ... --qc_summary_modules: ['prefs', 'fs', 'postfs', 'bold', 'dwi']
     ... --qc_summary_report: True
     ... --qc_summary_tsnr: True
     ... --mov_bad: udvarsme
     ... --mov_fd: 0.5
---> Writing results to sessions/QC/qc_summary_2026-08-13_14.32.21
---> Compiling information for 39 sessions, 8 at a time
---> Working on OP1000 ...
     ... 31 measures read, 4 BOLD runs
---> Working on OP1001 ...
     ... no eddy qc.json, skipping the diffusion measures
     ... 27 measures read, 4 BOLD runs
...
---> Comparing each session's registration against the study
     ... wrote qc_summary.tsv (39 rows)
     ... wrote qc_summary_bold.tsv (152 rows)
     ... wrote qc_summary_bold_traces.json (152 runs)
     ... wrote qc_summary_report.html (4.1 MB)

and finishes with a one-line summary of what it found:

compiled QC information for 39 sessions and 152 BOLD runs, 3 sessions missing a
stage, 5 flagged for review, 4 files written

Two narrower runs. The structural stages only, tables without a report:

qunex run_qc_summary \
    --batchfile=processing/batch.txt \
    --sessionsfolder=sessions \
    --qc_summary_modules="prefs fs postfs" \
    --qc_summary_report=no

and a single session's resting-state runs, for a closer look at one participant:

qunex run_qc_summary \
    --batchfile=processing/batch.txt \
    --sessionsfolder=sessions \
    --sessions=OP1025 \
    --bolds=rest

As with every processing command, adding --test reports what the command would read and write without creating anything, which is a cheap way to check that your selection and paths resolve the way you expect.