Skip to main content

Parallel Processing in DTI Pipelines

DTI preprocessing involves running the same operations on many subjects independently. Since each subject's data is processed in isolation, this is an "embarrassingly parallel" problem — you can process multiple subjects at the same time with almost no additional complexity. The only constraint is hardware resources.

Resource Requirements by Stage​

Before parallelizing, know how much each step needs:

StageRAM per SubjectCPU CoresTime per SubjectParallelism Level
dcm2niix~200 MB1< 1 minHigh (30–60 jobs)
ANTs skull stripping2–4 GB2–430–90 minLow (4–8 jobs)
TOPUP~1 GB15–15 minMedium (10–20 jobs)
Denoising (MRtrix3)~2 GB12–5 minMedium (10–20 jobs)
Eddy (CPU)4–8 GB1–41–4 hoursLow (4–8 jobs)
Eddy (GPU)4 GB1 + GPU5–15 minLimited by GPUs
BedpostX (CPU)4–8 GB1–26–24 hoursVery low (2–4 jobs)
BedpostX (GPU)4 GB1 + GPU30 min–2 hrLimited by GPUs
DTIFIT~500 MB1< 1 minHigh (30–60 jobs)
FLIRT~500 MB1< 1 minHigh (30–60 jobs)

Calculating Safe Parallelism​

# Check available memory (Linux)
available_gb=$(free -g | awk '/^Mem:/{print $7}')

# macOS
available_gb=$(sysctl -n hw.memsize | awk '{print int($1/1073741824)}')

# Calculate max jobs for a given stage
ram_per_job=4 # GB — adjust per stage (see table above)
max_jobs=$((available_gb / ram_per_job))
echo "Safe to run $max_jobs concurrent jobs for this stage"

If you calculate 16 safe jobs, start with 10 and monitor with htop. Increase gradually. One out-of-memory crash can corrupt output files and force a full re-run.

Method 1: GNU Parallel​

GNU Parallel is the simplest way to run multiple subjects concurrently:

# Install (Ubuntu/Debian)
sudo apt install parallel

# Run eddy for all subjects, 6 at a time
ls -d "$base_dir"/denoised/sub-* | xargs -n1 basename | \
parallel -j 6 ./process_eddy.sh {}

# With progress bar and logging
ls -d "$base_dir"/denoised/sub-* | xargs -n1 basename | \
parallel -j 6 --progress --joblog eddy_parallel.log \
./process_eddy.sh {}

Method 2: Bash Background Jobs​

For environments without GNU Parallel:

#!/bin/bash
# parallel_process.sh — Run a stage in parallel using bash

max_jobs=6
job_count=0

subjects=$(ls -d "$base_dir"/nifti/sub-* | xargs -n1 basename)

for subj in $subjects; do
echo "Starting: $subj"
./process_stage.sh "$subj" > "logs/${subj}.log" 2>&1 &
((job_count++))

# Wait for a slot to open if we've hit the limit
if (( job_count >= max_jobs )); then
wait -n # Wait for any one job to finish
((job_count--))
fi
done

wait # Wait for all remaining jobs to finish
echo "All subjects complete."

Protecting Against SSH Disconnection​

Long-running jobs are lost if your SSH session drops. Always use a terminal multiplexer:

tmux​

# Start a new session
tmux new -s dti

# Run your processing
./run_all_subjects.sh

# Detach: Ctrl-B, then D (session keeps running)
# Reconnect later:
tmux attach -t dti

# List sessions:
tmux ls

screen​

# Start a new session
screen -S dti

# Run your processing
./run_all_subjects.sh

# Detach: Ctrl-A, then D
# Reconnect: screen -r dti

nohup (simplest, no reattach)​

nohup ./run_all_subjects.sh > processing.log 2>&1 &

# Monitor:
tail -f processing.log

Multi-Stage Pipeline Script​

Run the full pipeline with appropriate parallelism at each stage:

#!/bin/bash
# run_pipeline.sh — Full pipeline with per-stage parallelism

base_dir="/path/to/project"

echo "=== Stage 1: DICOM to NIfTI (30 jobs) ==="
ls subjects.txt | parallel -j 30 ./01_dcm2nii.sh {}

echo "=== Stage 2: Skull Stripping (4 jobs) ==="
ls subjects.txt | parallel -j 4 ./02_skull_strip.sh {}

echo "=== Stage 3-6: TOPUP chain (10 jobs) ==="
ls subjects.txt | parallel -j 10 ./03_topup_chain.sh {}

echo "=== Stage 7: Denoising (10 jobs) ==="
ls subjects.txt | parallel -j 10 ./07_denoise.sh {}

echo "=== Stage 8: Eddy (6 jobs) ==="
ls subjects.txt | parallel -j 6 ./08_eddy.sh {}

echo "=== Stage 10-11: Shells + DTIFIT (30 jobs) ==="
ls subjects.txt | parallel -j 30 ./10_shells_dtifit.sh {}

echo "=== Stage 12: Registration (30 jobs) ==="
ls subjects.txt | parallel -j 30 ./12_flirt.sh {}

echo "Pipeline complete."

Common Pitfalls​

ProblemCauseSolution
Jobs killed randomlyOut of memoryReduce max_jobs; monitor with htop
Disk full mid-processingIntermediate files fill diskCheck disk usage (df -h) before starting; clean up intermediate files
Corrupted output filesJob killed during file writeDelete corrupted files and re-run; use verification scripts
All jobs finish instantlyScript error (wrong paths)Check one subject manually first before parallelizing
SSH drops, jobs dieNot using tmux/screenAlways use a terminal multiplexer or nohup