Skip to main content

Part A, Route 1: QSIPrep

Overview​

Part A has two routes to the same result. This is Route 1: the manual Steps 1–8 are replaced by a single run of QSIPrep, a containerized BIDS app that does for diffusion data what fMRIPrep does for functional data. It denoises, removes Gibbs ringing, runs TOPUP and eddy, corrects bias fields, skull-strips the T1, and aligns the diffusion data to the anatomical image, with a QC report for every participant. It uses most of the same underlying tools this tutorial walks through, with two differences the table below calls out, so the result is the same kind of data produced with fewer decisions on your part.

Part B does not change. QSIPrep produces corrected diffusion data; it does not produce FA maps or FODs, so tensor fitting and the CSD chain still run afterwards. This page is the bridge: what QSIPrep gives you, and how it maps onto the required outputs.

Whether to use QSIPrep or the manual steps is a real choice. QSIPrep is reproducible, container-pinned, and the right default for a new study or a multi-site one. The manual steps expose every parameter and are the right way to learn what the corrections do, or to handle an acquisition QSIPrep does not support. Both routes converge at Step 9.

What QSIPrep Does, Step for Step​

This is the manual route's eight steps against what one QSIPrep run does. Where QSIPrep uses the same tool with the same behaviour, the row says so; where it does not, the row says what is different. Flag names are from the current QSIPrep documentation; run qsiprep --help for your installed version.

Manual stepIn QSIPrepDefaultSame tool?
1. DICOM to NIfTINot done. QSIPrep reads BIDS only, so you run dcm2niix first (below).—Yes, you run it yourself
2. Skull stripping (ANTs)Done, on the T1OnNo — SynthStrip, not ANTs
3. B0 concatenationInternal. Reverse-phase b=0s are found in fmap/ via IntendedFor and paired automatically.On when fieldmaps existn/a
4. TOPUPDoneOn when fieldmaps existYes, FSL topup
5. Mean B0Done; written as *_dwiref.nii.gzOnn/a
6. Brain mask on DWIDone; written as *_desc-brain_mask.nii.gzOnQSIPrep's own masking, not BET
7a. DenoisingDone--denoise-method dwidenoise (on)Yes, MRtrix3 dwidenoise
7b. Gibbs removalDone only if requested--unringing-method none (off)Yes, MRtrix3 mrdegibbs, when enabled
8. Eddy + motionDone--hmc-method eddy (on); default config has repol: trueYes, FSL eddy with outlier replacement, as in Step 8
Shell extraction (optional, Part C)Not a QSIPrep option. Handled in Part 1 below: strip the shell from the BIDS DWI before QSIPrep runs.—Yes, MRtrix3 dwiextract, run before rather than after
—N4 bias-field correction on the DWI. The manual route does not do this.--dwi-biascorrect n4 (on)Extra step
—DWI resampled into the T1 (ACPC) grid. The manual route keeps native DWI space.On, at --output-resolutionThis is why Step 10 is skipped

Two consequences to be explicit about. Gibbs removal is off unless you pass --unringing-method mrdegibbs; a bare qsiprep call does less than the manual route at Step 7. And N4 bias correction is on unless you pass --dwi-biascorrect none; a bare call does more than the manual route. Neither is wrong, but if you want the closest match to Steps 1–8, set both.

Prerequisites​

RequirementNotes
Data in BIDS layoutQSIPrep reads BIDS and nothing else. The conversion below covers it; Step 1 and the BIDS page have the general case.
Fieldmaps in BIDSReverse phase-encode b=0 pairs go in fmap/ with IntendedFor set in their JSON sidecars. Without them QSIPrep skips susceptibility correction silently.
Docker or Singularity/ApptainerQSIPrep runs only as a container.
FreeSurfer licence fileRequired by the container even though FreeSurfer is not run. Free from the FreeSurfer site.

Script​

Two parts: convert to BIDS (this is Step 1, done with the same dcm2niix the manual route uses), then one QSIPrep call.

Part 1: DICOM to BIDS​

#!/bin/bash
# to_bids.sh — convert one participant's DICOMs into a minimal BIDS tree
# Adjust the three dicom_* paths to match how your scanner exports series.

subj="sub-001"
raw="/path/to/dicoms/$subj"
bids="/path/to/bids"

mkdir -p "$bids/$subj/anat" "$bids/$subj/dwi" "$bids/$subj/fmap"

# T1w
dcm2niix -b y -z y -f "${subj}_T1w" -o "$bids/$subj/anat" "$raw/t1_mprage"

# Main diffusion series (produces .nii.gz, .bval, .bvec, .json)
dcm2niix -b y -z y -f "${subj}_dir-AP_dwi" -o "$bids/$subj/dwi" "$raw/dwi_ap"

# Reverse phase-encode b=0 for TOPUP
dcm2niix -b y -z y -f "${subj}_dir-PA_epi" -o "$bids/$subj/fmap" "$raw/dwi_pa_b0"

# Optional: drop a very low shell (e.g. b=250) BEFORE QSIPrep sees it.
# Set keep_shells to the b-values you want; leave it empty to keep everything.
keep_shells="0,1000,2000,3000"
if [ -n "$keep_shells" ]; then
d="$bids/$subj/dwi/${subj}_dir-AP_dwi"
dwiextract "$d.nii.gz" -fslgrad "$d.bvec" "$d.bval" -shells "$keep_shells" \
"$d.tmp.nii.gz" -export_grad_fsl "$d.tmp.bvec" "$d.tmp.bval" -quiet
mv -f "$d.tmp.nii.gz" "$d.nii.gz"; mv -f "$d.tmp.bvec" "$d.bvec"; mv -f "$d.tmp.bval" "$d.bval"
fi

# Tell QSIPrep which DWI the fieldmap corrects
python3 - <<EOF
import json
p = "$bids/$subj/fmap/${subj}_dir-PA_epi.json"
j = json.load(open(p))
j["IntendedFor"] = ["dwi/${subj}_dir-AP_dwi.nii.gz"]
json.dump(j, open(p, "w"), indent=2)
EOF

# dataset_description.json is required at the BIDS root
[ -f "$bids/dataset_description.json" ] || cat > "$bids/dataset_description.json" <<EOF
{"Name": "My diffusion study", "BIDSVersion": "1.8.0"}
EOF

Removing volumes here is safe: the JSON sidecar carries no per-volume fields, so it stays valid after the shell is dropped. Check it has PhaseEncodingDirection and TotalReadoutTime; without them QSIPrep cannot run TOPUP. If your reverse-phase scan is a full DWI rather than b=0 only, put it in dwi/ as dir-PA_dwi instead and QSIPrep will use both.

Part 2: QSIPrep​

#!/bin/bash
# run_qsiprep.sh — Steps 2 through 8 in one container run

subj="sub-001"
bids="/path/to/bids"
out="/path/to/qsiprep_out"
work="/path/to/scratch"
license="/path/to/license.txt"

qsiprep "$bids" "$out" participant \
--participant-label "${subj#sub-}" \
--output-resolution 2.0 \
--unringing-method mrdegibbs \
--dwi-biascorrect none \
--fs-license-file "$license" \
--nthreads 8 \
--work-dir "$work"
FlagWhy it is set
--output-resolutionRequired. Voxel size in mm for the preprocessed DWI, which QSIPrep resamples into the T1 grid. Match your acquisition unless you have a reason not to.
--unringing-method mrdegibbsGibbs removal is off by default. This turns it on and matches Step 7.
--dwi-biascorrect noneN4 bias correction is on by default and the manual route does not do it. Drop this flag to keep QSIPrep's default; keep it for parity with Steps 1–8, or if your data is prescan-normalized.
--fs-license-filePath to license.txt.
--nthreadsCPU threads.
--work-dirScratch. Large; clean up after a successful run.

Denoising (--denoise-method dwidenoise), motion and eddy correction (--hmc-method eddy, with outlier replacement in the default config), and TOPUP (when fieldmaps are present) are on without flags.

With Singularity:

singularity run --cleanenv \
-B "$bids":/data:ro \
-B "$out":/out \
-B "$work":/work \
-B "$license":/license.txt:ro \
qsiprep.sif \
/data /out participant \
--participant-label "${subj#sub-}" \
--output-resolution 2.0 \
--unringing-method mrdegibbs \
--dwi-biascorrect none \
--fs-license-file /license.txt \
--nthreads 8 \
--work-dir /work

Output​

QSIPrep writes BIDS derivatives. The files that matter downstream:

out/qsiprep/sub-001/
├── anat/
│ ├── sub-001_space-ACPC_desc-preproc_T1w.nii.gz
│ └── sub-001_space-ACPC_desc-brain_mask.nii.gz
├── dwi/
│ ├── sub-001_space-ACPC_desc-preproc_dwi.nii.gz
│ ├── sub-001_space-ACPC_desc-preproc_dwi.bval
│ ├── sub-001_space-ACPC_desc-preproc_dwi.bvec
│ ├── sub-001_space-ACPC_desc-brain_mask.nii.gz
│ └── sub-001_space-ACPC_dwiref.nii.gz
└── figures/

QSIPrep versions before 1.0 wrote space-T1w in place of space-ACPC. The files are otherwise the same.

The preprocessed DWI is already in the T1-aligned grid. That is the one structural difference from the manual route, and it has a consequence: T1 and diffusion share a space, so the FLIRT matrix from Step 10 is not needed. The header carries the alignment.

Mapping to the Required Outputs​

Required fileQSIPrep sourceNotes
data.nii.gzdwi/*_desc-preproc_dwi.nii.gz
bvalsdwi/*_desc-preproc_dwi.bval
bvecsdwi/*_desc-preproc_dwi.bvecAlready rotated for eddy and reoriented to the output grid.
nodif_brain_mask.nii.gzdwi/*_desc-brain_mask.nii.gz
mean_b0.nii.gzdwi/*_dwiref.nii.gzQSIPrep's b=0 reference image.
<subj>_T1w_brain.nii.gzanat/*_desc-preproc_T1w.nii.gz masked by anat/*_desc-brain_mask.nii.gzSee below.
str2diff.matnot neededSame grid; use header alignment.
fa.nii.gznot producedRun Step 9 on the preprocessed DWI.
wm_fod_norm.mifnot producedRun Steps 11–12.

Linking the outputs into the required layout:

#!/bin/bash
# qsiprep_to_layout.sh — lay out QSIPrep derivatives in the required structure

qsiprep_dir="/path/to/out/qsiprep"
project="/path/to/project"

for d in "$qsiprep_dir"/sub-*; do
subj=$(basename "$d")
dwi="$d/dwi"; anat="$d/anat"
mkdir -p "$project/dwi/$subj" "$project/anat/$subj"

# QSIPrep < 1.0 used space-T1w; pick whichever exists
sp=ACPC; [ -e "$dwi/${subj}_space-T1w_desc-preproc_dwi.nii.gz" ] && sp=T1w

ln -sf "$dwi/${subj}_space-${sp}_desc-preproc_dwi.nii.gz" "$project/dwi/$subj/data.nii.gz"
ln -sf "$dwi/${subj}_space-${sp}_desc-preproc_dwi.bval" "$project/dwi/$subj/bvals"
ln -sf "$dwi/${subj}_space-${sp}_desc-preproc_dwi.bvec" "$project/dwi/$subj/bvecs"
ln -sf "$dwi/${subj}_space-${sp}_desc-brain_mask.nii.gz" "$project/dwi/$subj/nodif_brain_mask.nii.gz"
ln -sf "$dwi/${subj}_space-${sp}_dwiref.nii.gz" "$project/dwi/$subj/mean_b0.nii.gz"

# Skull-stripped T1: QSIPrep ships the preprocessed T1 and its mask separately
fslmaths "$anat/${subj}_space-${sp}_desc-preproc_T1w.nii.gz" \
-mas "$anat/${subj}_space-${sp}_desc-brain_mask.nii.gz" \
"$project/anat/$subj/${subj}_T1w_brain.nii.gz"

echo "$subj linked"
done

Then run Step 9 for FA and Steps 11–12 for FODs, and set need_xfm=0 in the output check script.

Optional Steps on This Route​

The manual route's optional steps still apply after QSIPrep; they just read the linked files instead of eddy output.

Shell extraction​

QSIPrep has no option to drop a shell, so the place to do it is Part 1, before QSIPrep runs; the script above has the block. That way denoising, eddy, and everything else operate on exactly the shells you intend, and the one QSIPrep command needs no follow-up. The reasons to drop a very low shell such as b=250 are on the Shell Extraction page and are the same on both routes.

Do not try to achieve this with --b0-threshold. Raising it above 250 makes QSIPrep treat b=250 volumes as b=0 references, which contaminates the b=0 average and eddy's reference image with diffusion-weighted signal. The threshold exists to absorb scanner jitter around b=0 (values like 5 or 50), not to remove a shell.

Tensor fitting is the one case that still extracts after QSIPrep, because Step 9 wants b=0 and b=1000 only while Steps 11–12 want every shell:

dwiextract "$project/dwi/$subj/data.nii.gz" \
-fslgrad "$project/dwi/$subj/bvecs" "$project/dwi/$subj/bvals" \
-shells 0,1000 \
"$project/dwi/$subj/data_1000.nii.gz" \
-export_grad_fsl "$project/dwi/$subj/bvecs_1000" "$project/dwi/$subj/bvals_1000"

This is identical to what Route 2 does before Step 9.

ICV​

fslstats "$project/anat/$subj/${subj}_T1w_brain.nii.gz" -V on the linked brain image, exactly as in ICV Calculation.

BedpostX​

The FSL counterpart to Steps 11–12; not needed on the CSD route. If your tractography will be probtrackx2 instead of tckgen, BedpostX runs on the linked data.nii.gz, bvals, bvecs, and nodif_brain_mask.nii.gz; the four names it insists on are already the names the layout uses.

BIDS and pyAFQ​

QSIPrep's output is already a BIDS derivatives tree, so the copy-and-rename work in BIDS & pyAFQ is not needed; point pyAFQ at out/qsiprep directly.

Quality Check​

QSIPrep writes one HTML report per participant in out/qsiprep/sub-001.html. Open it. It shows the brain mask over the b=0, the DWI-to-T1 alignment, the distortion correction before and after, and framewise displacement per volume. The confounds file, dwi/*_desc-confounds_timeseries.tsv, has the per-volume motion numbers if you want thresholds instead of pictures; the same exclusion guidance as Step 8 applies.

Common Issues​

SymptomLikely causeFix
"No fieldmaps found" and no distortion correctionIntendedFor missing from the fieldmap JSONAdd it, pointing at the DWI file's relative path
Run fails at the T1 stageNo anat/ T1w in BIDS, or licence file path wrong inside the containerCheck the bind mount and the --fs-license-file path as seen from inside
Output resolution unexpected--output-resolution left at a default that does not match the dataSet it explicitly
Very slow or out of memoryToo many threads for the machine, or work dir on a slow diskLower --nthreads; put --work-dir on local storage

Next Step​

Proceed to Step 9: Tensor Fitting on the preprocessed DWI, then Step 11. Step 10 is not needed for QSIPrep output.