Imported from PennLINC/qsirecon (
AGENTS.md). Install upstream withnpx skills add PennLINC/qsirecon. Copyright stays with the author.
AGENTS.md -- QSIRecon
This file provides instructions for AI coding agents and human maintainers working on QSIRecon, a BIDS App for reconstructing and postprocessing q-space (diffusion) MRI images.
Shared Instructions (All PennLINC BIDS Apps)
The following conventions apply equally to qsiprep, qsirecon, xcp_d, and aslprep. All four are PennLINC BIDS Apps built on the NiPreps stack.
Ecosystem Context
- These projects belong to the NiPreps ecosystem and follow its community guidelines.
- Core dependencies include nipype (workflow engine), niworkflows (reusable workflow components), nireports (visual reports), pybids (BIDS dataset querying), and nibabel (neuroimaging I/O).
- All four apps are containerized via Docker and distributed on Docker Hub under the
pennlinc/namespace. - Contributions follow the NiPreps contributing guidelines.
Architecture Overview
Every PennLINC BIDS App follows this execution flow:
CLI (parser.py / run.py)
-> config singleton (config.py, serialized as ToML)
-> workflow graph construction (workflows/*.py)
-> Nipype interfaces (interfaces/*.py)
-> BIDS-compliant derivative outputs
- CLI (
<pkg>/cli/):parser.pydefines argparse arguments;run.pyis the entry point;workflow.pybuilds the execution graph;version.pyhandles--version. - Config (
<pkg>/config.py): A singleton module with class-based sections (environment,execution,workflow,nipype,seeds). Config is serialized to ToML and passed across processes via the filesystem. Access settings asconfig.section.setting. - Workflows (
<pkg>/workflows/): Built usingnipype.pipeline.engine(pe.Workflow,pe.Node,pe.MapNode). UseLiterateWorkflowfromniworkflows.engine.workflowsfor auto-documentation. Every workflow factory function must be namedinit_<descriptive_name>_wf. - Interfaces (
<pkg>/interfaces/): Custom Nipype interfaces wrapping external tools or Python logic. Follow standard Nipype patterns: define_InputSpec/_OutputSpecwithBaseInterfaceInputSpec/TraitedSpec, implement_run_interface(). - Utilities (
<pkg>/utils/): Shared helper functions. BIDS-specific helpers live inutils/bids.py. - Reports (
<pkg>/reports/): HTML report generation using nireports. - Data (
<pkg>/data/): Static package data (config files, templates, atlases). Accessed viaimportlib.resourcesor theacrespackage. - Tests (
<pkg>/tests/): Pytest-based. Unit tests run without external data. Integration tests are gated behind pytest markers and are skipped by default.
Workflow Authoring Rules
- Every workflow factory function must be named
init_<name>_wfand return aWorkflowobject. - Use
LiterateWorkflow(fromniworkflows.engine.workflows) to enable automatic workflow graph documentation. - Define
inputnodeandoutputnodeasniu.IdentityInterfacenodes to declare the workflow's external API. - Connect nodes using
workflow.connect([(source, dest, [('out_field', 'in_field')])])syntax. - Add
# fmt:skipafter multi-lineworkflow.connect()calls to prevent the formatter from reformatting them. - Include a docstring with
Workflow Graphand.. workflow::Sphinx directive for auto-generated documentation. - Use
configmodule values (not function parameters) for global settings inside workflow builders.
Interface Conventions
- Input/output specs use Nipype traits (
File,traits.Bool,traits.Int, etc.). mandatory = Truefor required inputs; providedesc=for all traits.- Implement
_run_interface(self, runtime)-- neverrun(). - Return
runtimefrom_run_interface. - Set outputs via
self._results['field'] = value.
Config Module Usage
from <pkg> import config
# Read a setting
work_dir = config.execution.work_dir
# Serialize to disk
config.to_filename(path)
# Load from disk (in a subprocess)
config.load(path)
The config module is the single source of truth for runtime parameters. Never pass global settings as function arguments when they are available via config.
Testing Conventions
- Unit tests: Files named
test_*.pyin<pkg>/tests/. Must not require external neuroimaging data or network access. - Integration tests: Decorated with
@pytest.mark.<marker_name>. Excluded by default viaaddoptsinpyproject.toml. Require Docker or pre-downloaded test datasets. - Fixtures: Defined in
conftest.py. Common fixtures includedata_dir,working_dir,output_dir, anddatasets. - Coverage: Configured in
pyproject.tomlunder[tool.coverage.run]and[tool.coverage.report].
Documentation
- Built with Sphinx using
sphinx_rtd_theme. - Source files in
docs/. - Workflow graphs are auto-rendered via
.. workflow::directives that callinit_*_wffunctions. - API docs generated via
sphinxcontrib-apidoc. - Bibliography managed with
sphinxcontrib-bibtexandboilerplate.bib.
Docker
- Each app has a base image with runtime dependencies (
Dockerfile.base) and a mainDockerfilethat installs the Python environment and the app itself. - All four apps use Pixi-based multi-stage Docker builds:
Dockerfile.baseowns non-Python/non-conda runtime dependencies, whileDockerfileuses pixi to createbuild,test, and production targets frompixi.lock. - Base image naming is uniform across the four repos:
pennlinc/<pkg>-base:<YYYYMMDD>, pinned viaARG BASE_IMAGEon the first line ofDockerfile. - Entrypoint is the CLI binary in the pixi environment (e.g.,
/app/.pixi/envs/<env>/bin/<pkg>). - Labels follow the
org.label-schemaconvention; thetesttarget additionally carriesorg.opencontainers.image.revision, which CI uses to refuse running tests against a stale image. - The base image is rebuilt only when its
BASE_IMAGEtag is absent from Docker Hub. EditingDockerfile.basewithout bumping the date tag does not trigger a rebuild. - qsirecon has an extra
amico_cachestage that pre-populates$HOME/.dipyviascripts/set_up_amico.py; both thetestandqsirecontargets copy from it.
CircleCI
- All four repos use CircleCI dynamic configuration.
.circleci/config.ymlis asetup: trueworkflow that runs thepath-filteringorb and hands off to.circleci/continue_config.yml, which holds the real pipeline. A docs-only push leaveshas_code_changesfalse and skips the expensive matrix. continue_config.ymldefines two workflows:run_tests(branch pushes, gated onhas_code_changes) anddeploy(tag pushes only, which skips the matrix since the tagged commit already passed onmain).image_prepbuilds the base image (only if its tag is missing), thetestimage, and — onmainand tags only — the production image, then pushes to Docker Hub. Integration jobs consume the test image from a local registry seeded out of the build cache.- The build cache uses two keys: a workflow-scoped key that downstream jobs read from (unique per run, so always written) and a shared
-deps-key onDockerfile+pixi.lockchecksums that lets a later run skip the build entirely. Downstream jobs must not read the deps key — CircleCI caches are immutable, so it can point at an older run's image. - The integration-test data cache is keyed on
qsirecon/tests/integration_test_data.py, which is where the dataset URLs live. qsirecon keeps URLs in that (stdlib-only) module rather than in a.circleci/data_versions.txtlike the other three, so CI and local test runs share one source of truth. - Docker Hub credentials are not named consistently yet: aslprep and xcp_d use
$DOCKERHUB_USERNAME/$DOCKERHUB_TOKEN, qsiprep and qsirecon use$DOCKER_USER/$DOCKER_PASS. Logins are guarded by a[[ -n ... ]]check, so an unset variable skips login silently rather than failing.
Release Process
- Versions are derived from git tags via
hatch-vcs(VCS-based versioning). - GitHub Releases use auto-generated changelogs configured in
.github/release.yml. - Release categories: Breaking Changes, New Features, Deprecations, Bug Fixes, Other.
- Docker images are built and pushed via CI on tagged releases.
Code Style
- Formatter:
ruff format(target: all four repos). - Linter:
ruff checkwith an extended rule set (F, E, W, I, UP, YTT, S, BLE, B, A, C4, DTZ, T10, EXE, FA, ISC, ICN, PT, Q). - Import sorting: Handled by ruff's
Irule (isort-compatible). - Pre-commit: Uses
ruff-pre-commithooks for both linting and formatting. - Black is disabled:
[tool.black] exclude = ".*"in repos that have migrated to ruff.
BIDS Compliance
- All outputs must conform to the BIDS Derivatives specification.
- Use
pybids.BIDSLayoutfor querying input datasets. - Use
DerivativesDataSink(from the project's interfaces or niworkflows) for writing BIDS-compliant output files. - Entity names, suffixes, and extensions must match the BIDS specification.
QSIRecon-Specific Instructions
Project Overview
QSIRecon is a BIDS App for reconstructing and postprocessing diffusion MRI data that has been preprocessed by QSIPrep. It supports:
- Diffusion model fitting (DTI, DKI, MAPMRI, GQI, NODDI via AMICO)
- Tractography and bundle segmentation (MRtrix3, DSI Studio, TORTOISE, pyAFQ)
- Scalar mapping to atlases
- Connectivity matrix generation
- ODF conversion between formats
Repository Details
| Item | Value |
|---|---|
| Package name | qsirecon |
| Default branch | main |
| Entry point | qsirecon.cli.run:main |
| Python requirement | >=3.10 |
| Build backend | hatchling + hatch-vcs + cython + numpy |
| Linter | flake8 + black + isort (migration to ruff pending) |
| Pre-commit | None (to be added) |
| Tox | Yes |
| Docker base | pennlinc/qsirecon-base:<YYYYMMDD> |
| Dockerfile | Pixi-based multi-stage (build, templates, amico_cache, test, qsirecon) |
Key Directories
qsirecon/workflows/recon/: Reconstruction workflow modules organized by tool (amico, dipy, dsi_studio, mrtrix, pyafq, tortoise)qsirecon/interfaces/: Nipype interfaces for AMICO, DIPY, DSI Studio, MRtrix3, pyAFQ, TORTOISE, and scalar mappingqsirecon/data/: YAML-based reconstruction workflow specifications (44.yamlfiles defining reconstruction pipelines)qsirecon/cli/convertODFs.py: CLI tool for converting between ODF formats (MIF <-> FIB)qsirecon/cli/group_report.py: Aggregates individual reports into group-level reports
IMPORTANT: Current Linting State
QSIRecon has not yet migrated to ruff. It currently uses:
blackfor formatting (line-length 99, target Python 3.8, double quotes)isortfor import sorting (profile "black")flake8for linting (configured in[tool.flake8]inpyproject.toml)
The CI lint workflow (.github/workflows/lint.yml) still runs flake8, not ruff.
When writing new code for qsirecon:
- Use double quotes (the current black default) until the ruff migration is complete.
- Follow
isort"black" profile for import ordering. - The migration to ruff + single quotes is a planned maintenance task (see roadmap below).
Reconstruction Workflow Specifications
QSIRecon uses YAML files in qsirecon/data/ to define reconstruction pipelines. These YAML files specify:
- Which reconstruction nodes to run
- Node parameters
- How nodes connect to each other
When adding new reconstruction workflows, create a new .yaml file following existing patterns and register it in the workflow builder.
External Tool Dependencies
QSIRecon wraps several external neuroimaging tools:
- pyAFQ (
== 2.0): Automated fiber quantification - dmri-amico (
== 2.0.3): AMICO implementation of NODDI - Ingress2QSIRecon (
== 0.2.3): Data ingression from other pipelines - DSI Studio, MRtrix3, TORTOISE: Called via subprocess interfaces
Cross-Project Development Roadmap
This roadmap covers harmonization work across all four PennLINC BIDS Apps (qsiprep, qsirecon, xcp_d, aslprep) to reduce maintenance burden.
Phase 1: Bring qsirecon to parity
- Migrate qsirecon from flake8+black+isort to ruff -- copy the
[tool.ruff]config from xcp_d'spyproject.tomland remove[tool.black],[tool.isort],[tool.flake8]sections. - Add
.pre-commit-config.yamlto qsirecon -- identical to the config used by qsiprep, xcp_d, and aslprep. - Add
tox.inito qsirecon -- copy from qsiprep or xcp_d (they are identical). - Add
.github/dependabot.ymlto qsirecon. - Reformat qsirecon codebase -- run
ruff formatto switch from double quotes to single quotes.
Phase 2: Standardize across all four repos
- Rename qsiprep default branch from
mastertomainand update.github/workflows/lint.yml. - Rename aslprep test extras from
testtotestsfor consistency with the other three repos. - Converge on version management -- recommend the simpler
_version.pydirect-import pattern (used by qsiprep/qsirecon). Migrate xcp_d and aslprep away from__about__.py. - Pin the same ruff version in all four repos' dev dependencies and
.pre-commit-config.yaml. - Harmonize ruff ignore lists -- adopt xcp_d's minimal set (
S105,S311,S603) as the target; fix suppressed rules in qsiprep and aslprep incrementally.
Phase 3: Shared infrastructure
- Extract a reusable GitHub Actions workflow for lint + codespell + build checks, hosted in a shared repo (e.g.,
PennLINC/.github). Standardize Dockerfile patterns-- done: all four repos now use pixi-based multi-stage builds withpennlinc/<pkg>-base:<YYYYMMDD>base images.- Create a shared
pennlinc-stylepackage or cookiecutter template providingpyproject.tomllint/test config,.pre-commit-config.yaml,tox.ini, and CI workflows. - Evaluate
nipreps-versionscalver -- theraw-options = { version_scheme = "nipreps-calver" }line is commented out in all four repos. Decide whether to adopt it.