Developers
QSIPrep follows the NiPreps contributing guidelines. This page covers what is specific to this repository; the Python API is documented in API.
Working on the code
The dependencies are hard to install outside the container, so development
happens against the pennlinc/qsiprep:unstable image. To test changes
without rebuilding it, mount your checkout over the installed package. The
package lives in the image’s Pixi environment; PYTHONPATH is the simplest
way to put your copy first:
docker run --rm -it \
-v $HOME/projects/qsiprep:/src/qsiprep:ro \
-e PYTHONPATH=/src/qsiprep \
-v /path/to/bids:/data:ro -v /path/to/out:/out \
pennlinc/qsiprep:unstable /data /out participant --output-resolution 2
With Apptainer:
PYTHONPATH=$HOME/projects/qsiprep apptainer run qsiprep.sif \
/path/to/bids /path/to/out participant --output-resolution 2
To rebuild the image, run docker build --target qsiprep -t qsiprep . in
the repository (--target test builds the image the integration tests
run in). The Dockerfile is a Pixi-based multi-stage build whose first line
names its base, pennlinc/qsiprep-base:<date>. That base carries the
non-Python tools (FSL, MRtrix3, DSI Studio, AFNI, TORTOISE, FreeSurfer’s
SynthStrip and SynthSeg, SynB0) and is pulled from Docker Hub, not built
locally. Python dependencies go in pyproject.toml; changing them
requires pixi lock and a rebuild of the main image. Non-Python
dependencies go in Dockerfile.base; bump its date tag in both files, and
CI builds and pushes the new base when it finds the tag missing from Docker
Hub. To try a base change before that, build it yourself with
docker build -f Dockerfile.base -t pennlinc/qsiprep-base:<date> ..
Running tests
Unit tests need no data:
pytest qsiprep/tests
Integration tests are marked and skipped by default. Each one preprocesses a
simulated dataset generated by TRXScan
(qsiprep/tests/trxscan_fixtures.py) and scores the result against the
simulator’s ground truth (qsiprep/tests/truth_scoring.py): the recovered
susceptibility field, the corrected b=0, the coregistration, eddy’s motion
estimates, the gradient-nonlinearity map. They run inside the container:
cd qsiprep/tests
python run_local_tests.py -m "trxscan_rpe_topup"
A failure names the quantity, its value and the bound it missed, and the full
scores are in truth_score.json next to the derivatives; a run that wrote
crash files or whose HTML report lists errors fails too. Each takes 10–25
minutes. CI runs every trxscan_* marker and stores the JUnit results, so a
failed run reads from the Tests tab. End-to-end runs are not how code paths
get covered: new behaviour goes with a unit test of its interface or a
workflow-construction test (build the workflow for a CLI combination and
assert its nodes), and gets a truth-scored run only when it changes what the
pipeline estimates.
Building the documentation
The documentation is built with Sphinx. It needs a working QSIPrep import and Graphviz, because the workflow graphs are drawn by building the workflows:
pip install -e '.[doc]'
make -C docs html
Read the Docs builds the same way from .readthedocs.yaml.
A few conventions:
Headings use
#with overline for page titles,*with overline for chapters, then=,-and^.Options are written in double backticks (
--output-resolution). Cite papers with:footcite:p:and a.. footbibliography::at the end of the page; the entries live inqsiprep/data/boilerplate.bib, shared with the methods boilerplate.Workflow graphs come from
.. workflow::directives, which execute their code block. The builders read their settings fromqsiprep.configand some read NIfTI headers, so the block should importqsiprep_docsfromdocs/sphinxext: it loads the parser defaults into the config, writes a small fake dataset, and providesexample_unit()forsingle,pepolar,epiandphasedifflayouts. A block that raises is reported as a build warning and renders no graph, so check the build log.Keep the pages at the level of what runs, which option changes it, and what is written. Method background belongs in the forthcoming dMRI artifacts book.
Methods boilerplate
The citation boilerplate is built from two attributes of workflow objects,
__desc__ and __postdesc__. Once the full workflow is built, the
sub-workflows are visited in topological order: each __desc__ is
appended before descending into a workflow’s children, and its
__postdesc__ after. The text is Markdown, and a citation is a Bibtex
handle with @: [@myreference] renders as a parenthetical citation.
Add new entries to qsiprep/data/boilerplate.bib.
The qsiplan contract
Scan grouping and plan compilation live in the qsiplan package.
QSIPrep pins a compatible range (qsiplan >= 0.4.1, < 0.5) and imports
its grouping, plan, report and sidecar functions from
qsiprep/workflows/base.py. The grouping and plan options on QSIPrep’s
command line (--hmc-method, --sdc-method, --sdc-anat-reference,
--separate-all-dwis, --distortion-group-merge,
--subject-anatomical-reference and the grouping values of --ignore
and --force) are owned by qsiplan.cli_spec.PLAN_OPTIONS, and
qsiprep/tests/test_plan_cli_spec.py fails if the parser drifts from that
list. The one exemption is --shoreline-model, which QSIPrep replaced
with the "model" key of --shoreline-config; qsiplan still exposes
the flag until it grows a --shoreline-config of its own. Add a grouping
option to qsiplan first, then expose it here.