Run this notebook
Reconstructing a structural connectome¶
Author: Monika Doerig
Date: 16 July 2026
License:
Note: If this notebook uses neuroimaging tools from Neurocontainers, those tools retain their original licenses. Please see Neurodesk citation guidelines for details.
✨ Use of AI ▽
This notebook was drafted with assistance from an LLM (GLM-5.2, via Neurodesk) and then revised by the author. The author reviewed the final content and takes responsibility for it.
Citation and Resources:¶
Tools included in this workflow¶
QSIRecon:
Cieslak, M., Cook, P. A., He, X., Yeh, F. C., Dhollander, T., Adebimpe, A., Aguirre, G. K., Bassett, D. S., Betzel, R. F., Bourque, J., Cabral, L. M., Davatzikos, C., Detre, J. A., Earl, E., Elliott, M. A., Fadnavis, S., Fair, D. A., Foran, W., Fotiadis, P., Garyfallidis, E., … Satterthwaite, T. D. (2021). QSIPrep: an integrative platform for preprocessing and reconstructing diffusion MRI data. Nature methods, 18(7), 775–778. Cieslak et al. (2021)
Workflows this work is based on¶
Chains on the QSIPrep output produced by the companion QSIPrep notebook.
Dataset¶
OpenNeuro:
Hannelore Aerts and Daniele Marinazzo (2018). BTC_preop. OpenNeuro Dataset ds001226
OSF Preprocessed data with QSIPrep :
Dörig, M. (2026, July 14). Diffusion MRI with QSIPrep & QSIRecon: Interactive NeurodeskEDU Examples. Retrieved from osf.io/q7v8c
Educational resources¶
Load software tools and import python libraries¶
# Load QSIRecon
import module
await module.load('qsirecon/1.1.0')
await module.list()['qsirecon/1.1.0']The base image already provides the common scientific Python packages (see the Neurodesktop Dockerfile). Only a few standard libraries are needed below.
# Import the necessary libraries
import os
import glob
import shutil
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
from bs4 import BeautifulSoup
from IPython.display import HTML, display
import base64
import subprocessFreeSurfer license¶
# Request a freesurfer license from https://surfer.nmr.mgh.harvard.edu/registration.html
# and store it in your homedirectory
# This is just an exampe - please replace with your license id:
!echo "Steffen.Bollmann@cai.uq.edu.au" > ~/.license
!echo "21029" >> ~/.license
!echo "*Cqyn12sqTCxo" >> ~/.license
!echo "FSxgcvGkNR59Y" >> ~/.licenseIntroduction¶
QSIRecon is the post-processing companion to QSIPrep (it does for diffusion MRI what XCP-D does for BOLD). Where QSIPrep stops at preprocessed data, QSIRecon builds the workflows that turn it into many of the biologically interesting derivatives you actually test hypotheses on: ODF/FOD reconstruction, model fits and parameter estimates, tractography, tractometry, regional connectivity, and tabular outputs.
Its aim is to make state-of-the-art methods from DIPY, MRtrix3, DSI Studio, PyAFQ and other packages straightforward to apply to preprocessed dMRI. Rather than one fixed pipeline, QSIRecon offers a set of curated reconstruction workflows, selected at runtime.
This notebook runs one of them: an MRtrix3 multi-shell pipeline using MSMT-CSD to estimate fibre orientation distributions, iFOD2 probabilistic tractography to generate streamlines, and SIFT2 to weight them — producing a structural connectome, which we inspect in the QSIRecon visual report.
Learning objectives¶
This notebook assumes a basic understanding of diffusion MRI and familiarity with QSIPrep’s role in preprocessing (see the companion QSIPrep notebook). By the end you will be able to:
Describe what QSIRecon does and how it relates to QSIPrep
Select and run a QSIRecon reconstruction workflow on QSIPrep derivatives
Inspect the resulting FOD, tractography and connectome outputs in the QSIRecon visual report
Data Preparation¶
QSIRecon consumes QSIPrep derivatives. We download a copy of the output from the QSIPrep notebook from OSF, so this notebook can be run on its own.
def fetch_if_missing(remote, local):
"""Fetch from OSF, downloading to a temp name so a partial file never
looks like a complete one on rerun."""
if os.path.exists(local):
print(f"Already exists, skipping download: {local}")
return
tmp = local + ".part"
subprocess.run(["osf", "-p", "q7v8c", "fetch", remote, tmp], check=True)
os.replace(tmp, local)
print(f"Downloaded: {local}")
fetch_if_missing("qsiprep/qsiprep-CON01.tar.gz", "qsiprep-CON01.tar.gz")
if os.path.exists("qsiprep-output/sub-CON01"):
print("Already extracted, skipping: qsiprep-output/sub-CON01")
else:
os.makedirs("qsiprep-output", exist_ok=True)
subprocess.run(["tar", "xzf", "qsiprep-CON01.tar.gz", "-C", "qsiprep-output"], check=True)
print("Extracted QSIPrep derivatives to qsiprep-output/")
if os.path.exists("qsiprep-CON01.tar.gz"):
os.remove("qsiprep-CON01.tar.gz")
print("Removed tarball to save disk")100%|██████████| 453M/453M [00:03<00:00, 146Mbytes/s]
Downloaded: qsiprep-CON01.tar.gz
Extracted QSIPrep derivatives to qsiprep-output/
Removed tarball to save disk
Analysis¶
We run QSIRecon on the QSIPrep derivatives using the mrtrix_multishell_msmt_noACT reconstruction spec — an MRtrix3 multishell pipeline using MSMT-CSD for FOD estimation, iFOD2 probabilistic tractography, SIFT2 streamline weighting, and tck2connectome to produce a structural connectome.
As the name suggests, this spec does not use anatomically constrained tractography (ACT). The alternative mrtrix_multishell_msmt_ACT-hsvs spec constrains tractography using a hybrid surface-volume segmentation, but that requires FreeSurfer derivatives passed via --fs-subjects-dir, which our QSIPrep 1.0.1 run from the companion notebook doesn’t produce — FreeSurfer would have to be run separately. The mrtrix_multishell_msmt_ACT-fast variant substitutes FSL FAST for the segmentation, but the QSIRecon documentation does not recommend it. So we use the non-ACT spec here.
Customising the spec¶
Reconstruction workflows are defined in YAML files, and --recon-spec accepts either a
built-in name or a path to your own file. The built-in specs are a good starting point for
customisation — so rather than invoking mrtrix_multishell_msmt_noACT by name, we download it and reduce tckgen’s select parameter from 10 million streamlines to 200,000.
This is purely to keep the notebook runnable in reasonable time and disk: streamline count drives most of the cost of tractography. A research analysis would use the full count. Note the YAML is fetched at a pinned commit (af43da9), which is the commit the QSIRecon version in this container was built from — so the spec we edit matches the built-in one.
Let’s look at the command-line arguments first and then run qsirecon:
!qsirecon --helpusage: qsirecon [-h]
[--participant-label PARTICIPANT_LABEL [PARTICIPANT_LABEL ...]]
[--session-id SESSION_ID [SESSION_ID ...]]
[-d PACKAGE=PATH [PACKAGE=PATH ...]] [--bids-filter-file FILE]
[--bids-database-dir PATH] [--nprocs NPROCS]
[--omp-nthreads OMP_NTHREADS] [--mem MEMORY_MB] [--low-mem]
[--use-plugin FILE] [--sloppy] [--boilerplate-only]
[--reports-only]
[--report-output-level {root,subject,session}] [--infant]
[--b0-threshold B0_THRESHOLD]
[--output-resolution OUTPUT_RESOLUTION]
[--fs-license-file PATH] [--recon-spec RECON_SPEC]
[--input-type {qsiprep,ukb,hcpya}] [--fs-subjects-dir PATH]
[--skip-odf-reports] [--atlases ATLAS [ATLAS ...]] [--version]
[-v] [-w WORK_DIR] [--resource-monitor] [--config-file FILE]
[--write-graph] [--stop-on-first-crash] [--notrack]
[--debug {pdb,all} [{pdb,all} ...]]
input_dir output_dir {participant}
QSIRecon v1.1.1.dev0+gaf43da9.d20250414: q-Space Image Reconstruction
Workflows
positional arguments:
input_dir The root folder of the input dataset (subject-level
folders should be found at the top level in this
folder). If the dataset is not BIDS-valid, then a
BIDS-compliant version will be created based on the
--input-type value.
output_dir The output path for the outcomes of postprocessing and
visual reports
{participant} Processing stage to be run, only "participant" in the
case of QSIRecon (for now).
options:
-h, --help show this help message and exit
Options for filtering input data:
--participant-label PARTICIPANT_LABEL [PARTICIPANT_LABEL ...]
A space delimited list of participant identifiers or a
single identifier (the sub- prefix can be removed)
(default: None)
--session-id SESSION_ID [SESSION_ID ...]
A space delimited list of session identifiers or a
single identifier (the ses- prefix can be removed)
(default: None)
-d PACKAGE=PATH [PACKAGE=PATH ...], --datasets PACKAGE=PATH [PACKAGE=PATH ...]
Search PATH(s) for derivatives or atlas datasets.
These may be provided as named folders (e.g.,
``--datasets smriprep=/path/to/smriprep``). (default:
None)
--bids-filter-file FILE
A JSON file describing custom BIDS input filters using
PyBIDS. For further details, please check out https://
fmriprep.readthedocs.io/en/latest/faq.html#how-do-I-
select-only-certain-files-to-be-input-to-fMRIPrep
(default: None)
--bids-database-dir PATH
Path to a PyBIDS database folder, for faster indexing
(especially useful for large datasets). Will be
created if not present. (default: None)
Options to handle performance:
--nprocs NPROCS, --nthreads NPROCS, --n-cpus NPROCS
Maximum number of threads across all processes
(default: None)
--omp-nthreads OMP_NTHREADS
Maximum number of threads per-process (default: None)
--mem MEMORY_MB, --mem-mb MEMORY_MB
Upper bound memory limit for QSIRecon processes
(default: None)
--low-mem Attempt to reduce memory usage (will increase disk
usage in working directory) (default: False)
--use-plugin FILE, --nipype-plugin-file FILE
Nipype plugin configuration file (default: None)
--sloppy Use low-quality tools for speed - TESTING ONLY
(default: False)
Options for performing only a subset of the workflow:
--boilerplate-only, --boilerplate
Generate boilerplate only (default: False)
--reports-only Only generate reports, don't run workflows. This will
only rerun report aggregation, not reportlet
generation for specific nodes. (default: False)
--report-output-level {root,subject,session}
Where should the html reports be written? By default
root will write them to the --output-dir. Other
options will write them into their subject or session
directory. (default: root)
Workflow configuration:
--infant configure pipelines to process infant brains (default:
False)
--b0-threshold B0_THRESHOLD
any value in the .bval file less than this will be
considered a b=0 image. Current default threshold =
100; this threshold can be lowered or increased. Note,
setting this too high can result in inaccurate
results. (default: 100)
--output-resolution OUTPUT_RESOLUTION
the isotropic voxel size in mm the data will be
resampled to after preprocessing. If set to a lower
value than the original voxel size, your data will be
upsampled using BSpline interpolation. (default: None)
Specific options for FreeSurfer preprocessing:
--fs-license-file PATH
Path to FreeSurfer license key file. Get it (for free)
by registering at
https://surfer.nmr.mgh.harvard.edu/registration.html
(default: None)
Options for recon workflows:
--recon-spec RECON_SPEC
json file specifying a reconstruction pipeline to be
run after preprocessing (default: None)
--input-type {qsiprep,ukb,hcpya}
Specify which pipeline was used to create the data
specified as the input_dir.Not necessary to specify if
the data was processed by QSIPrep. Other options
include "ukb" for data processed with the UK BioBank
minimal preprocessing pipeline and "hcpya" for the HCP
young adult minimal preprocessing pipeline. (default:
qsiprep)
--fs-subjects-dir PATH
Directory containing Freesurfer outputs to be
integrated into recon. Freesurfer must already be run.
QSIRecon will not run Freesurfer. (default: None)
--skip-odf-reports run only reconstruction, assumes preprocessing has
already completed. (default: False)
Parcellation options:
--atlases ATLAS [ATLAS ...]
Selection of atlases to apply to the data. Built-in
atlases include: AAL116, AICHA384Ext,
Brainnetome246Ext, Gordon333Ext, and the 4S atlases.
(default: None)
Other options:
--version show program's version number and exit
-v, --verbose Increases log verbosity for each occurrence, debug
level is -vvv (default: 0)
-w WORK_DIR, --work-dir WORK_DIR
Path where intermediate results should be stored
(default: /home/jovyan/workspace/books/examples/diffus
ion_imaging/work)
--resource-monitor Enable Nipype's resource monitoring to keep track of
memory and CPU usage (default: False)
--config-file FILE Use pre-generated configuration file. Values in file
will be overridden by command-line arguments.
(default: None)
--write-graph Write workflow graph. (default: False)
--stop-on-first-crash
Force stopping on first crash, even if a work
directory was specified. (default: False)
--notrack Opt-out of sending tracking information of this run to
the QSIRecon developers. This information helps to
improve QSIRecon and provides an indicator of real
world usage crucial for obtaining funding. (default:
False)
--debug {pdb,all} [{pdb,all} ...]
Debug mode(s) to enable. 'all' is alias for all
available modes. (default: None)
Now we’ll customise the spec file and reduce the streamlines to 200,000:
%%bash
set -e -u -o pipefail
curl -fsSL -o recon_spec_ci.yaml \
https://raw.githubusercontent.com/PennLINC/qsirecon/af43da9/qsirecon/data/pipelines/mrtrix_multishell_msmt_noACT.yaml
sed -i -E 's/^([[:space:]]*select:).*/\1 200000/' recon_spec_ci.yaml
grep -q 'select: 200000' recon_spec_ci.yaml || { echo "sed did not match — spec unchanged"; exit 1; }
cat recon_spec_ci.yamlanatomical: []
name: mrtrix_multishell_msmt_noACT
nodes:
- action: csd
input: qsirecon
name: msmt_csd
parameters:
fod:
algorithm: msmt_csd
max_sh:
- 8
- 8
- 8
mtnormalize: true
response:
algorithm: dhollander
qsirecon_suffix: MRtrix3_act-None
software: MRTrix3
- action: tractography
input: msmt_csd
name: track_ifod2
parameters:
sift2: {}
tckgen:
algorithm: iFOD2
max_length: 250
min_length: 30
power: 0.33
quiet: true
select: 200000
use_5tt: false
use_sift2: true
qsirecon_suffix: MRtrix3_act-None
software: MRTrix3
- action: connectivity
input: track_ifod2
name: mrtrix_conn
parameters:
tck2connectome:
- measure: sift_invnodevol_radius2_count
scale_invnodevol: true
search_radius: 2
stat_edge: sum
symmetric: true
use_sift_weights: true
zero_diagonal: false
- length_scale: length
measure: radius2_meanlength
scale_invnodevol: false
search_radius: 2
stat_edge: mean
symmetric: true
use_sift_weights: false
zero_diagonal: false
- measure: radius2_count
scale_invnodevol: false
search_radius: 2
stat_edge: sum
symmetric: true
use_sift_weights: false
zero_diagonal: false
- measure: sift_radius2_count
scale_invnodevol: false
search_radius: 2
stat_edge: sum
symmetric: true
use_sift_weights: true
zero_diagonal: false
qsirecon_suffix: MRtrix3_act-None
software: MRTrix3
space: T1w
%%bash
set -e -u -o pipefail
# ensure subjects dir exists at startup
export SUBJECTS_DIR="$HOME/qsiprep-freesurfer-dir"
export APPTAINERENV_SUBJECTS_DIR=$SUBJECTS_DIR
export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1
mkdir -p "$SUBJECTS_DIR"
N_CPUS=$(nproc); N_CPUS=$(( N_CPUS > 12 ? 12 : N_CPUS ))
MEM_MB=$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo)
QSIRECON_MEM=$(( MEM_MB * 75 / 100 ))
OMP_THREADS=$(( N_CPUS >= 8 ? 4 : 2 ))
qsirecon qsiprep-output qsirecon-output participant \
--participant-label CON01 \
--recon-spec recon_spec_ci.yaml \
--fs-license-file ~/.license \
--atlases AAL116 \
-w qsirecon-work \
--nthreads "${N_CPUS}" --omp-nthreads "${OMP_THREADS}" --mem-mb "${QSIRECON_MEM}" \
2>&1 | tee qsirecon.log#remove the working dir
shutil.rmtree("qsirecon-work", ignore_errors=True)QSIRecon outputs¶
The reconstruction results are nested under derivatives/qsirecon-MRtrix3_act-None/ rather than sitting at the top level. The directory name comes from the qsirecon_suffix declared in the recon spec — a single run can emit several such datasets, so each is written as its own self-contained BIDS derivatives dataset, with act-None recording that we used the non-ACT variant.
Inside, sub-CON01/ses-preop/dwi/ holds the reconstruction proper: per-tissue FODs from MSMT-CSD (model-msmtcsd_param-fod_label-{WM,GM,CSF}, with the matching response functions as .txt), the mtnormalise outputs, the iFOD2 streamlines (model-ifod2_streamlines.tck.gz), the SIFT2 weights and proportionality coefficient (model-sift2_streamlineweights.csv, model-sift2_mu.txt), and the connectome itself in connectivity.mat. The figures/ directory holds the panels shown in the visual report, and sub-CON01_ses-preop.html is that report.
At the top level, atlases/ contains the AAL116 atlas as passed to --atlases, while the top-level sub-CON01/ses-preop/dwi/ holds it resampled into the subject’s ACPC space — the parcellation actually used to build the connectome.
! tree qsirecon-output/qsirecon-output/
├── atlases
│ ├── atlas-AAL116
│ │ ├── atlas-AAL116_dseg.tsv
│ │ ├── atlas-AAL116_space-MNI152NLin2009cAsym_res-01_dseg.json
│ │ └── atlas-AAL116_space-MNI152NLin2009cAsym_res-01_dseg.nii.gz
│ └── dataset_description.json
├── dataset_description.json
├── derivatives
│ └── qsirecon-MRtrix3_act-None
│ ├── dataset_description.json
│ ├── logs
│ │ ├── CITATION.bib
│ │ ├── CITATION.html
│ │ ├── CITATION.md
│ │ └── CITATION.tex
│ ├── sub-CON01
│ │ └── ses-preop
│ │ ├── dwi
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_connectivity.mat
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_exemplarbundles.zip
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-ifod2_streamlines.tck.gz
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-msmtcsd_param-fod_label-CSF_dwimap.mif.gz
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-msmtcsd_param-fod_label-CSF_dwimap.txt
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-msmtcsd_param-fod_label-GM_dwimap.mif.gz
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-msmtcsd_param-fod_label-GM_dwimap.txt
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-msmtcsd_param-fod_label-WM_dwimap.mif.gz
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-msmtcsd_param-fod_label-WM_dwimap.txt
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-mtnorm_param-inliermask_dwimap.nii.gz
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-mtnorm_param-norm_dwimap.nii.gz
│ │ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_model-sift2_mu.txt
│ │ │ └── sub-CON01_ses-preop_acq-AP_space-ACPC_model-sift2_streamlineweights.csv
│ │ └── figures
│ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_desc-MRtrix3Connectivity_matrices.svg
│ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_desc-wmFOD_odfs.png
│ │ ├── sub-CON01_ses-preop_acq-AP_space-ACPC_desc-wmFOD_peaks.png
│ │ ├── sub-CON01_ses-preop_desc-about_T1w.html
│ │ └── sub-CON01_ses-preop_desc-summary_T1w.html
│ └── sub-CON01_ses-preop.html
├── logs
│ ├── CITATION.bib
│ ├── CITATION.html
│ ├── CITATION.md
│ └── CITATION.tex
└── sub-CON01
├── log
│ └── 20261005-144407_f9f73044-ed5b-4b00-a1a5-c7c4fad64d81
│ ├── qsirecon.toml
│ └── recon_spec.yaml
└── ses-preop
└── dwi
├── sub-CON01_ses-preop_acq-AP_space-ACPC_seg-AAL116_dseg.mif.gz
├── sub-CON01_ses-preop_acq-AP_space-ACPC_seg-AAL116_dseg.nii.gz
└── sub-CON01_ses-preop_acq-AP_space-ACPC_seg-AAL116_dseg.txt
16 directories, 38 files
Results¶
We display the full QSIRecon visual report. It contains FOD peak/ODF visualisations and the connectome matrices figure, alongside run provenance and summary sections. The report references its figures as separate files, so we inline them (PNGs as base64, SVGs embedded directly) to make it render in the notebook.
# Display the QSIRecon HTML report with all images inlined.
# PNGs are base64-encoded; SVGs are embedded directly into the DOM.
def display_report(report_path):
report_dir = os.path.dirname(report_path)
with open(report_path) as f:
soup = BeautifulSoup(f, 'html.parser')
# Inline <img src=...> tags: PNG as base64, SVG embedded directly
for img in soup.find_all('img'):
src = img.get('src', '')
if not src:
continue
img_path = os.path.join(report_dir, src.lstrip('./'))
if os.path.exists(img_path):
if src.endswith('.svg'):
with open(img_path) as f:
svg_soup = BeautifulSoup(f.read(), 'html.parser')
svg_tag = svg_soup.find('svg')
if svg_tag:
img.replace_with(svg_tag)
elif src.endswith('.png'):
with open(img_path, 'rb') as f:
data = base64.b64encode(f.read()).decode()
img['src'] = f'data:image/png;base64,{data}'
# The figures are inlined above, so drop the report's "Get figure file: <link>" lines:
# they point into qsirecon-output/, which is not published with this page, and would 404.
for small in soup.find_all('small'):
if small.get_text().startswith('Get figure file'):
small.decompose()
# Remove nav/button clutter
for tag in soup(['button', 'nav', 'head']):
tag.decompose()
# QSIRecon's methods text links a docs page that no longer exists; point it at the
# workflow descriptions in the docs for the QSIRecon version loaded above (1.1.0).
html = str(soup).replace('https://qsirecon.readthedocs.io/en/latest/workflows.html',
'https://qsirecon.readthedocs.io/en/1.1.0/builtin_workflows.html')
display(HTML(html))display_report('./qsirecon-output/derivatives/qsirecon-MRtrix3_act-None/sub-CON01_ses-preop.html')Dependencies in Jupyter/Python¶
Using the package watermark to document system environment and software versions used in this notebook, alongside the Neurodesktop version extracted from the
JUPYTER_IMAGEorNEURODESKTOP_VERSIONenvironment variables.
import os
%load_ext watermark
%watermark
%watermark --iversions
neurodesktop_version = (
os.environ.get('JUPYTER_IMAGE', '').split(':')[-1] or
os.environ.get('NEURODESKTOP_VERSION', 'unknown')
)
print(f"Neurodesktop version: {neurodesktop_version}")Last updated: 2026-10-05T15:12:29.184103+00:00
Python implementation: CPython
Python version : 3.13.15
IPython version : 9.17.1
Compiler : GCC 15.3.0
OS : Linux
Release : 6.8.0-111-generic
Machine : x86_64
Processor : x86_64
CPU cores : 16
Architecture: 64bit
IPython : 9.17.1
bs4 : 4.15.0
matplotlib: 3.11.2
numpy : 2.5.3
Neurodesktop version: 2026-09-28
- Cieslak, M., Cook, P. A., He, X., Yeh, F.-C., Dhollander, T., Adebimpe, A., Aguirre, G. K., Bassett, D. S., Betzel, R. F., Bourque, J., Cabral, L. M., Davatzikos, C., Detre, J. A., Earl, E., Elliott, M. A., Fadnavis, S., Fair, D. A., Foran, W., Fotiadis, P., … Satterthwaite, T. D. (2021). QSIPrep: an integrative platform for preprocessing and reconstructing diffusion MRI data. Nature Methods, 18(7), 775–778. 10.1038/s41592-021-01185-5