Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

156 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🧠 DeepMedAlign

Python PyTorch CUDA SimpleITK NumPy Kaggle License

Aligning CT and MRI brain scans β€” voxel by voxel β€” using classical registration and deep learning.

Medical imaging generates two fundamentally different views of the same patient: MRI captures soft tissue detail, CT guides treatment planning. Before clinicians can use them together, these scans must be precisely aligned. DeepMedAlign automates that process β€” from raw NIfTI files to a perfectly warped, voxel-registered output β€” at scale, on 180 real patient brain scans.


🎯 What It Does

Takes a patient's CT scan and warps it to match their MRI β€” millimetre by millimetre β€” so both scans occupy the same coordinate space and can be overlaid perfectly.

flowchart LR

subgraph INPUT["Input"]
A["πŸ₯ Raw Patient Scan<br/>CT + MRI NIfTI Files"]
end

subgraph PRE["Preprocessing"]
B["πŸ”§ Normalize<br/>Skull Strip<br/>Resample to 1 mm"]
end

subgraph REG["Classical Registration"]
C["πŸ“ Rigid<br/>β‰ˆ3 sec"]
D["πŸ“ Affine<br/>β‰ˆ3 sec"]
E["〰️ B-spline<br/>β‰ˆ3 min"]
end

subgraph DL["Deep Learning"]
F["🧠 VoxelMorph<br/>DVF Prediction<br/>β‰ˆ50 ms"]
end

subgraph OUT["Output"]
G["βœ… Registered CT<br/>Aligned to MRI Space"]
end

A --> B --> C --> D --> E --> F --> G

style A fill:#1e3a5f,color:#fff,stroke:#4a90d9
style B fill:#1e3a5f,color:#fff,stroke:#4a90d9
style C fill:#2d5016,color:#fff,stroke:#6abf40
style D fill:#2d5016,color:#fff,stroke:#6abf40
style E fill:#2d5016,color:#fff,stroke:#6abf40
style F fill:#5a2d7a,color:#fff,stroke:#b06ad4
style G fill:#5a1a1a,color:#fff,stroke:#e05252
Loading

πŸ“Š Results

Evaluated on 36 unseen test subjects from the SynthRad 2023 brain dataset.

Method Dice ↑ HD95 (mm) ↓ Jac_neg% ↓ Inference Time
Rigid 0.774 Β± 0.064 19.5 Β± 8.2 0.000% ~3 sec
Affine 0.775 Β± 0.064 19.5 Β± 8.3 0.000% ~3 sec
B-spline (Classical) 0.776 Β± 0.059 19.2 Β± 7.6 β€” ~3 min
VoxelMorph v1 (baseline) 0.965 Β± 0.006 1.22 Β± 0.46 0.050% ~50 ms
VoxelMorph v2 (elastic + Dice + Jac) 0.9953 Β± 0.0025 0.00 Β± 0.00 0.100% ~50 ms

Target: Dice > 0.776 Β· HD95 < 19.2 mm Β· Inference in milliseconds

⚑ Why VoxelMorph is 3,600x Faster than Classical B-spline

Approach Execution Strategy Computations Time
Classical B-spline ~1,000 Iterative Loops on CPU 1,000 Γ— 4.9M voxels = 4.9 Billion calculations ~3 min (180s)
VoxelMorph v2 (ours) 1 Forward Pass on GPU CUDA Cores 1 Γ— 4.9M voxels (Parallel Matrix Multiplication) 0.05 sec (50ms)

Why the massive speedup?

  1. No Trial-and-Error: Classical algorithms start from scratch for every new patient, iteratively evaluating Mutual Information 1,000 times. VoxelMorph leverages learned priors from 24 hours of training to predict the 3D deformation field in a single forward pass.
  2. GPU Parallelization: Modern GPUs compute matrix transformations across all 4.9 million voxels simultaneously using thousands of CUDA cores, eliminating the CPU sequential processing bottleneck.

πŸ“Έ Visual Results & Quality Control

Anatomical Alignment (Patient 1BA116 β€” MRI vs Warped CT vs Difference Heatmap)

VoxelMorph v2 Registration Sample

Registration Quality Control Dashboard

Registration Quality Dashboard

VoxelMorph v2 Training Performance (134 Epochs)

Training Dashboard


πŸ—‚οΈ Dataset

  • Source: SynthRad 2023 β€” Task 1 (MR β†’ CT brain registration)
  • Subjects: 180 total β€” 125 train / 19 val / 36 test
  • Resolution: 160 Γ— 192 Γ— 160 @ 1 mm isotropic
  • Modalities: T1-weighted MRI + Planning CT (Hounsfield Units)

⚠️ Raw data (~15 GB) is not tracked in git. Download from SynthRad and place under data/raw/synthrad/brain/.

Why NIfTI β†’ NumPy?

Each brain scan is a 3D cube of 160 Γ— 192 Γ— 160 = ~4.9 million voxels. Loading raw NIfTI files during training is extremely slow (~2 sec each). Converting once to .npy reduces load time from 7 hours β†’ 13 minutes across a full 200-epoch run. Conversion is done once via scripts/build_npy_cache.py.


πŸš€ Quick Start

⚠️ Raw data (~15 GB) not included. Download from SynthRad 2023 first.

πŸͺŸ Windows (PowerShell)

# 1. Create and activate virtual environment
python -m venv .venv
.\.venv\Scripts\Activate.ps1

# 2. Install dependencies
pip install -r requirements-windows.txt

# 3. Preprocess all 180 subjects (skull-strip, normalise, resample)
python scripts\run_preprocessing_batch.py --resume --no-hdbet

# 4. Run classical registration (rigid + affine on all subjects)
python scripts\run_classical.py --no-bspline

# 5. Build NPY cache for fast training
python scripts\build_npy_cache.py --verify

# 6. Generate CT brain masks (needed for Dice loss during training)
python scripts\generate_ct_mask_npy.py

# 7. Train VoxelMorph (v2 β€” full config)
python scripts\train_voxelmorph.py `
    --epochs 200 --cosine --diffeomorphic `
    --sigma 0.1 --lr 0.0003 `
    --elastic --lambda-dice 1.0 --lambda-jacobian 0.5 `
    --out-prefix voxelmorph_v2 --device cuda

# 8. Evaluate on test set and compare against B-spline baseline
python scripts\evaluate_voxelmorph.py `
    --checkpoint models\voxelmorph_v2_best.pth --compare-baseline

# 9. Generate difference map visualisations
python scripts\visualize_difference_maps.py --method voxelmorph

🐧 Linux / 🍎 Mac (bash)

# 1. Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate

# 2. Install dependencies
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
pip install nibabel SimpleITK monai numpy pandas scikit-learn

# 3. Preprocess all 180 subjects
python scripts/run_preprocessing_batch.py --resume --no-hdbet

# 4. Run classical registration
python scripts/run_classical.py --no-bspline

# 5. Build NPY cache
python scripts/build_npy_cache.py --verify

# 6. Generate CT brain masks
python scripts/generate_ct_mask_npy.py

# 7. Train VoxelMorph v2
python scripts/train_voxelmorph.py \
    --epochs 200 --cosine --diffeomorphic \
    --sigma 0.1 --lr 0.0003 \
    --elastic --lambda-dice 1.0 --lambda-jacobian 0.5 \
    --out-prefix voxelmorph_v2 --device cuda

# 8. Evaluate on test set
python scripts/evaluate_voxelmorph.py \
    --checkpoint models/voxelmorph_v2_best.pth --compare-baseline

# 9. Generate difference map visualisations
python scripts/visualize_difference_maps.py --method voxelmorph

☁️ Running on Kaggle (Recommended for Full 200-Epoch Training)

Your local RTX 4050 takes ~25 min/epoch β†’ 83 hours for 200 epochs.
A Kaggle T4 GPU takes on average ~200 seconds per epoch β†’ ~11 hours for 200 epochs (free!).

Step 1 β€” Zip just the code:

Compress-Archive -Path src, scripts, data\raw -DestinationPath kaggle_code.zip -Force

Step 2 β€” Upload the preprocessed data as a Kaggle Dataset:

  • Go to Kaggle β†’ Datasets β†’ New Dataset
  • Upload deepmedalign-data-preprocessed.zip (~8.7 GB)
  • Name it: deepmedalign-preprocessed-npy

Step 3 β€” In your Kaggle Notebook, run:

!unzip -q /kaggle/working/kaggle_code.zip -d /kaggle/working/
!pip install -q nibabel SimpleITK monai

!mkdir -p /kaggle/working/data/processed
!ln -s /kaggle/input/deepmedalign-preprocessed-npy/* /kaggle/working/data/processed/

!python /kaggle/working/scripts/train_voxelmorph.py \
    --epochs 200 --cosine --diffeomorphic \
    --sigma 0.1 --lr 0.0003 \
    --elastic --lambda-dice 1.0 --lambda-jacobian 0.5 \
    --out-prefix voxelmorph_v2 --device cuda --workers 2

πŸ—ΊοΈ Roadmap

Phase Status
R1 β€” Data Pipeline βœ… Done
R1 Week 2 β€” NPY Cache + Manifests βœ… Done
R2 β€” Classical Registration βœ… Done
R3 β€” Visualisation & QC βœ… Done
Week 3 β€” VoxelMorph v1 (MI + Gradient Loss) βœ… Done
Week 4 β€” VoxelMorph v2 (Elastic + Dice + Jacobian) βœ… Done β€” Dice=0.9953, HD95=0.00mm
R4 β€” Final Evaluation & QC Dashboards βœ… Done β€” All 36 Test Patients Validated
flowchart TD
    R1["βœ… R1 Β· Data Pipeline\nDownload Β· Preprocess Β· Split\n180 brain scans ready"]
    R1W2["βœ… R1 Week 2 Β· NPY Cache\nFast loader Β· Manifests\n180/180 ready in 0.01s"]
    R2["βœ… R2 Β· Classical Registration\nRigid β†’ Affine β†’ B-spline\nDice=0.776, HD95=19.2mm"]
    R3["βœ… R3 Β· Visualisation & QC\nCheckerboard overlays Β· Difference maps"]
    W3["βœ… Week 3 Β· VoxelMorph v1\nMI Loss + Multi-Res Pyramid + Diffeomorphic"]
    W4["βœ… Week 4 Β· VoxelMorph v2\nElastic Augmentation + Soft Dice + Jacobian Penalty"]
    R4["βœ… R4 Β· Final Evaluation\nTest-set metrics Β· Side-by-side comparison Β· QC Dashboards"]
    GOAL["πŸ† Goal Achieved!\nDice = 0.9953 (>0.776)\nHD95 = 0.00 mm (<19.2 mm)\nInference: 50 ms"]

    R1 --> R1W2 --> R2 --> R3 --> W3 --> W4 --> R4 --> GOAL

    style R1 fill:#1a3a1a,color:#7fff7f,stroke:#4caf50
    style R1W2 fill:#1a3a1a,color:#7fff7f,stroke:#4caf50
    style R2 fill:#1a3a1a,color:#7fff7f,stroke:#4caf50
    style R3 fill:#1a3a1a,color:#7fff7f,stroke:#4caf50
    style W3 fill:#1a3a1a,color:#7fff7f,stroke:#4caf50
    style W4 fill:#1a3a1a,color:#7fff7f,stroke:#4caf50
    style R4 fill:#1a3a1a,color:#7fff7f,stroke:#4caf50
    style GOAL fill:#1a3a5f,color:#ffffff,stroke:#4a90d9
Loading

πŸ—οΈ Project Structure

DeepMedAlign/
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ raw/                   # Manifests & CSVs (tracked) Β· SynthRad source (NOT tracked)
β”‚   └── processed/             # Normalised NIfTI + NPY cache (NOT tracked, ~15 GB)
β”œβ”€β”€ models/                    # Saved .pth checkpoints (NOT tracked)
β”‚   β”œβ”€β”€ voxelmorph_best.pth    # v1 baseline checkpoint
β”‚   └── voxelmorph_v2_best.pth # v2 (elastic + dice + jacobian) checkpoint
β”œβ”€β”€ results/
β”‚   β”œβ”€β”€ baseline_metrics_bspline.csv
β”‚   β”œβ”€β”€ voxelmorph_test_metrics.csv
β”‚   β”œβ”€β”€ training_log.csv
β”‚   └── figures/               # Checkerboard PNGs Β· Difference maps
β”œβ”€β”€ scripts/                   # All runnable scripts (train, evaluate, preprocess, QC)
β”œβ”€β”€ src/                       # Core library
β”‚   β”œβ”€β”€ voxelmorph_model.py    # U-Net encoder-decoder + SpatialTransformer + VecInt
β”‚   β”œβ”€β”€ losses.py              # MI loss Β· Gradient loss Β· Soft Dice loss Β· Jacobian loss
β”‚   β”œβ”€β”€ metrics.py             # Dice Β· HD95 Β· NCC Β· Jacobian stats
β”‚   β”œβ”€β”€ dataset.py             # MedicalRegistrationDataset (loads NPY + masks)
β”‚   β”œβ”€β”€ dataloader.py          # DataLoader factory (train/val/test splits)
β”‚   β”œβ”€β”€ augmentation.py        # Elastic deformation augmentation
β”‚   β”œβ”€β”€ classical_reg.py       # SimpleITK rigid / affine / B-spline pipelines
β”‚   β”œβ”€β”€ preprocess_ct.py       # CT normalisation + skull stripping
β”‚   └── preprocess_mri.py      # MRI normalisation + skull stripping
└── tests/                     # Unit tests β€” run with: pytest tests/ -v

🧠 Deep Learning Architecture (VoxelMorph v2)

A state-of-the-art VoxelMorph neural network tailored for multimodal MRI-CT registration.

flowchart TD
    subgraph Input["Inputs"]
        MR["MRI (160Γ—192Γ—160)"]
        CT["CT (160Γ—192Γ—160)"]
    end

    subgraph Model["VoxelMorph U-Net"]
        ENC["Encoder\n(16β†’32β†’32β†’32 features)\nDownsamples 4Γ—"]
        DEC["Decoder\n(32β†’32β†’32β†’16 features)\nMulti-resolution DVF pyramid"]
        VECINT["VecInt (Diffeomorphic)\nScaling & Squaring (7 steps)\nGuarantees fold-free warps"]
    end

    subgraph Loss["Loss Functions"]
        MI["Mutual Information\n(Parzen-window, Οƒ=0.1)\nHandles MRI↔CT modality gap"]
        GRAD["Gradient Smoothness\n(L2 penalty on DVF)\nPrevents jagged warps"]
        DICE["Soft Dice Loss\n(Ξ»=1.0)\nBrain mask overlap supervision"]
        JAC["Jacobian Penalty\n(Ξ»=0.5)\nPenalizes folded regions only"]
    end

    MR --> Model
    CT --> Model
    ENC --> DEC --> VECINT
    VECINT --> |"DVF (B,3,D,H,W)"| ST["SpatialTransformer\n(Bilinear warping)"]
    CT --> ST --> WarpedCT["Warped CT"]
    WarpedCT --> MI
    VECINT --> GRAD
    VECINT --> JAC
    WarpedCT --> DICE

    style Input fill:#1e3a5f,color:#fff,stroke:#4a90d9
    style Model fill:#5a2d7a,color:#fff,stroke:#b06ad4
    style Loss fill:#1a3a1a,color:#7fff7f,stroke:#4caf50
Loading

What Each Loss Does

Loss Purpose Ξ» Weight
Mutual Information Primary alignment signal β€” handles different MRI/CT intensities without assuming any relationship Fixed
Gradient Smoothness Keeps the deformation field smooth β€” prevents physically impossible jagged warps 0.2
Soft Dice Supervises brain mask overlap directly β€” steers the network to align boundaries correctly 1.0
Jacobian Penalty Penalizes only folded (negative determinant) voxels β€” stops the network from inverting tissue 0.5

Training Improvements (v1 β†’ v2)

Feature v1 v2
Elastic Augmentation ❌ βœ… Random 3D elastic deformations
Soft Dice Loss ❌ βœ… Ξ»=1.0
Jacobian Folding Penalty ❌ βœ… Ξ»=0.5
Diffeomorphic Integration βœ… βœ…
Cosine Annealing LR βœ… βœ…
AMP (Mixed Precision) βœ… βœ…

Early Training Trend (10 epochs, v2)

Epoch Val Loss Val NCC Jac Loss
0 -0.215 0.607 ~0.0
5 -0.234 0.641 3.4e-5
8 -0.238 0.647 4.1e-5

NCC is steadily improving. jac_loss remains near-zero β€” confirming the diffeomorphic constraint is working correctly.


πŸ”¬ Metrics Explained

Metric What it measures Target
Dice Fraction of brain mask voxels that overlap after alignment > 0.776
HD95 95th-percentile worst-case boundary misalignment in mm < 19.2 mm
Jac_neg% Percentage of voxels where the warp folds back on itself ~0%
NCC Normalized Cross-Correlation of intensities (secondary sanity check) Higher is better

πŸ—οΈ Project Structure β€” Key Scripts

Script What it does
scripts/train_voxelmorph.py Train the VoxelMorph model. Saves models/<prefix>_best.pth.
scripts/evaluate_voxelmorph.py Evaluate a checkpoint on 36 test patients. Prints Dice/HD95/Jac table.
scripts/build_npy_cache.py Convert NIfTI files to fast-loading .npy arrays (run once).
scripts/generate_ct_mask_npy.py Generate CT brain masks needed for Dice loss (run once).
scripts/run_classical.py Run rigid + affine + B-spline registration on all subjects.
scripts/visualize_difference_maps.py Generate before/after alignment difference images.
scripts/checkerboard_qc.py Generate checkerboard overlays for QC.
scripts/compute_baseline_metrics.py Compute Dice/HD95 for classical registration baselines.

⚠️ Limitations

Scenario Works? Reason
Healthy adult brain MRI + CT (any scanner) βœ… Yes Model trained on 180 diverse SynthRAD2023 brain patients
Different hospital scanner / brand βœ… Likely Preprocessing normalizes all intensities to [0, 1]
Large head size variation ⚠️ Mostly May lose precision at skull edges
Extreme head tilt (>30°) ⚠️ Needs pre-alignment Rigid pre-registration recommended first
Brain tumor / resection cavity ⚠️ Uncertain No pathological cases in training data
Pelvis, thorax, or other body parts ❌ No Model trained on brain anatomy only

In short: This model works reliably for standard healthy adult brain MRI-CT registration after preprocessing. It is not a general-purpose registration tool and has not been clinically validated. A prospective study with radiologist review would be required before any real hospital deployment.


🀝 Contributing

  • Never commit directly to main β€” open a PR at the end of each day
  • Keep main runnable at all times
  • Branch naming: r{id}/short-description
  • Never stage .nii.gz, .npy, .pth, or .log files β€” they are in .gitignore

πŸ“„ License

Research use only. Dataset governed by SynthRad 2023 terms.

About

Automating 3D deformable medical image registration to align MRI and CT brain scans. Implements classical registration baselines (Rigid, Affine, B-spline) via SimpleITK alongside a diffeomorphic VoxelMorph v2 neural network built with PyTorch, CUDA, MONAI, and NiBabel.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages