Deep learning-based 3D myocardial scar reconstruction from sparse 2D Late Gadolinium-Enhanced Cardiac MRI (LGE-CMR).
CardioScar implements a coordinate-based Bayesian neural network that reconstructs continuous 3D scar probability fields from sparse 2D MRI slices. It addresses the challenge of low through-plane resolution (8-10mm) in LGE-CMR by learning smooth anatomically plausible interpolations that preserve narrow conducting isthmuses critical for arrhythmia prediction.
Key Features:
- Faster training than legacy implementation
- Bayesian uncertainty quantification via Monte Carlo Dropout
- Configurable architecture - default 50k parameters (4×128), scalable to legacy-equivalent 330k (6×256)
- Foundation model support - train once on a cohort, fine-tune per patient
- Production-ready architecture - type-safe contracts, CLI tooling, comprehensive testing
- Oblique image support - correct handling of arbitrarily oriented medical images
- Flexible input - works with VTK grid slices or NIfTI/NRRD volumes
- Run with Docker
- Installation
- Quick Start
- Command Line Interface
- Python API
- Method Overview
- Examples
- Performance
- Citation
- Acknowledgments
The quickest way to use CardioScar. You need Docker and one file from this
repository: the run_docker.sh wrapper.
# 1. Get the wrapper script
curl -O https://raw.githubusercontent.com/OpenHeartDevelopers/cardioscar/main/run_docker.sh
chmod +x run_docker.sh
# 2. Pull the image
docker pull cemrg/cardioscar:latestThe first argument is your data directory. It is mounted at /data inside the
container, so every path in the command options must be relative to /data,
not a host path. The wrapper does not rewrite them for you.
# Prepare training data from a mesh and an image
./run_docker.sh /path/to/subject01 prepare \
--mesh-vtk /data/mesh.vtk \
--image /data/lge.nii.gz \
--output /data/training_data.npz
# Train
./run_docker.sh /path/to/subject01 train \
--training-data /data/training_data.npz \
--output /data/model.pth
# Apply the trained model to a mesh
./run_docker.sh /path/to/subject01 apply \
--model /data/model.pth \
--mesh /data/mesh.vtk \
--output /data/mesh_with_scar.vtkAdd --utils before the data directory to reach the utility commands:
# Write VTK slice planes for ParaView overlay
./run_docker.sh --utils /path/to/subject01 image-to-slices \
--image /data/lge.nii.gz \
--output-dir /data/planesThe container runs as your own user, so output files belong to you, not to root.
Images are CPU-only and built for linux/amd64. On Apple Silicon they run under
emulation, which works but is slow; to train on a GPU, install from source below.
Set CARDIOSCAR_IMAGE to use a different tag, for example
CARDIOSCAR_IMAGE=cemrg/cardioscar:0.2 ./run_docker.sh ....
New images are published automatically when a release is published on GitHub.
- Python 3.10 or higher
- CUDA-enabled GPU recommended (CPU supported)
- pycemrg suite dependencies
# 1. Clone repositories
git clone https://github.com/alonsoJASL/cardioscar.git
cd cardioscar
# Clone pycemrg dependencies (if not already installed)
git clone https://github.com/OpenHeartDevelopers/pycemrg.git ../pycemrg
git clone https://github.com/OpenHeartDevelopers/pycemrg-image-analysis.git ../pycemrg-image-analysis
git clone https://github.com/OpenHeartDevelopers/pycemrg-model-creation.git ../pycemrg-model-creation
# 2. Create environment
conda create -n cardioscar python=3.11 -y
conda activate cardioscar
# 3. Install dependencies
pip install -e ../pycemrg
pip install -e ../pycemrg-image-analysis
pip install -e ../pycemrg-model-creation
pip install -e .
# 4. (Optional) Install PyTorch with CUDA support
pip install torch --index-url https://download.pytorch.org/whl/cu118cardioscar --version
python -c "import cardioscar; print('CardioScar installed successfully')"CardioScar provides a unified CLI with four main commands: prepare, train, fine-tune, and apply.
# 1. Prepare training data from NIfTI image
cardioscar prepare \
--mesh-vtk data/lv_mesh.vtk \
--image data/lge_scan.nii.gz \
--output data/training.npz
# 2. Train model
cardioscar train \
--training-data data/training.npz \
--output models/patient_001.pth
# 3. Apply to mesh
cardioscar apply \
--model models/patient_001.pth \
--mesh data/lv_mesh.vtk \
--output results/scar_predictions.vtk \
--mc-samples 20First, prepare all the data you have available
# 1. Build foundation dataset from cohort - this is work in progress
python scripts/build_foundation_dataset.py \
--input-dir prepare_outputs/ \
--output-dir foundation/ \
--train-subsets 11 --finetune-subsets 2 --test-subsets 2 \
--expected-subsets 180
# 2. Train foundation model on cohort
cardioscar train \
--training-data foundation/foundation_training.npz \
--output foundation_model.pth \
--batch-size 500000 \
--max-epochs 3000
# 3. Fine-tune per patient (faster convergence)
cardioscar fine-tune \
--checkpoint foundation_model.pth \
--training-data patient_001_training.npz \
--output patient_001_finetuned.pthFinally, run cardioscar apply on the fine-tuned model.
from pathlib import Path
from cardioscar.logic import (
PreprocessingRequest,
prepare_training_data,
save_preprocessing_result,
TrainingConfig,
train_scar_model,
save_trained_model,
apply_scar_model,
save_inference_result
)
# 1. Prepare data
request = PreprocessingRequest(
mesh_path=Path("data/lv_mesh.vtk"),
image_path=Path("data/lge_scan.nii.gz"),
slice_axis='z'
)
result = prepare_training_data(request)
save_preprocessing_result(result, Path("data/training.npz"))
# 2. Train model
config = TrainingConfig(max_epochs=10000, early_stopping_patience=500)
checkpoint = train_scar_model(Path("data/training.npz"), config)
save_trained_model(checkpoint, Path("models/patient_001.pth"))
# 3. Apply model
inference_result = apply_scar_model(
model_checkpoint_path=Path("models/patient_001.pth"),
mesh_path=Path("data/lv_mesh.vtk"),
mc_samples=20
)
save_inference_result(
inference_result,
Path("data/lv_mesh.vtk"),
Path("results/scar_predictions.vtk")
)Prepare training data by mapping 2D image intensities to 3D mesh nodes.
cardioscar prepare \
--mesh-vtk MESH.vtk \
--image IMAGE.nii.gz \
--output TRAINING.npz \
[--slice-axis {x,y,z}] \
[--slice-indices "2,5,8,11"]cardioscar prepare \
--mesh-vtk MESH.vtk \
--grid-layers SLICE1.vtk [SLICE2.vtk ...] \
--output TRAINING.npz \
[--vtk-scalar-field FIELD_NAME]Key Options:
| Option | Description | Default |
|---|---|---|
--mesh-vtk |
Path to 3D target mesh (VTK) | Required |
--image |
Path to medical image (NIfTI, NRRD) | Required* |
--grid-layers |
Paths to VTK grid files | Required* |
--slice-axis |
Axis to slice along (x/y/z) | z |
--slice-indices |
Comma-separated slice indices | All slices |
--output |
Output path for training data (.npz) | Required |
*Must provide either --image OR --grid-layers
Train Bayesian neural network on prepared data.
cardioscar train \
--training-data TRAINING.npz \
--output MODEL.pth \
[OPTIONS]Example:
# Default settings (recommended)
cardioscar train \
--training-data training_data.npz \
--output model.pth
# Larger architecture (for complex datasets)
cardioscar train \
--training-data training_data.npz \
--output model.pth \
--hidden-size 256 \
--hidden-layers 4Key Options:
| Option | Description | Default |
|---|---|---|
--training-data |
Path to training data (.npz) | Required |
--output |
Output path for trained model (.pth) | Required |
--batch-size |
Target batch size | 10000 |
--max-epochs |
Maximum training epochs | 10000 |
--early-stopping-patience |
Epochs without improvement before stopping | 500 |
--mc-samples |
MC Dropout samples during training | 3 |
--hidden-size |
Neurons per hidden layer | 128 |
--hidden-layers |
Number of hidden layers | 4 |
--cpu |
Force CPU usage | Auto-detect GPU |
Fine-tune a pretrained foundation model on new patient data. Architecture is always restored from the checkpoint — it cannot be overridden.
cardioscar fine-tune \
--checkpoint FOUNDATION.pth \
--training-data PATIENT.npz \
--output FINETUNED.pth \
[OPTIONS]Example:
# Full fine-tune (recommended)
cardioscar fine-tune \
--checkpoint foundation_model.pth \
--training-data patient_001.npz \
--output patient_001_finetuned.pth
# Frozen backbone (experimental - only valid for 4-layer models)
cardioscar fine-tune \
--checkpoint foundation_model.pth \
--training-data patient_001.npz \
--output patient_001_finetuned.pth \
--freeze-stages 2Key Options:
| Option | Description | Default |
|---|---|---|
--checkpoint |
Path to pretrained foundation model (.pth) | Required |
--training-data |
Path to fine-tuning training data (.npz) | Required |
--output |
Output path for fine-tuned model (.pth) | Required |
--freeze-stages |
Stages to freeze (0-4). 0=full fine-tune | 0 |
--batch-size |
Target batch size | 10000 |
--max-epochs |
Maximum fine-tuning epochs | 1000 |
--early-stopping-patience |
Epochs without improvement before stopping | 200 |
--mc-samples |
MC Dropout samples during training | 3 |
--base-lr |
Base learning rate (lower than scratch training) | 1e-4 |
--max-lr |
Maximum learning rate (lower than scratch training) | 1e-3 |
--cpu |
Force CPU usage | Auto-detect GPU |
Freeze stages (valid for default 4-layer architecture only):
| Value | Frozen layers |
|---|---|
| 0 | None (full fine-tune, recommended) |
| 1 | Linear(3→128) + ReLU |
| 2 | + Linear(128→128) + Dropout + ReLU |
| 3 | + Linear(128→128) + ReLU |
| 4 | + Linear(128→128) + Dropout + ReLU |
Apply trained model to predict scar probability on mesh.
cardioscar apply \
--model MODEL.pth \
--mesh MESH.vtk \
--output OUTPUT.vtk \
[OPTIONS]Key Options:
| Option | Description | Default |
|---|---|---|
--model |
Path to trained model (.pth) | Required |
--mesh |
Path to input mesh (VTK) | Required |
--output |
Output path for augmented mesh (.vtk) | Required |
--mc-samples |
MC Dropout samples for uncertainty | 10 |
--threshold |
Optional threshold for binary classification | None |
--batch-size |
Batch size for inference | 50000 |
Output Fields:
The output mesh contains three scalar fields:
scar_probability: Mean scar probability per node [0, 1]scar_uncertainty: Uncertainty (standard deviation) per nodescar_binary: Binary classification (if--thresholdprovided)
For detailed API documentation, see the API Reference.
# Data preparation
from cardioscar.logic import prepare_training_data, PreprocessingRequest
# Training
from cardioscar.logic import train_scar_model, TrainingConfig
# Fine-tuning
from cardioscar.logic import fine_tune_scar_model
from cardioscar.training.config import FineTuneConfig
# Inference
from cardioscar.logic import apply_scar_model
# I/O
from cardioscar.logic import (
save_preprocessing_result,
save_trained_model,
save_inference_result
)from pathlib import Path
from cardioscar.logic import apply_scar_model, save_inference_result
def process_patient_cohort(patient_ids, model_path, output_dir):
"""Process multiple patients with same trained model."""
output_dir = Path(output_dir)
output_dir.mkdir(exist_ok=True)
for patient_id in patient_ids:
print(f"Processing {patient_id}...")
result = apply_scar_model(
model_checkpoint_path=model_path,
mesh_path=Path(f"data/{patient_id}/mesh.vtk"),
mc_samples=10
)
save_inference_result(
result,
Path(f"data/{patient_id}/mesh.vtk"),
output_dir / f"{patient_id}_scar.vtk"
)
print(f" Mean scar: {result.mean_scar_probability:.3f}")
print(f" Uncertainty: {result.mean_uncertainty:.3f}")Input:
- Sparse 2D LGE-CMR slices (typically 8-10mm apart) with scar intensity/segmentation
- Dense 3D left ventricular mesh (thousands of nodes, ~0.5mm resolution)
Output:
- Continuous scar probability at every 3D mesh node
- Per-node uncertainty estimates
-
Spatial Constraint Mapping Each 2D image pixel extends through slice thickness as a rectangular prism. All 3D mesh nodes within this prism form a "group".
-
Physical Constraint The mean prediction across all nodes in a group must equal the observed 2D pixel value.
-
Network Architecture Coordinate-based MLP: (X, Y, Z) → scar probability
- Input: 3D coordinates (normalized to [0, 1])
- Architecture: configurable hidden layers × neurons (default: 4×128, ~50k parameters)
- Dropout: configurable rate for uncertainty estimation (default: 10%)
- Output: Sigmoid activation → [0, 1] probability
-
Loss Function Group-based reconstruction loss:
L = Σ_groups (pixel_value - mean(group_predictions))² -
Optimization
- Adam optimizer with cyclical learning rate (1e-3 to 1e-2)
- Monte Carlo Dropout (3-5 samples during training)
- Complete-group mini-batching (groups never split across batches)
- Early stopping with patience
Traditional mini-batching would split spatial groups across batches, violating the physical constraint. Our implementation:
- Pre-sorts nodes by group ID
- Ensures batches contain only complete groups
- Batch sizes vary slightly to maintain exact constraints
- Fully vectorized via
torch.scatter_add- no Python loops over groups
Monte Carlo Dropout provides:
- Epistemic uncertainty: Model uncertainty about scar location
- High uncertainty regions: Areas where model is less confident (e.g., sparse data, ambiguous boundaries)
- Quality control: Flag regions requiring manual review or additional imaging
For cohort studies, a foundation model can be trained on multiple patients and then fine-tuned per patient:
- Foundation training captures shared cardiac anatomy representations
- Per-patient fine-tuning adapts to individual scar patterns
- Fine-tuning uses lower learning rates (1e-4/1e-3 vs 1e-3/1e-2) to preserve pretrained weights
- Optionally freeze early network stages to retain shared representations
If you use CardioScar in your research, please cite:
@article{SEN2025111219,
title = {Weakly supervised learning for scar reconstruction in personalized cardiac models: Integrating 2D MRI to 3D anatomical models},
journal = {Computers in Biology and Medicine},
volume = {198},
pages = {111219},
year = {2025},
issn = {0010-4825},
doi = {https://doi.org/10.1016/j.compbiomed.2025.111219},
url = {https://www.sciencedirect.com/science/article/pii/S0010482525015720},
author = {Ahmet SEN and Ursula Rohrer and Pranav Bhagirath and Reza Razavi and Mark O'Neill and John Whitaker and Martin Bishop},
keywords = {Myocardial scar segmentation, Late gadolinium-enhanced cardiac MRI, Deep learning-based interpolation, Deep learning for medical imaging, Monte Carlo Dropout}
}@software{cardioscar2024,
title={CardioScar: Deep Learning-Based 3D Myocardial Scar Reconstruction},
author={Sen, Ahmet and Bishop, Martin J. and Solis-Lemus, Jose Alonso},
year={2024},
url={https://github.com/alonsoJASL/cardioscar},
version={0.2.0}
}Original Research: Ahmet Sen, Martin J. Bishop (King's College London) Collaborators: Ursula Rohrer, Pranav Bhagirath, Reza Razavi, Mark O'Neill, John Whitaker Engineering & pycemrg Integration: Jose Alonso Solis-Lemus (Imperial College London)
This work builds upon:
- Original TensorFlow implementation by Ahmet Sen
- pycemrg suite for cardiac image analysis and mesh processing
- SimpleITK for medical image coordinate transforms
MIT License - see LICENSE file for details.
- Documentation: Full API Reference
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: j.solis-lemus [at] imperial.ac.uk
- pycemrg - Core utilities for cardiac image analysis
- pycemrg-model-creation - Mesh processing and UVC coordinates
- pycemrg-image-analysis - Medical image preprocessing
- pycemrg-interpolation - Volumetric super-resolution
Version: 0.2.0 Last Updated: March 2026