The Structural MRI (sMRI) Processing Pipeline is a lightweight, end-to-end workflow for structural brain MRI. It performs brain extraction, registration to MNI, and post-processing (N4 bias correction, masked Z-score normalization, cropping), and produces clear QC visualizations plus CSV metadata for downstream analysis. The design is config-driven, scalable across cohorts, and interoperable with common neuroimaging tools and AI pipelines.
- Brain Extraction, SynthStrip (Singularity/Docker/FreeSurfer) with montage QC & volume summary.
- Registration, Rigid → Affine alignment to
MNI152NLin2009cAsym (1mm)using SimpleITK. - Post-processing, Optional N4, masked Z-score, tight cropping with affine update.
- QC & Reports, Per-subject overlays and global QC CSV (PASS/FAIL, volumes, messages).
- Metadata, CSV indices for warped/normalized/cropped outputs (+ optional demographics encodings).
- Python ≥ 3.8
- Python packages:
numpy,nibabel,SimpleITK,matplotlib,tqdm,templateflow,pandas - SynthStrip via one of:
- Singularity image:
freesurfer/synthstrip:latest(recommended) - Docker image:
freesurfer/synthstrip:latest - FreeSurfer installation with
mri_synthstripin$FREESURFER_HOME/bin
- Singularity image:
- (Optional) TemplateFlow cache (auto-downloads
MNI152NLin2009cAsym) - (Optional) conda for environment management
Example env setup
conda create -y -n smri python=3.10
conda activate smri
pip install numpy nibabel SimpleITK matplotlib tqdm templateflow pandas
The pipeline is config-driven. Edit config.py once and drive all scripts consistently. Below is a concise map of the most important keys, with usage tips and sensible defaults.
- Paths
-
INPUT_DIR: Root directory for input NIfTI files.OUTPUT_DIR: Root for all outputs (skullstrip / registration / postprocess / metadata).LOG_DIR: Log directory (automatically placed insideOUTPUT_DIR).FREESURFER_HOME: Path to FreeSurfer installation directory.SYNTHSTRIP_BIN: Full path tomri_synthstripbinary.SYNTHSTRIP_SIF_PATH: Singularity image path for SynthStrip (if used in container mode).
- Structure
-
STRUCTURE: Path pattern template (e.g.,{root}/{subject}/{session}/anat).STAGE_ROOTS: Dictionary with stage-specific output subfolders (e.g.,skullstrip,registration,qc).- Legacy aliases:
FORCE_REPROCESS,QC_DIR,SKULLSTRIP_DIR, etc. are auto-derived.
- Scope
-
DATASET_NAME: Name of the dataset being processed (e.g.,"OpenNeuro ds004215").MODALITIES: List of modalities to process (usually["T1w", "T2w"]).SUBJECTS,SESSIONS: Can be set toNoneor a filtered list.FORCE_REPROCESSING: IfTrue, overwrites previous outputs.
- Parallel Processing
-
CPU_CORES: Number of CPU cores available on the system.ENABLE_PARALLEL: Whether to run stages in parallel.MAX_WORKERS: Number of parallel workers to spawn.
- Logging
-
LOG_LEVEL: Logging verbosity (e.g.,"INFO","DEBUG").LOG_FORMAT: Format string for log messages.
- File patterns
-
SKULLSTRIP_T1_FILE_PATTERN: Pattern to locate T1-weighted input NIfTI files.SKULLSTRIP_T2_FILE_PATTERN: Pattern to locate T2-weighted input NIfTI files.
- QC configuration
-
ENABLE_QC: Whether to enable QC checks.QC_GENERATE_IMAGES: Whether to save image previews for QC.QC_SUMMARY_FILE: Filename of the summary CSV (e.g.,"qc_summary.csv").QC_BRAIN_VOLUME_MIN_ML,QC_BRAIN_VOLUME_MAX_ML: Min/max bounds for acceptable brain volume.DICE_PASS_THRESHOLD: Threshold for Dice overlap between masks.
- Registration
-
MNI_TEMPLATE_VERSION: e.g.,"MNI152NLin2009cAsym".MNI_TEMPLATE_RESOLUTION: e.g.,1(for 1mm resolution).
- Post-process
-
POSTPROCESS_APPLY_N4: Apply N4 bias field correction before normalization.POSTPROCESS_CROP_MARGIN: Crop margin in voxels after Z-score normalization.
- CSV metadata
-
GENERATE_CSV_METADATA: Whether to generate output CSV files.CSV_METADATA_DIR: Path to store CSV files.T1_CSV_FILENAME,T2_CSV_FILENAME: Output file names for metadata.PARTICIPANTS_TSV_PATH: Path to BIDSparticipants.tsv.CSV_COLUMNS: Ordered list of fields to appear in the metadata files.T1_PROCESSED_PATTERNS,T2_PROCESSED_PATTERNS: Patterns to locate output files.SEX_ENCODING,HANDEDNESS_ENCODING: Encoding dictionaries.MISSING_DATA_VALUE: Value used for missing entries (e.g.,"n/a").REQUIRE_BOTH_MODALITIES: Whether both T1w and T2w are mandatory.VALIDATE_FILE_EXISTENCE: Whether to check for input/output file presence before processing.
Example config.py
# ============ GENERAL CONFIGURATION ============
DATASET_NAME = "OpenNeuro ds004215"
INPUT_DIR = "/path/to/REDACTED"
OUTPUT_DIR = "/path/to/REDACTED"
LOG_DIR = os.path.join(OUTPUT_DIR, "logs")
# ============ HARDWARE & PARALLEL ============
CPU_CORES = 64
ENABLE_PARALLEL = True
MAX_WORKERS = 8
# ============ LOGGING ============
LOG_LEVEL = "INFO"
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
# ============ FREESURFER ============
FREESURFER_HOME = "/path/to/REDACTED"
SYNTHSTRIP_BIN = os.path.join(FREESURFER_HOME, "bin", "mri_synthstrip")
SYNTHSTRIP_SIF_PATH = "/path/to/REDACTED"
# ============ FILE PATTERNS ============
SKULLSTRIP_T1_FILE_PATTERN = "*acq-MPRAGE_T1w.nii.gz"
SKULLSTRIP_T2_FILE_PATTERN = "*acq-CUBE_T2w.nii.gz"
# ============ PROCESSING SCOPE ============
MODALITIES = ["T1w", "T2w"]
SUBJECTS = None
SESSIONS = None
FORCE_REPROCESSING = True
# ============ QUALITY CONTROL ============
ENABLE_QC = True
QC_GENERATE_IMAGES = True
QC_SUMMARY_FILE = "qc_summary.csv"
QC_BRAIN_VOLUME_MIN_ML = 800
QC_BRAIN_VOLUME_MAX_ML = 2000
DICE_PASS_THRESHOLD = 0.8
# ============ REGISTRATION ============
MNI_TEMPLATE_VERSION = "MNI152NLin2009cAsym"
MNI_TEMPLATE_RESOLUTION = 1
# ============ POSTPROCESS ============
POSTPROCESS_APPLY_N4 = True
POSTPROCESS_CROP_MARGIN = 1
# ============ METADATA ============
GENERATE_CSV_METADATA = True
CSV_METADATA_DIR = os.path.join(OUTPUT_DIR, "metadata")
T1_CSV_FILENAME = "T1_metadata.csv"
T2_CSV_FILENAME = "T2_metadata.csv"
PARTICIPANTS_TSV_PATH = "/path/to/REDACTED"
CSV_COLUMNS = [
"subjectId", "session", "MNI_Warped", "MNI_ZSCORE",
"MNI_Z_Cropped", "age", "sex", "handedness"
]
T1_PROCESSED_PATTERNS = {
"MNI_Warped": "*T1w*_mni_warped.nii.gz",
"MNI_ZSCORE": "T1w_mni_zscore_fixed.nii.gz",
"MNI_Z_Cropped": "T1w_mni_zscore_fixed_cropped.nii.gz"
}
T2_PROCESSED_PATTERNS = {
"MNI_Warped": "*T2w*_mni_warped.nii.gz",
"MNI_ZSCORE": "T2w_mni_zscore_fixed.nii.gz",
"MNI_Z_Cropped": "T2w_mni_zscore_fixed_cropped.nii.gz"
}
SEX_ENCODING = {
"male": 1,
"female": 2,
"n/a": 0
}
HANDEDNESS_ENCODING = {
"right": 1,
"left": 2,
"ambidextrous": 3,
"n/a": 0
}
MISSING_DATA_VALUE = "n/a"
REQUIRE_BOTH_MODALITIES = False
VALIDATE_FILE_EXISTENCE = True
# ============ STRUCTURE ============
STAGE_ROOTS = {
"skullstrip": os.path.join(OUTPUT_DIR, "skullstrip"),
"registration": os.path.join(OUTPUT_DIR, "registration"),
"qc": os.path.join(OUTPUT_DIR, "QC"),
}
STRUCTURE = "{root}/{subject}/{session}/anat"
# Legacy aliases
FORCE_REPROCESS = FORCE_REPROCESSING
QC_DIR = STAGE_ROOTS["qc"]
SKULLSTRIP_DIR = STAGE_ROOTS["skullstrip"]
REGISTRATION_DIR = STAGE_ROOTS["registration"]
POSTPROCESS_MODALITIES = ["T1", "T2"]
POSTPROCESS_SUBJECTS = SUBJECTS
0) Convert DICOM to NIfTI, dicom_to_nifti.py
If your dataset is already in NIfTI, skip this step. Otherwise, this script converts DICOM folders into gzip-compressed NIfTI (BIDS-style layout) using dcm2niix.
Inputs
- DICOM series or a root directory containing series
Outputs
{subject}/{session}/anat/*.nii.gz(+ JSON sidecars)
CLI
python dicom_to_nifti.py --dicom_dir /path/to/DICOM --output_dir /path/to/nifti --subject sub-0001 --session ses-01
1) Brain Extraction, skullstrip.py
Removes non-brain tissue using SynthStrip. The script attempts Singularity → Docker → FreeSurfer binary (in that order). Generates brain, mask, and QC images.

Inputs
- NIfTI files discovered via
STRUCTUREand patterns:*acq-MPRAGE_T1w.nii.gz*acq-CUBE_T2w.nii.gz
Outputs (per subject/session in STAGE_ROOTS["skullstrip"])
*_brain.nii.gz,*_brain_mask.nii.gz*_desc-qc.pngmontage overlay- Global CSV:
QC/qc_summary.csv(brain volume, PASS/FAIL, paths, message)
CLI
python skullstrip.py # process all
python skullstrip.py --subject sub-ON12345
2) Registration to MNI (Rigid → Affine), reg.py
Registers T1w to MNI152NLin2009cAsym (1mm) with SimpleITK (Mattes MI, multi-resolution), then applies the same transforms to T2w in the same session.
Method
- Rigid: Euler3D, shrink [4,2,1], linear
- Affine: 12-DOF, shrink [2,1], linear
- Final resampling: composite transform (rigid+affine) for single interpolation
- Saves
*.mattransforms
Outputs (per subject/session in STAGE_ROOTS["registration"])
*_mni_rigid_warped.nii.gz(QC/ref)*_mni_warped.nii.gz(final)other/*_rigid.mat,other/*_affine.mat
CLI
python reg.py
python reg.py --subject sub-ON12345
3) Post-processing, postprocess_mni_mask_zscore_crop.py
Warp masks to MNI, run optional N4 bias correction, compute masked Z-score, crop tightly around the brain, and save a multi-slice visualization.

Steps
- Warp native masks → MNI with nearest-neighbor using the composite transform.
- N4 bias correction (if
POSTPROCESS_APPLY_N4 = True). - Masked Z-score: μ/σ computed within mask; background set to 0.
- Crop: tight bounding box (+
POSTPROCESS_CROP_MARGIN), affine updated (qform/sform set). - Save multi-slice PNG montage.
Outputs (per subject/session; under the same registration anat/)
T1w_mni_mask.nii.gzT1w_mni_warped_n4.nii.gz(if N4 enabled)T1w_mni_zscore_fixed.nii.gzT1w_mni_zscore_fixed_cropped.nii.gzmultislice_visualization_<subj>.png- (T2 equivalents when available; falls back to T1 mask if T2 mask missing)
CLI
python postprocess_mni_mask_zscore_crop.py
python postprocess_mni_mask_zscore_crop.py --subject sub-ON12345
4) Quality Control & Reports, skullstrip.py, structural_qc.py
skullstrip.pywrites per-subject QC montages and a global QC CSV with brain volume and status.structural_qc.py(optional helpers) can support extended validations (e.g., Dice vs reference) if needed.



QC CSV Columns
input_file,output_file,modality,qc_status,brain_volume_ml,qc_image_path,error_message
5) CSV Metadata, generate_csv_metadata.py
Scans registration outputs and builds CSVs listing MNI warped, Z-score, and cropped paths, with optional demographics (participants.tsv) and encodings (sex, handedness).
Outputs
metadata/T1_metadata.csvmetadata/T2_metadata.csv
CLI
python generate_csv_metadata.py
Show commands
# 0) (optional) environment
conda create -y -n smri python=3.10
conda activate smri
pip install numpy nibabel SimpleITK matplotlib tqdm templateflow pandas
# 1) configure
# edit config.py (private paths, thresholds)
# 2) brain extraction
python skullstrip.py
# 3) registration
python reg.py
# 4) post-process
python postprocess_mni_mask_zscore_crop.py
# 5) metadata tables
python generate_csv_metadata.py
- Skullstrip →
STAGE_ROOTS["skullstrip"]*_brain.nii.gz,*_brain_mask.nii.gz,*_desc-qc.png, globalQC/qc_summary.csv - Registration →
STAGE_ROOTS["registration"]*_mni_rigid_warped.nii.gz,*_mni_warped.nii.gz,other/*_rigid.mat,other/*_affine.mat - Post-process → same
anat/underregistrationT1w_mni_mask.nii.gz,T1w_mni_zscore_fixed.nii.gz,T1w_mni_zscore_fixed_cropped.nii.gz,multislice_visualization_<subj>.png(and T2 analogs) - CSV metadata →
CSV_METADATA_DIRT1_metadata.csv,T2_metadata.csv
Best Practices
- Keep
config.pyunder version control; avoid hardcoding private paths in scripts. - Use Singularity/Docker for SynthStrip consistency across systems.
- Visually inspect outliers flagged by QC (extreme brain volumes, failed masks/registration).
- Pin TemplateFlow template (
MNI152NLin2009cAsym, 1mm) to ensure reproducibility. - Log CPU threads and memory; large cohorts benefit from batched runs.
Troubleshooting
- SynthStrip not found: ensure one of Singularity/Docker/FreeSurfer is available in PATH; try
freesurfer/synthstrip:latest. - Strange intensity after N4: disable N4 (
POSTPROCESS_APPLY_N4 = False) and compare before/after Z-score. - Registration misalignment: check
*_mni_rigid_warped.nii.gzoverlay; verify correct template resolution. - CSV empty: confirm
STRUCTUREand file patterns inconfig.pymatch your dataset.
- Author: Mohammad H. Abbasi (mabbasi [at] stanford.edu)
- Lab: Stanford University, STAI Lab (https://stai.stanford.edu)
- Created: 2025 | Version: 1.0.0 | Last update: Sep 18, 2025
Acknowledgements
Thanks to the open-source neuroimaging community (FreeSurfer/SynthStrip, TemplateFlow, SimpleITK, nibabel) and dataset providers.
If you use this pipeline in your research, please cite it as follows:
Abbasi, M.H., & Adeli, E. (2025). sMRI Processing Pipeline: A lightweight, end-to-end framework for BIDS-compatible structural MRI preprocessing and quality control.
Stanford Translational AI Lab, Stanford University. Zenodo.
https://doi.org/10.5281/zenodo.17503175
@software{abbasi2025smri,
author = {Mohammad Hassan Abbasi and Ehsan Adeli},
title = {s{MRI} {P}rocessing {P}ipeline: A lightweight, end-to-end workflow for structural brain {MRI} preprocessing and quality control},
year = {2025},
institution = {Stanford Translational AI Lab, Stanford University},
publisher = {Zenodo},
doi = {10.5281/zenodo.17503175},
url = {https://doi.org/10.5281/zenodo.17503175},
note = {Zenodo, doi: 10.5281/zenodo.17503175},
}
---


GitHub
Wiki