Skip to content

Repository files navigation

CardioScar

Deep learning-based 3D myocardial scar reconstruction from sparse 2D Late Gadolinium-Enhanced Cardiac MRI (LGE-CMR).

Python 3.10+ PyTorch License: MIT

Overview

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

Table of Contents


Run with Docker

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:latest

The 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.vtk

Add --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/planes

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


Installation

Prerequisites

  • Python 3.10 or higher
  • CUDA-enabled GPU recommended (CPU supported)
  • pycemrg suite dependencies

Setup

# 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/cu118

Verify Installation

cardioscar --version
python -c "import cardioscar; print('CardioScar installed successfully')"

Quick Start

CardioScar provides a unified CLI with four main commands: prepare, train, fine-tune, and apply.

Complete Workflow (CLI)

# 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 20

Transfer learning + Fine-Tune Workflow

First, 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.pth

Finally, run cardioscar apply on the fine-tuned model.

Python API

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

Command Line Interface

cardioscar prepare

Prepare training data by mapping 2D image intensities to 3D mesh nodes.

From NIfTI/NRRD Images (Recommended)

cardioscar prepare \
    --mesh-vtk MESH.vtk \
    --image IMAGE.nii.gz \
    --output TRAINING.npz \
    [--slice-axis {x,y,z}] \
    [--slice-indices "2,5,8,11"]

From VTK Grid Slices (Legacy)

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


cardioscar train

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 4

Key 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

cardioscar fine-tune

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 2

Key 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

cardioscar apply

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 node
  • scar_binary: Binary classification (if --threshold provided)

Python API

For detailed API documentation, see the API Reference.

Core Functions

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

Example: Batch Processing

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}")

Method Overview

Problem Statement

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

Approach

  1. 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".

  2. Physical Constraint The mean prediction across all nodes in a group must equal the observed 2D pixel value.

  3. 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
  4. Loss Function Group-based reconstruction loss:

    L = Σ_groups (pixel_value - mean(group_predictions))²
    
  5. 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

Key Innovation: Complete-Group Batching

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

Bayesian Uncertainty Quantification

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

Transfer Learning Model + Fine-Tuning

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

Citation

If you use CardioScar in your research, please cite:

Original Research

@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

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

Acknowledgments

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

License

MIT License - see LICENSE file for details.


Support & Contributing


Related Projects


Version: 0.2.0 Last Updated: March 2026

About

Building/reconstructing scar via constrained interpolation innovation

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages