Skip to content
marcotag93Public

About

Fast gradient-scheme QC, b-vector flip detection, and repair for diffusion MRI and tractography pipelines.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

GradLint

Journal of Neuroscience Methods paper DOI Zenodo software archive DOI Latest release CI PyPI License: MIT

Fast, standalone gradient-scheme quality control, b-vector flip / axis-permutation detection, and auto-repair for diffusion MRI.

GradLint is a quality-control gate. It runs in seconds before a diffusion pipeline starts: it catches the silent wrong-bvec error, audits the b-value and gradient scheme, and — with --apply-repair — writes an authorized correction together with a machine-readable provenance log. The input gradient files are never modified in place unless explicitly requested.


👤 Author

Marco Tagliaferri — PhD Candidate in Neuroscience 🏛️ Center for Mind/Brain Sciences (CIMeC), University of Trento, Italy Email Email GitHub

If you use GradLint in your research, please cite the article:

Tagliaferri, M., & Cattaneo, L. (2027). GradLint: A fast, open-source Rust tool for b-vector flip detection, gradient-scheme QC, and auto-repair in diffusion MRI. Journal of Neuroscience Methods, 438, 110942. https://doi.org/10.1016/j.jneumeth.2026.110942

BibTeX:

@article{Tagliaferri_GradLint_2027,
  author    = {Tagliaferri, Marco and Cattaneo, Luigi},
  title     = {{GradLint}: A fast, open-source {Rust} tool for b-vector flip detection, gradient-scheme {QC}, and auto-repair in diffusion {MRI}},
  journal   = {Journal of Neuroscience Methods},
  year      = {2027},
  volume    = {438},
  pages     = {110942},
  doi       = {10.1016/j.jneumeth.2026.110942},
  url       = {https://doi.org/10.1016/j.jneumeth.2026.110942}
}

Citation metadata is provided in CITATION.cff and is available through GitHub's "Cite this repository" button, with the article as the preferred citation.


Install

pip install gradlint

Alternatively, install the gradlint command in its own isolated environment with pipx:

pipx install gradlint

Both routes install from the same pre-built PyPI wheels.

This installs the single gradlint command — every subcommand (inspect, audit, detect-flip, recompute-bval) and every flag — plus the importable gradlint Python package. The command is a thin wrapper; all computation runs in the native Rust core compiled into the wheel. The published wheels are pure-Rust and need no C toolchain.

Build from source

The build backend is maturin (pulled in automatically), so a Rust toolchain (≥ 1.74) is the only prerequisite:

pip install ./crates/py

Faster decompression (libdeflate)

On large, fine-resolution scans (e.g. HCP 1.25 mm, 288 volumes) the audit is bound by gzip decompression of the DWI. The opt-in libdeflate feature roughly halves the decompress stage with byte-identical output (same verdict, same report). It is off by default. Enable it at build time via either toolchain:

# cargo
cargo build --release -p gradlint-cli --features libdeflate

# pip
pip install ./crates/py --config-settings=build-args="--features libdeflate"

The feature compiles a small C library. On ~2 mm data the audit is already a few seconds, so most users do not need it.

Linker note for source builds

This applies to every source build — cargo, pip install ./crates/py, with or without libdeflate. If linking fails with rust-lld: error: undefined symbol: getauxval, a non-system linker is earlier on your PATH (common with FSL or conda toolchains). Export the system linker first, then re-run the install or build command:

export PATH="/usr/bin:$PATH" CC=/usr/bin/gcc CXX=/usr/bin/g++
export RUSTFLAGS="-C linker=/usr/bin/gcc -C link-self-contained=no -C linker-features=-lld"

The variables must be exported in the same shell before building; a fresh shell needs them again. The pre-built PyPI wheels are unaffected — this is only relevant when compiling from source.

Usage

GradLint is a single command-line tool with one importable Python package behind it. Every subcommand and flag below — including --bids, --profile, and --figures — is available from the same gradlint install; there is no separate or feature-reduced build.

Use --nthreads N to choose the number of worker threads (for example, --nthreads 8); omit it to keep the default environment/automatic setting. The Python API accepts threads=N. With --profile, the effective worker count is printed alongside the timings.

Command line

# Inspect the gradient scheme only (no image needed)
gradlint inspect --bvec dwi.bvec --bval dwi.bval

# Full audit: scheme QC + b-vector flip detection, write a JSON report
gradlint audit --bvec dwi.bvec --bval dwi.bval --dwi dwi.nii.gz \
  --mask wm.nii.gz --report report.json

# Repair after verifying volume-to-gradient pairing (writes a corrected copy)
gradlint audit --apply-repair --bvec dwi.bvec --bval dwi.bval --dwi dwi.nii.gz \
  --out-bvec dwi.fixed.bvec --out-bval dwi.fixed.bval --provenance prov.json

# Opt-in: recover b-values hidden in amplitude-encoded bvec norms (separate from repair)
gradlint recompute-bval --bvec dwi.bvec --bval dwi.bval \
  --out-bvec dwi.bvec.recovered --out-bval dwi.bval.recovered

# BIDS dataset: audit every *_dwi and write derivatives under derivatives/gradlint/
gradlint audit --bids /data/my_bids

# Audit with a per-stage timing breakdown (decompress / convert / fit / coherence)
gradlint audit --bvec dwi.bvec --bval dwi.bval --dwi dwi.nii.gz --profile

# Use a 0.5% margin (the default is 2%)
gradlint audit --bvec dwi.bvec --bval dwi.bval --dwi dwi.nii.gz \
  --margin-threshold 0.005

# Audit and write the HTML report, standard figures, and tensor glyph QC
gradlint audit --bvec dwi.bvec --bval dwi.bval --dwi dwi.nii.gz \
  --figures qc_figures

audit ranks all 48 candidate gradient conventions by fibre coherence, mark the current table and the best-scoring convention, and end with a one-line verdict. When a flip is detected, the fix is reported as a per-axis remap (e.g. x=-x, y=+y, z=+z). Progress streams to stderr; the machine-readable verdict goes to stdout, so the run is safe to pipe.

Outcomes

For audit, exit 0 allows continuation; exit 3 requires review.

Verdict Exit Meaning
PASS 0 No orientation correction needed.
WARN 3; 0 if corrected Correction withheld or scheme-quality warning.
FLAG 0 Confident flip; authorized correction written.

Writing requires --apply-repair; a thin-margin WARN also needs --force-repair. Without consent, nonidentity proposals return WARN/exit 3; flip.decision retains the detector result. Verify volume-to-gradient pairing before applying corrections: GradLint cannot exclude reordering errors.

A false repair on good data is the worst outcome, so detection is deliberately specificity-first: when the margin is ambiguous GradLint emits WARN rather than an automatic repair.

--margin-threshold F sets the relative margin for a FLAG decision (default 0.02 = 2%; 0.005 = 0.5%) in audit and detect-flip, including BIDS audits. Higher values withhold more repairs as WARN; lower values permit more FLAG decisions without changing the candidate ranking. Nondefault values are recorded in report.json and shown in the margin figure so saved reports can be interpreted correctly.

The FLAG verdict marks a flagged gradient table, not a tool failure. It is the verdict everywhere — terminal, rendered reports, and the report.json status field when an authorized FLAG correction is written, with exit code 0. Use the corrected outputs in downstream steps. Without write consent, audit returns overall WARN/exit 3 for a nonidentity proposal, including in BIDS and profiled runs; flip.decision still records FLAG or WARN. detect-flip remains a diagnostic-only command; use audit for the complete pipeline gate.

Masks. Flip detection is most sensitive over white matter. With no --mask, GradLint derives a foreground-gated FA white-matter proxy automatically. A supplied --mask is used as-is; a focused WM (FA-thresholded) mask gives a wider detection margin than a loose whole-brain mask. A mask whose grid does not match the DWI, or a gradient table whose direction count differs from the number of DWI volumes, is rejected with a clear error — GradLint never fits on mismatched inputs.

Scheme quality. Beyond flip detection, every inspect/audit emits advisory notes for scheme issues (too few directions, an ill-conditioned DTI design matrix, duplicate directions, non-integer b-values, b0 drift). These are advisory by default — the verdict follows the flip decision. Pass --strict to gate on them: a severe scheme issue then promotes PASS to WARN (it never changes a flip verdict, so it cannot trigger a repair).

Amplitude-encoded bvecs. Some exports leave the b-vector columns non-unit-length, with the b-value hidden in the vector norm (b ∝ |g|²) while the bval file holds only a single nominal value. Trusting that bval silently corrupts downstream tensors. GradLint reports the non-b0 unit-norm statistics (count, fraction, min/max/mean norm) in the JSON, and when a majority of non-b0 directions deviate from unit length it raises WARN (with --strict, a hard error and non-zero exit) naming the likely cause. GradLint does not auto-correct it; recover the b-values explicitly with recompute-bval (below). The --norm-tolerance flag tunes the per-direction deviation threshold (default 0.05).

Frame handling (FSL ↔ MRtrix). Each gradient format is interpreted in its own stored frame: FSL bvec is image-relative (with the affine-determinant x-flip convention), MRtrix .b is in world/scanner coordinates. audit --apply-repair reads the input in its own frame, detects the flip there, and emits each requested output in that format's correct frame, converting across formats through the image affine. So you can feed --bvec/--bval and ask for --out-grad (a true world .b), or feed --grad and ask for --out-bvec (a true FSL bvec), and both are correct — matching MRtrix's own mrinfo -export_grad_{mrtrix,fsl}. Same-format output is numerically unchanged. --dwi (required when requesting repair) supplies the affine. When the image's FSL and world frames genuinely diverge (positive determinant with a multi-axis rotation, e.g. diag(-2,-2,2)), an advisory note is added so a cross-format hand-off is not applied blindly. (recompute-bval takes no image and so keeps same-format output.)

Options

All options are long form: value-taking options are --name value, switches are bare --name, and they combine on one command line as in the examples above. Run gradlint --help, or gradlint <command> --help, for the authoritative per-command list.

Gradient input (one is required):

  • --bvec FILE --bval FILE — FSL-style gradient table.
  • --grad FILE — MRtrix .b gradient table (used instead of --bvec/--bval).

Image input (audit, detect-flip):

  • --dwi FILE — the diffusion-weighted NIfTI (.nii or .nii.gz).
  • --mask FILE — optional brain/WM mask; a WM mask gives the most sensitive detection.

Scheme-QC tuning:

  • --tolerance F — relative tolerance for shell clustering (default 0.05).
  • --b0-threshold F — b-value at/below which a volume counts as a b0 (default 50).
  • --shell B — working shell for the DTI fit (default: auto-selected).
  • --step VOXELS — coherence sampling step; flip detection only (default: auto from voxel size).
  • --margin-threshold F — relative margin required for a FLAG decision; flip detection only (default 0.02 = 2%).
  • --strict — let a severe scheme finding promote PASS to WARN, and a majority amplitude-encoded bvec finding become a hard error.
  • --norm-tolerance F — per-direction unit-norm tolerance for the amplitude-encoded bvec check (default 0.05).

Output:

  • --report FILE — write the canonical JSON report.
  • --figures DIR — write a self-contained report.html plus figure PNGs. Image-based commands also write glyphs.png: sagittal, coronal, and axial DTI principal-direction views comparing the input table with GradLint's best candidate, plus anatomical locator views. Titles distinguish an applied correction from a withheld, previewed, dry-run, or unnecessary correction. Rendering uses matplotlib and jinja2, imported only when this flag is given; without it, GradLint neither transfers tensor fields to Python nor loads plotting dependencies.

Optional repair within audit:

  • --out-bvec FILE / --out-bval FILE / --out-grad FILE — destinations for the corrected gradients.
  • --provenance FILE — write a JSON provenance log of the repair.
  • --dry-run — report what would change without writing anything.
  • --force — allow existing corrected output paths to be overwritten (a .bak backup is kept).
  • --apply-repair — authorize correction writing.
  • --force-repair — also accept a thin-margin WARN; requires --apply-repair.

Writing and dry runs require --dwi, --out-bvec, and --out-bval. Output/provenance paths require --apply-repair or --dry-run. Repair options cannot be combined with --bids or --profile. Python: audit(..., apply_repair=True).

Recompute-bval only (opt-in b-value recovery from amplitude-encoded bvecs; never run from audit):

  • --out-bvec FILE / --out-bval FILE / --out-grad FILE — destinations for the recovered gradients (unit-normalized bvec + recovered bval). Defaults derive a .recovered sibling of the input.
  • --b0-threshold F — b-value at/below which a volume counts as a b0 (default 50).
  • --provenance FILE — write a JSON provenance log of the recovery.
  • --dry-run — report the recovery (per-shell before/after) without writing anything.
  • --force — overwrite the input gradients (a .bak backup is kept).

Batch / profiling (audit):

  • --bids DIR — discover and audit every *_dwi in a BIDS dataset, writing per-run and dataset reports under derivatives/gradlint/ (both the .bvec/.bval and HCP .bvecs/.bvals spellings are recognised, and a grid-matching sibling mask is used automatically).
  • --profile — print a per-stage timing breakdown (decompress / convert / fit / coherence); requires --dwi.

Global:

  • -h, --help — usage.
  • -v, --version — package and core version, plus the libdeflate build status (on/off).

Terminal output is coloured on a TTY and plain when piped (honours NO_COLOR).

Python

import gradlint as gg

report = gg.audit("dwi.bvec", "dwi.bval", dwi="dwi.nii.gz")
print(report.status, report.flip.best.label)

# After checking volume-to-gradient pairing:
fixed = gg.audit(
    "dwi.bvec", "dwi.bval", dwi="dwi.nii.gz", apply_repair=True,
    out_bvec="dwi.fixed.bvec", out_bval="dwi.fixed.bval",
    provenance="provenance.json",
)

# Render the canonical report to self-contained HTML (figures embedded)
open("report.html", "w").write(gg.render_html(report))

The standard report figures can also be rendered from a previously saved --report JSON, with no recomputation and no re-reading of the DWI:

from pathlib import Path

from gradlint import load_report, render_html
from gradlint.report.figures import save_figures

report = load_report("report.json")          # written by `gradlint ... --report`
open("report.html", "w").write(render_html(report))
save_figures(report, Path("figures"))         # individual PNGs

The tensor glyph figure is intentionally generated only during an image-based CLI run with --figures: its fitted tensor field is not stored in report.json. In glyphs.png, the first column is the input table before correction, the second visualizes GradLint's best candidate, and the third shows the crop location in the full anatomical slice. The second-column title states whether that candidate was applied, force-applied from WARN, withheld, shown only as a FLAG preview, computed during a dry run, or unnecessary on PASS. The HTML report uses the same correction status. Both glyph columns use the same voxels and background, so differences reflect only the candidate gradient transform.

Example notebook

A runnable quickstart notebook walks through the full workflow — inspect, audit, authorize correction, and verify — on a public diffusion MRI example, downloaded automatically on first run. It needs only pip install gradlint dipy.

Layout

crates/core   Rust core: I/O, metrics, DTI fit, coherence index, repair, BIDS batch
crates/cli    Internal reference binary over the same core (not the published CLI)
crates/py     Python bindings (PyO3 / maturin), the gradlint CLI, and report rendering
tests/        Cross-language smoke and integration tests

Data flows one way: the Rust core computes every metric and emits a canonical JSON report, which the Python layer renders to Markdown/HTML. With --figures, the core also returns the already-fitted tensor field needed for glyph rendering; it is not fitted a second time. No metric logic is duplicated in Python.

Validation

GradLint was compared with MRtrix3 dwigradcheck on three multi-shell DWI cohorts: HCP1200 (N = 105), single-site (N = 57), and OpenNeuro ds007908 (N = 28). Testing covered acquired tables and injected axis flips under Rician noise and direction-count reduction:

  • 0 false repairs on 133 clean acquired tables.
  • 1106/1106 correct top-ranked conventions, with 100% antipode-aware agreement with dwigradcheck; all 1022 FLAG repairs matched the oracle, while 84 WARN cases withheld repair. Writing requires --apply-repair for FLAG output and both repair flags for WARN output.
  • Frame-aware FSL ↔ MRtrix emission passed all numerical comparisons in 190/190 subjects through both input routes.

With GradLint 1.1.5 and libdeflate, median paired speed-ups were 12.90–23.25× at eight threads per tool and 5.27–8.91× with automatic/default threading across the three tested cohort groups. These results concern the tested axis flips; they do not establish repair safety for errors outside that model.

Development

Requires a Rust toolchain (≥ 1.74), Python (≥ 3.10), and maturin.

# Rust
cargo test --workspace --exclude gradlint-py
cargo clippy --workspace --exclude gradlint-py -- -D warnings

# Python bindings: editable dev install into the active virtual environment
maturin develop --release -m crates/py/Cargo.toml
pytest

maturin develop rebuilds the native extension into the current virtual environment on each call — use it while iterating on the Rust core.

License

MIT — see LICENSE.

About

Fast gradient-scheme QC, b-vector flip detection, and repair for diffusion MRI and tractography pipelines.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages