Catch silently-broken NIfTI geometry (qform/sform mismatch, bad affines, misaligned inputs) before it ruins your results.
Project description
nifti-qc
Catch silently-broken NIfTI geometry before it ruins your results.
A NIfTI file stores its world→voxel mapping twice — the quaternion
qform and the affine sform. Different tools trust different ones:
nibabel and most Python tooling follow the
sform; some C/registration tools (e.g. greedy) read the qform. When an
earlier step updates only one of them — SPM co-registration is the classic
culprit — the two disagree and your image is silently mislocated in world
space. No error, no crash: just a segmentation, overlay, or registration
that lands in the wrong place.
nifti-qc is a tiny, dependency-light QC gate that catches that trap and a
handful of related geometry/data problems, so you find them in seconds instead
of in a downstream result three steps later.
This tool grew out of a real fix for exactly this bug in the MS-lesion segmentation pipeline LST-AI (CompImg/LST-AI#45), where a qform/sform mismatch mislocated lesion masks in FLAIR space.
Install
pip install nifti-qc # once published
# or from source:
pip install -e .
Requires only nibabel and numpy.
Use it on the command line
# QC a single file
nifti-qc scan t1.nii.gz
# QC each file AND check they share a grid / world space (e.g. T1 + FLAIR)
nifti-qc scan t1.nii.gz flair.nii.gz
# machine-readable, for CI / pipelines
nifti-qc scan *.nii.gz --json
Example output:
sub-01_flair.nii.gz [RAS]
ERROR qform_sform_mismatch: qform and sform disagree: up to 10.000 mm
translation and 0.000 deg rotation apart. Tools that read the qform
(e.g. greedy) and tools that read the sform (e.g. nibabel) will place
this image differently.
PROBLEMS: 1 error(s), 0 warning(s) across 1 file(s)
The command exits non-zero when any error-severity problem is found, so it drops straight into a CI step or a pre-processing script as a gate:
nifti-qc scan "$T1" "$FLAIR" || { echo "fix your inputs first"; exit 1; }
Use it in CI (GitHub Action)
A composite Action ships with the repo, so a workflow can gate on NIfTI geometry with no setup — the job fails if any file has an error-severity problem:
- uses: CedricConday/nifti-qc@main
with:
paths: data/*.nii.gz
# args: "--no-align" # optional flags for `nifti-qc scan`
Use it as a library
import nifti_qc
report = nifti_qc.scan(["t1.nii.gz", "flair.nii.gz"])
if not report.ok:
for fr in report.files:
for f in fr.findings:
print(fr.path, f.code, f.severity, f.message)
for f in report.alignment:
print("align", f.code, f.message)
Every check is also importable on its own (check_affine, check_geometry,
check_data, check_alignment) and returns plain Finding dataclasses.
What it checks
| Check | Severity | What it means |
|---|---|---|
qform_sform_mismatch |
error | qform and sform disagree — the silent-mislocation trap |
no_valid_affine |
error | both codes are 0; world orientation undefined |
qform_unset / sform_unset |
warn | only one transform is set; some tools will misread |
nonpositive_voxel_size |
error | a voxel dimension is ≤ 0 |
extreme_anisotropy |
warn | very non-isotropic voxels (default ≥ 5:1) |
non_orthonormal_direction |
warn | affine encodes shear — usually a corrupt header |
non_finite_data |
error | NaN/Inf voxels |
empty_volume |
warn | volume is all zeros |
grid_shape_mismatch |
warn | inputs live on different voxel grids (resampling needed) |
voxel_size_mismatch |
warn | inputs have different voxel sizes |
world_space_mismatch |
info | inputs are not co-registered in world space |
Each finding carries a machine-readable detail dict (e.g. the exact
translation in mm and rotation in degrees) for programmatic use.
Why trust the numbers
Every check has a unit test that builds a synthetic NIfTI designed to trigger it and asserts on the reported magnitude — the qform/sform mismatch test constructs a 10 mm offset and asserts the tool reports 10 mm. Run them:
pip install -e ".[test]"
pytest -q
Scope
nifti-qc reports problems; it does not fix them. To repair a qform/sform
mismatch you decide which transform is authoritative and harmonize to it — see
the LST-AI fix linked above for one approach. Keeping detection and repair
separate is deliberate: the right fix depends on which upstream tool you trust.
License
MIT © Cedric Conday
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nifti_qc-0.1.0.tar.gz.
File metadata
- Download URL: nifti_qc-0.1.0.tar.gz
- Upload date:
- Size: 12.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
782f3edea9c9f943a860cbbdd260fdcaaa4aa9895792019ca9211ea9c3b49919
|
|
| MD5 |
ccf1b31e88576c64859d1768f1cd95de
|
|
| BLAKE2b-256 |
58e8e1524efaad87e1e9322766b0670222563f2b310b97cccbe5436ff63ad3cb
|
Provenance
The following attestation bundles were made for nifti_qc-0.1.0.tar.gz:
Publisher:
publish.yml on CedricConday/nifti-qc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nifti_qc-0.1.0.tar.gz -
Subject digest:
782f3edea9c9f943a860cbbdd260fdcaaa4aa9895792019ca9211ea9c3b49919 - Sigstore transparency entry: 2047335788
- Sigstore integration time:
-
Permalink:
CedricConday/nifti-qc@fe9822be6cbd7ecd7a344ab3703e7b7f4a19e513 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/CedricConday
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fe9822be6cbd7ecd7a344ab3703e7b7f4a19e513 -
Trigger Event:
release
-
Statement type:
File details
Details for the file nifti_qc-0.1.0-py3-none-any.whl.
File metadata
- Download URL: nifti_qc-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a348131af842a203b6ff819e8cf22a23c63a8ebd93c219e356bd373cd58b75b4
|
|
| MD5 |
e5c219eab1bddc7d96b9c51a134336a5
|
|
| BLAKE2b-256 |
daa6d8d00c82b84a2f9dd864234c73d4ac8fcecfde8acaf3e0be2293022262df
|
Provenance
The following attestation bundles were made for nifti_qc-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on CedricConday/nifti-qc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nifti_qc-0.1.0-py3-none-any.whl -
Subject digest:
a348131af842a203b6ff819e8cf22a23c63a8ebd93c219e356bd373cd58b75b4 - Sigstore transparency entry: 2047335834
- Sigstore integration time:
-
Permalink:
CedricConday/nifti-qc@fe9822be6cbd7ecd7a344ab3703e7b7f4a19e513 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/CedricConday
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fe9822be6cbd7ecd7a344ab3703e7b7f4a19e513 -
Trigger Event:
release
-
Statement type: