{"id":51229669,"url":"https://github.com/marcotag93/gradlint","last_synced_at":"2026-06-28T15:01:18.598Z","repository":{"id":367345065,"uuid":"1272298477","full_name":"marcotag93/GradLint","owner":"marcotag93","description":"Fast gradient-scheme QC, b-vector flip detection, and repair for diffusion MRI and tractography pipelines.","archived":false,"fork":false,"pushed_at":"2026-06-25T16:00:10.000Z","size":182,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-25T16:21:19.582Z","etag":null,"topics":["bvec","csd","diffusion-mri","dti","dwi","neuroimaging","python","quality-control","rust","tractography"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcotag93.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":".zenodo.json","notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-17T13:29:32.000Z","updated_at":"2026-06-25T15:56:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/marcotag93/GradLint","commit_stats":null,"previous_names":["marcotag93/gradlint"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/marcotag93/GradLint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcotag93%2FGradLint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcotag93%2FGradLint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcotag93%2FGradLint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcotag93%2FGradLint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcotag93","download_url":"https://codeload.github.com/marcotag93/GradLint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcotag93%2FGradLint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34892547,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bvec","csd","diffusion-mri","dti","dwi","neuroimaging","python","quality-control","rust","tractography"],"created_at":"2026-06-28T15:01:17.514Z","updated_at":"2026-06-28T15:01:18.574Z","avatar_url":"https://github.com/marcotag93.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GradLint\n\n[![CI](https://github.com/marcotag93/GradLint/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/marcotag93/GradLint/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/gradlint.svg?cacheSeconds=3600)](https://pypi.org/project/gradlint/)\n[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20847595.svg)](https://doi.org/10.5281/zenodo.20847595)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nFast, standalone **gradient-scheme quality control**, **b-vector flip / axis-permutation\ndetection**, and **auto-repair** for diffusion MRI.\n\nGradLint is a quality-control *gate*. It runs in seconds before a diffusion pipeline starts: it catches the silent wrong-`bvec` error,\naudits the b-value and gradient scheme, and — when a flip is found — emits a corrected\ngradient table together with a machine-readable provenance log. The input gradient files\nare never modified in place unless explicitly requested.\n\n## Install\n\n```bash\npip install gradlint\n```\n\nThis installs the single `gradlint` command — every subcommand (`inspect`, `audit`,\n`detect-flip`, `repair`, `recompute-bval`) and every flag — plus the importable `gradlint`\nPython package.\nThe command is a thin wrapper; all computation runs in the native Rust core compiled into\nthe wheel. The published wheels are pure-Rust and need no C toolchain.\n\n### Build from source\n\nThe build backend is [maturin](https://www.maturin.rs/) (pulled in automatically), so a\nRust toolchain (≥ 1.74) is the only prerequisite:\n\n```bash\npip install ./crates/py\n```\n\n### Faster decompression (`libdeflate`)\n\nOn large, fine-resolution scans (e.g. HCP 1.25 mm, 288 volumes) the audit is bound by\ngzip decompression of the DWI. The opt-in `libdeflate` feature roughly halves the\ndecompress stage with **byte-identical output** (same verdict, same report). It is off by\ndefault. Enable it at build time via either toolchain:\n\n```bash\n# cargo\ncargo build --release -p gradlint-cli --features libdeflate\n\n# pip\npip install ./crates/py --config-settings=build-args=\"--features libdeflate\"\n```\n\nThe feature compiles a small C library. On ~2 mm data the audit is already a few seconds,\nso most users do not need it.\n\n### Linker note for source builds\n\nThis applies to **every** source build — `cargo`, `pip install ./crates/py`, with or\nwithout `libdeflate`. If linking fails with `rust-lld: error: undefined symbol: getauxval`,\na non-system linker is earlier on your `PATH` (common with FSL or conda toolchains). Export\nthe system linker first, then re-run the install or build command:\n\n```bash\nexport PATH=\"/usr/bin:$PATH\" CC=/usr/bin/gcc CXX=/usr/bin/g++\nexport RUSTFLAGS=\"-C linker=/usr/bin/gcc -C link-self-contained=no -C linker-features=-lld\"\n```\n\nThe variables must be exported in the same shell before building; a fresh shell needs them\nagain. The pre-built PyPI wheels are unaffected — this is only relevant when compiling from\nsource.\n\n## Usage\n\nGradLint is a single command-line tool with one importable Python package behind it.\nEvery subcommand and flag below — including `--bids`, `--profile`, and `--figures` — is\navailable from the same `gradlint` install; there is no separate or feature-reduced build.\n\n### Command line\n\n```bash\n# Inspect the gradient scheme only (no image needed)\ngradlint inspect --bvec dwi.bvec --bval dwi.bval\n\n# Full audit: scheme QC + b-vector flip detection, write a JSON report\ngradlint audit --bvec dwi.bvec --bval dwi.bval --dwi dwi.nii.gz \\\n  --mask wm.nii.gz --report report.json\n\n# Repair a detected flip (writes a corrected copy; never touches the input)\ngradlint repair --bvec dwi.bvec --bval dwi.bval --dwi dwi.nii.gz \\\n  --out-bvec dwi.fixed.bvec --out-bval dwi.fixed.bval --provenance prov.json\n\n# Opt-in: recover b-values hidden in amplitude-encoded bvec norms (separate from repair)\ngradlint recompute-bval --bvec dwi.bvec --bval dwi.bval \\\n  --out-bvec dwi.bvec.recovered --out-bval dwi.bval.recovered\n\n# BIDS dataset: audit every *_dwi and write derivatives under derivatives/gradlint/\ngradlint audit --bids /data/my_bids\n\n# Audit with a per-stage timing breakdown (decompress / convert / fit / coherence)\ngradlint audit --bvec dwi.bvec --bval dwi.bval --dwi dwi.nii.gz --profile\n\n# Audit and write the HTML report, standard figures, and tensor glyph QC\ngradlint audit --bvec dwi.bvec --bval dwi.bval --dwi dwi.nii.gz \\\n  --figures qc_figures\n```\n\n`audit` and `repair` rank all 48 candidate gradient conventions by fibre coherence,\nmark the current table and the best-scoring convention, and end with a one-line verdict.\nWhen a flip is detected, the fix is reported as a per-axis remap (e.g. `x=-x, y=+y, z=+z`).\nProgress streams to stderr; the machine-readable verdict goes to stdout, so the run is\nsafe to pipe.\n\n### Outcomes\n\nGradLint returns one of three verdicts. The exit code is intended for pipeline gating:\n`0` means the workflow may continue, while `3` means GradLint found a WARN condition\nthat was not repaired and should be reviewed.\n\n| Verdict        | Exit | Meaning                                                                                                                                                                                                                                                                              |\n| -------------- | :---: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| **PASS** | `0` | The gradient table is consistent with the image; no flip detected. Nothing is written.                                                                                                                                                                                               |\n| **WARN** | `3` when unrepaired, `0` with `repair --force-repair` | A likely issue with low confidence — the best convention's margin is below the auto-repair threshold. The repair is withheld unless `repair --force-repair` is used. With `--strict`, a severe scheme-quality finding also raises PASS to WARN. |\n| **FLAG** | `0` | A b-vector flip / axis-permutation is detected with high confidence. `repair` writes the corrected `bvec`/`bval` and a provenance log, allowing the downstream pipeline to continue with the repaired table. |\n\nA false repair on good data is the worst outcome, so detection is deliberately\nspecificity-first: when the margin is ambiguous GradLint emits WARN rather than an\nautomatic repair.\n\nThe `FLAG` verdict marks a flagged gradient table, not a tool failure. It is the\nverdict everywhere — terminal, rendered reports, and the `report.json` `status`\nfield — and maps to exit code `0`; use the corrected outputs from `repair`, not the\noriginal gradients, in downstream steps. `audit` and `detect-flip` do not write a\nrepair, so production pipelines that should auto-correct should gate on `repair`.\n\n**Masks.** Flip detection is most sensitive over white matter. With no `--mask`, GradLint\nderives a foreground-gated FA white-matter proxy automatically. A supplied `--mask` is\nused as-is; a focused WM (FA-thresholded) mask gives a wider detection margin than a loose\nwhole-brain mask. A mask whose grid does not match the DWI, or a gradient table whose\ndirection count differs from the number of DWI volumes, is rejected with a clear error —\nGradLint never fits on mismatched inputs.\n\n**Scheme quality.** Beyond flip detection, every `inspect`/`audit` emits advisory notes\nfor scheme issues (too few directions, an ill-conditioned DTI design matrix, duplicate\ndirections, non-integer b-values, b0 drift). These are advisory by default — the verdict\nfollows the flip decision. Pass `--strict` to gate on them: a *severe* scheme issue then\npromotes PASS to WARN (it never changes a flip verdict, so it cannot trigger a repair).\n\n**Amplitude-encoded bvecs.** Some exports leave the b-vector columns non-unit-length, with\nthe b-value hidden in the vector norm (`b ∝ |g|²`) while the `bval` file holds only a single\nnominal value. Trusting that `bval` silently corrupts downstream tensors. GradLint reports\nthe non-b0 unit-norm statistics (count, fraction, min/max/mean norm) in the JSON, and when a\nmajority of non-b0 directions deviate from unit length it raises **WARN** (with `--strict`, a\nhard error and non-zero exit) naming the likely cause. GradLint does **not** auto-correct it;\nrecover the b-values explicitly with `recompute-bval` (below). The `--norm-tolerance` flag\ntunes the per-direction deviation threshold (default `0.05`).\n\n**Frame handling (FSL ↔ MRtrix).** Each gradient format is interpreted in its own stored\nframe: FSL `bvec` is image-relative (with the affine-determinant x-flip convention), MRtrix\n`.b` is in world/scanner coordinates. `repair` reads the input in its own frame, detects the\nflip there, and emits **each requested output in that format's correct frame**, converting\nacross formats through the image affine. So you can feed `--bvec/--bval` and ask for\n`--out-grad` (a true world `.b`), or feed `--grad` and ask for `--out-bvec` (a true FSL bvec),\nand both are correct — matching MRtrix's own `mrinfo -export_grad_{mrtrix,fsl}`. Same-format\noutput is numerically unchanged. `--dwi` (already required by `repair`) supplies the\naffine. When the image's FSL and world frames genuinely diverge (positive determinant\nwith a multi-axis rotation, e.g. `diag(-2,-2,2)`), an advisory note is added so a\ncross-format hand-off is not applied blindly. (`recompute-bval` takes no image and so\nkeeps same-format output.)\n\n### Options\n\nAll options are long form: value-taking options are `--name value`, switches are bare\n`--name`, and they combine on one command line as in the examples above. Run\n`gradlint --help`, or `gradlint \u003ccommand\u003e --help`, for the authoritative per-command list.\n\n**Gradient input** (one is required):\n\n- `--bvec FILE --bval FILE` — FSL-style gradient table.\n- `--grad FILE` — MRtrix `.b` gradient table (used instead of `--bvec`/`--bval`).\n\n**Image input** (`audit`, `detect-flip`, `repair`):\n\n- `--dwi FILE` — the diffusion-weighted NIfTI (`.nii` or `.nii.gz`).\n- `--mask FILE` — optional brain/WM mask; a WM mask gives the most sensitive detection.\n\n**Scheme-QC tuning:**\n\n- `--tolerance F` — relative tolerance for shell clustering (default `0.05`).\n- `--b0-threshold F` — b-value at/below which a volume counts as a b0 (default `50`).\n- `--shell B` — working shell for the DTI fit (default: auto-selected).\n- `--step VOXELS` — coherence sampling step; flip detection only (default: auto from voxel size).\n- `--strict` — let a *severe* scheme finding promote PASS to WARN, and a majority amplitude-encoded bvec finding become a hard error.\n- `--norm-tolerance F` — per-direction unit-norm tolerance for the amplitude-encoded bvec check (default `0.05`).\n\n**Output:**\n\n- `--report FILE` — write the canonical JSON report.\n- `--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.\n\n**Repair only:**\n\n- `--out-bvec FILE` / `--out-bval FILE` / `--out-grad FILE` — destinations for the corrected gradients.\n- `--provenance FILE` — write a JSON provenance log of the repair.\n- `--dry-run` — report what would change without writing anything.\n- `--force` — allow existing corrected output paths to be overwritten (a `.bak` backup is kept).\n- `--force-repair` — apply the best convention on a thin-margin WARN (write-side only; the verdict stays WARN, but the process exits `0` because a repair was produced).\n\n**Recompute-bval only** (opt-in b-value recovery from amplitude-encoded bvecs; never run from `repair`):\n\n- `--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.\n- `--b0-threshold F` — b-value at/below which a volume counts as a b0 (default `50`).\n- `--provenance FILE` — write a JSON provenance log of the recovery.\n- `--dry-run` — report the recovery (per-shell before/after) without writing anything.\n- `--force` — overwrite the input gradients (a `.bak` backup is kept).\n\n**Batch / profiling** (`audit`):\n\n- `--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).\n- `--profile` — print a per-stage timing breakdown (decompress / convert / fit / coherence); requires `--dwi`.\n\n**Global:**\n\n- `-h`, `--help` — usage.\n- `-v`, `--version` — package and core version, plus the `libdeflate` build status (`on`/`off`).\n\nTerminal output is coloured on a TTY and plain when piped (honours `NO_COLOR`).\n\n### Python\n\n```python\nimport gradlint as gg\n\nreport = gg.audit(\"dwi.bvec\", \"dwi.bval\", dwi=\"dwi.nii.gz\")\nprint(report.status, report.flip.best.label)\n\n# Render the canonical report to self-contained HTML (figures embedded)\nopen(\"report.html\", \"w\").write(gg.render_html(report))\n```\n\nThe standard report figures can also be rendered from a previously saved\n`--report` JSON, with no recomputation and no re-reading of the DWI:\n\n```python\nfrom pathlib import Path\n\nfrom gradlint import load_report, render_html\nfrom gradlint.report.figures import save_figures\n\nreport = load_report(\"report.json\")          # written by `gradlint ... --report`\nopen(\"report.html\", \"w\").write(render_html(report))\nsave_figures(report, Path(\"figures\"))         # individual PNGs\n```\n\nThe tensor glyph figure is intentionally generated only during an image-based\nCLI run with `--figures`: its fitted tensor field is not stored in `report.json`.\nIn `glyphs.png`, the first column is the input table before correction, the\nsecond visualizes GradLint's best candidate, and the third shows the crop\nlocation in the full anatomical slice. The second-column title states whether\nthat candidate was applied, force-applied from WARN, withheld, shown only as a\nFLAG preview, computed during a dry run, or unnecessary on PASS. The HTML report\nuses the same correction status. Both glyph columns use the same voxels and\nbackground, so differences reflect only the candidate gradient transform.\n\n### Example notebook\n\nA runnable [quickstart notebook](examples/quickstart.ipynb) walks through the full\nworkflow — inspect, audit, repair, and verify — on a public diffusion MRI example,\ndownloaded automatically on first run. It needs only\n`pip install gradlint dipy`.\n\n## Layout\n\n```\ncrates/core   Rust core: I/O, metrics, DTI fit, coherence index, repair, BIDS batch\ncrates/cli    Internal reference binary over the same core (not the published CLI)\ncrates/py     Python bindings (PyO3 / maturin), the gradlint CLI, and report rendering\ntests/        Cross-language smoke and integration tests\n```\n\nData flows one way: the Rust core computes every metric and emits a canonical JSON\nreport, which the Python layer renders to Markdown/HTML. With `--figures`, the core also\nreturns the already-fitted tensor field needed for glyph rendering; it is not fitted a\nsecond time. No metric logic is duplicated in Python.\n\n## Validation\n\nGradLint was validated against MRtrix3 `dwigradcheck` on three real multi-shell DWI\ncohorts — HCP1200 (N = 105, 1.25 mm), a single-site acquisition (N = 57, 2 mm), and the\nOpenNeuro multiple-sclerosis dataset ds007908 (N = 28, 1.5 mm). Each tool's native flip\ncheck was run on the real acquired table of every subject (specificity), and on\nknown-good data corrupted with an injected flip across a severity ladder of Rician noise\nand direction-count reduction (sensitivity).\n\nAcross the three cohorts GradLint produced **0 false repairs on 133 real gradient\ntables**, recovered the injected convention on **100 % of 1049 graded corrupted cases**,\nand agreed with `dwigradcheck` on the recovered orientation in **100 %** of comparisons\n(antipode-aware), while running **~4–6× faster** per subject. On oblique affines, GradLint\nresolves the gradient table into the image voxel frame, so its corrected b-vectors\nreproduce the ground-truth orientation exactly.\n\nFrame-aware emission was also tested on the undegraded injected-flip case from every\nsubject in these cohorts (N = 190). Repairs were run from format-native FSL and MRtrix\ninputs, with both output formats requested. Both routes recovered the expected correction\nand passed every numerical output comparison in **190/190 subjects**. Cross-format files\nmatched MRtrix `mrinfo` exports within **1.20e-6** per component, and the two routes agreed\nwithin **1.0e-6** in both output formats. Same-format FSL output was byte-identical in\n190/190 cases. MRtrix output was numerically identical in 190/190 and byte-identical in\n190/190.\n\n## Development\n\nRequires a Rust toolchain (≥ 1.74), Python (≥ 3.10), and\n[maturin](https://www.maturin.rs/).\n\n```bash\n# Rust\ncargo test --workspace --exclude gradlint-py\ncargo clippy --workspace --exclude gradlint-py -- -D warnings\n\n# Python bindings: editable dev install into the active virtual environment\nmaturin develop --release -m crates/py/Cargo.toml\npytest\n```\n\n`maturin develop` rebuilds the native extension into the current virtual environment on\neach call — use it while iterating on the Rust core.\n\n## Citing GradLint\n\nIf you use GradLint in your research, please cite the software release. Citation metadata\nis provided in [CITATION.cff](CITATION.cff) and is resolved automatically by GitHub's\n\"Cite this repository\" button.\n\n\u003e A manuscript describing GradLint is currently in preparation. This section will be\n\u003e updated with the article citation once it is published.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcotag93%2Fgradlint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcotag93%2Fgradlint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcotag93%2Fgradlint/lists"}