Structural MRI Pre-Processing Pipeline

Stanford Translational AI Lab (STAI)

Structural MRI Processing Pipeline

 

GitHub GitHub YouTube YouTube Wiki Wiki QC QC Sample

DOI

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).

Prerequisites

  • 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_synthstrip in $FREESURFER_HOME/bin
  • (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

Configuration, Key items in config.py

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 inside OUTPUT_DIR).
  • FREESURFER_HOME: Path to FreeSurfer installation directory.
  • SYNTHSTRIP_BIN: Full path to mri_synthstrip binary.
  • 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 to None or a filtered list.
  • FORCE_REPROCESSING: If True, 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 BIDS participants.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

 

Processing Steps

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.

Skullstrip QC

Inputs

  • NIfTI files discovered via STRUCTURE and 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.png montage 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 *.mat transforms

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.

Postprocess visualization

Steps

  1. Warp native masks → MNI with nearest-neighbor using the composite transform.
  2. N4 bias correction (if POSTPROCESS_APPLY_N4 = True).
  3. Masked Z-score: μ/σ computed within mask; background set to 0.
  4. Crop: tight bounding box (+ POSTPROCESS_CROP_MARGIN), affine updated (qform/sform set).
  5. Save multi-slice PNG montage.

Outputs (per subject/session; under the same registration anat/)

  • T1w_mni_mask.nii.gz
  • T1w_mni_warped_n4.nii.gz (if N4 enabled)
  • T1w_mni_zscore_fixed.nii.gz
  • T1w_mni_zscore_fixed_cropped.nii.gz
  • multislice_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.py writes 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 sample 1

QC sample 2

QC sample 3

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.csv
  • metadata/T2_metadata.csv

CLI

python generate_csv_metadata.py

Quick Start

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

Outputs (Where to Find Things)

  • SkullstripSTAGE_ROOTS["skullstrip"]
    *_brain.nii.gz, *_brain_mask.nii.gz, *_desc-qc.png, global QC/qc_summary.csv
  • RegistrationSTAGE_ROOTS["registration"]
    *_mni_rigid_warped.nii.gz, *_mni_warped.nii.gz, other/*_rigid.mat, other/*_affine.mat
  • Post-process → same anat/ under registration
    T1w_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 metadataCSV_METADATA_DIR
    T1_metadata.csv, T2_metadata.csv

Best Practices

  • Keep config.py under 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.gz overlay; verify correct template resolution.
  • CSV empty: confirm STRUCTURE and file patterns in config.py match your dataset.

Authors / Version

  • 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.

 

How to Cite This Work

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},
} ---

Mind Data Hub Home Page >>