CardioSyntax v2: SYNTAX Score Prediction from Coronary Angiography
This repository contains code and pre-trained models for automated SYNTAX score prediction from coronary angiography videos. The SYNTAX score is a validated metric for quantifying the anatomical complexity of coronary artery disease.
Overview
CardioSyntax v2 is a large-scale dataset of 11,410 angiography studies with SYNTAX scores, designed for developing machine learning models for automated coronary lesion complexity assessment. This repository provides:
- Dataset: 11,209 training studies (Philips Allura Clarity + Azurion 7) + 120 test studies (100 Philips + 20 Siemens) annotated by 6 independent interventional cardiologists
- Baseline Models:
- Backbone network (R3D-18): single projection β SYNTAX contribution
- RNN head (LSTM/GRU/Transformer): multi-view aggregation β final SYNTAX score
- Pre-trained Weights: 5-fold ensemble with post-calibration
- Evaluation: Pearson correlation r β 0.81, balanced accuracy β 82% for SYNTAX > 22 classification
The dataset addresses inter-observer variability through multi-expert annotation and enables systematic investigation of domain shift effects across imaging systems.
Repository Structure
coronary-syntax-prediction/
βββ backbone/ # Single-frame backbone (3D ResNet)
β βββ dataset.py # SyntaxDataset for single-view training
β βββ pl_model.py # SyntaxLightningModule (Lightning wrapper)
β βββ pl_train.py # Training script for backbone
β βββ __init__.py
βββ full_model/ # Multi-view RNN head + backbone
β βββ rnn_dataset.py # SyntaxDataset for multi-view (RNN) training
β βββ rnn_model.py # SyntaxLightningModule with RNN variants
β βββ rnn_train.py # Training script for full model
β βββ __init__.py
βββ inference/ # Inference & evaluation
β βββ rnn_apply.py # Ensemble inference script
β βββ metrics_visualization.py # Plotly-based metrics & visualization
β βββ __init__.py
βββ backbone_weights/ # Backbone .pt checkpoints (5 folds Γ 2 arteries)
βββ full_model_weights/ # Full model .pt checkpoints (5 folds Γ 2 arteries Γ variants)
βββ scaling_coeffs/ # Calibration coefficients (a, b) per fold
βββ requirements.txt
βββ README.md
Installation
# Clone the repository
git clone https://huggingface.co/MesserMMP/coronary-syntax-prediction
cd coronary-syntax-prediction
# Install dependencies
pip install -r requirements.txt
Requirements
Key packages:
- PyTorch 2.0+ with CUDA support
- Lightning 2.x
- torchvision, pytorchvideo
- numpy, scikit-learn, pandas
- plotly for visualization
- click for CLI
See requirements.txt for full list.
Training
Stage 1: Backbone Training (Single-View)
Train 3D ResNet-18 on single angiographic projections (left/right coronary artery separately):
python backbone/pl_train.py \
-r /path/to/dataset_root \
--fold 0 \
-a right \
--num-classes 2 \
-b 50 \
-f 32 \
-v 256 256 \
--max-epochs 10 \
--num-workers 8 \
--devices 0 \
--precision bf16-mixed \
--logdir ./logs/backbone \
--use-weighted-sampler
Parameters:
-r, --dataset-root: Path to dataset root (default:.)--fold: Fold number (0-4, default: 4)-a, --artery:leftorright(default:right)-nc, --num-classes: Output units: 2 for (classification, regression) (default: 2)-b, --batch-size: Batch size (default: 50)-f, --frames-per-clip: Frames per clip (default: 32)-v, --video-size: Frame resolution H W (default: 256 256)--max-epochs: Epochs for full training (default: 10)--num-workers: DataLoader workers (default: 8)--devices: GPU device IDs (default: )--precision: Training precision mode (default:bf16-mixed)--logdir: Log directory (default:./logs/backbone)--use-weighted-sampler: Balance classes by score intervals (flag)--seed: Random seed (default: 42)
Output: TensorBoard logs + .ckpt checkpoints in --logdir
Stage 2: RNN Head Training (Multi-View)
Train LSTM/GRU/Transformer aggregation head on multi-view studies:
python full_model/rnn_train.py \
-r /path/to/dataset_root \
--fold 0 \
-a right \
--variant lstm_mean \
--num-classes 2 \
-b 8 \
-f 32 \
-v 256 256 \
--max-epochs 15 \
--num-workers 16 \
--devices 0 \
--precision bf16-mixed \
--logdir ./logs/rnn \
--backbone-pt-dir backbone_weights \
--backbone-from-pt \
--rnn-folds-dir rnn_folds
Key Parameters:
--variant: Head architecture:mean_out: Mean of projection scores (no RNN)mean: MLP on mean pooled backbone featureslstm_mean: LSTM with mean pooling of hidden stateslstm_last: LSTM, use last hidden stategru_mean,gru_last: GRU variantsbert_mean,bert_cls,bert_cls2: Transformer encoder variants- (default:
lstm_mean)
--backbone-pt-dir: Path to .pt backbone weights (default:backbone_weights)--backbone-from-pt: Load backbone from.ptfiles (flag, default: True)--backbone-logdir: Alternative: load backbone from Lightning logs--rnn-folds-dir: Directory withrnn_fold{fold}_train.json,rnn_fold{fold}_eval.json(default:rnn_folds)--use-weighted-sampler: Balance by score (flag)--pt-weights-format: If loading pre-trained head, expect.ptinstead of.ckpt(flag)
Output: Full model checkpoints in --logdir/{artery}BinSyntax_R3D_fold{fold:02d}_{variant}_post/...
Inference
Run ensemble inference on test datasets:
python inference/rnn_apply.py \
-d "test_philips_100.json" "test_siemens_20.json" \
-n "Philips Test" "Siemens Test" \
-p "philips_100" "siemens_20" \
-r /path/to/dataset_root \
--model-dir full_model_weights \
-v 256 256 \
--frames-per-clip 32 \
--num-workers 8 \
--variant lstm_mean \
--pt-weights-format \
--use-scaling \
--scaling-file scaling_coeffs.json \
-e "Ensemble_lstm_mean" \
-m metrics.json
Parameters:
-d, --dataset-paths: Multiple JSON test dataset paths (relative to--dataset-root)-n, --dataset-names: Display names for each dataset (must match-dcount)-p, --postfixes: Suffixes for result files (must match-dcount)-r, --dataset-root: Dataset root (default:.)--model-dir: Directory with full model.ptweights (default:full_model_weights)-v, --video-size: Frame resolution H W (default: 256 256)--frames-per-clip: Frames per clip (default: 32)--num-workers: DataLoader workers (default: 8)--variant: Head model variant (default:lstm_mean)--pt-weights-format: Model weights are.ptraw state_dict (flag, default: True)--use-scaling: Apply post-calibration scaling a*x+b from JSON (flag)--scaling-file: Calibration coefficients file relative to--dataset-root-e, --ensemble-name: Experiment name for metrics (e.g., "Ensemble_lstm_mean")-m, --metrics-file: JSON output file path for all metrics--seed: Random seed (default: 42)
Output:
- Per-dataset predictions:
results/{postfix}.json - Ensemble metrics:
{metrics_file}with Pearson correlation, balanced accuracy, per-fold statistics - Plotly HTML plots:
visualizations/{postfix}.html
Dataset
Link: https://huggingface.co/datasets/MesserMMP/coronary-angiography-syntax
Data Format
The dataset provides two different JSON annotation formats depending on the training stage:
Format 1: Backbone Training (Single-View/Per-Video)
Used for training the backbone model (backbone/ scripts). Each record represents one angiographic video projection.
File structure: folds/fold{fold_id}_{split}.json
[
{
"study_uid": "1.3.46.670589.28.26690171363123020190823114413193175",
"series_uid": "1.3.46.670589.28.26690171363123020190823115102913895.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158145991852211512",
"path": "../anon_data/chunk1/.../IM-2254-0039.dcm",
"shape": [38, 512, 512],
"artery": 0,
"artery_prob": 1.9078343029832467e-05,
"syntax": 0.0,
"syntax_left": 0.0,
"syntax_right": 0.0,
"bin_syntax": 0,
"manufacturer": "Philips",
"device_model": "Allura Clarity"
},
{
"study_uid": "1.3.46.670589.28.26690171363123020190823114413193175",
"series_uid": "1.3.46.670589.28.26690171363123020190823115648342907.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158148282212211512",
"path": "../anon_data/chunk1/.../IM-2260-0022.dcm",
"shape": [15, 512, 512],
"artery": 1,
"artery_prob": 0.9559882879257202,
"syntax": 0.0,
"syntax_left": 0.0,
"syntax_right": 0.0,
"bin_syntax": 0,
"manufacturer": "Philips",
"device_model": "Allura Clarity"
}
]
Field descriptions:
| Field | Type | Description |
|---|---|---|
study_uid |
str | Unique study identifier (DICOM) |
series_uid |
str | Unique series identifier (DICOM) |
sop_uid |
str | Unique SOP instance UID (DICOM) |
path |
str | Relative path to DICOM video file |
shape |
list[int] | Video dimensions: [T, H, W] (frames, height, width) |
artery |
int | Coronary artery: 0 = left (LCA), 1 = right (RCA) |
artery_prob |
float | Model confidence for artery classification (0β1) |
syntax |
float | Total SYNTAX score (LCA + RCA) |
syntax_left |
float | SYNTAX score for left coronary artery |
syntax_right |
float | SYNTAX score for right coronary artery |
bin_syntax |
int | Binary classification: 0 = SYNTAX < threshold, 1 = SYNTAX β₯ threshold |
manufacturer |
str | Imaging equipment manufacturer (e.g., "Philips", "Siemens") |
device_model |
str | Equipment model (e.g., "Allura Clarity", "AXIOM-Artis") |
Format 2: RNN Head Training (Multi-View/Per-Patient)
Used for training the full RNN model (full_model/ scripts). Each record represents one patient study with all angiographic projections grouped by artery.
File structure: rnn_folds/rnn_fold{fold_id}_{split}.json
[
{
"study_uid": "1.3.46.670589.28.26690171363123020190823114413193175",
"manufacturer": "Philips",
"device_model": "Allura Clarity",
"syntax": 0.0,
"syntax_left": 0.0,
"syntax_right": 0.0,
"bin_syntax": 0,
"videos": [
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115102913895.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158145991852211512",
"path": "../anon_data/chunk1/.../IM-2254-0039.dcm",
"shape": [38, 512, 512],
"artery": 0,
"artery_prob": 1.9078343029832467e-05
},
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115648342907.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158148282212211512",
"path": "../anon_data/chunk1/.../IM-2260-0022.dcm",
"shape": [15, 512, 512],
"artery": 1,
"artery_prob": 0.9559882879257202
}
],
"videos_left": [
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115102913895.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158145991852211512",
"path": "../anon_data/chunk1/.../IM-2254-0039.dcm",
"shape": [38, 512, 512],
"artery": 0,
"artery_prob": 1.9078343029832467e-05
},
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115533239902.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158147562092211512",
"path": "../anon_data/chunk1/.../IM-2258-0055.dcm",
"shape": [50, 512, 512],
"artery": 0,
"artery_prob": 0.0003698925720527768
},
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115052517893.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158145581792211512",
"path": "../anon_data/chunk1/.../IM-2253-0047.dcm",
"shape": [47, 512, 512],
"artery": 0,
"artery_prob": 0.00018035581160802394
},
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115550658904.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158147942152211512",
"path": "../anon_data/chunk1/.../IM-2259-0047.dcm",
"shape": [41, 512, 512],
"artery": 0,
"artery_prob": 0.00022948876721784472
},
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115523054900.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158147162032211512",
"path": "../anon_data/chunk1/.../IM-2257-0047.dcm",
"shape": [43, 512, 512],
"artery": 0,
"artery_prob": 0.00034276593942195177
}
],
"videos_right": [
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115648342907.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158148282212211512",
"path": "../anon_data/chunk1/.../IM-2260-0022.dcm",
"shape": [15, 512, 512],
"artery": 1,
"artery_prob": 0.9559882879257202
},
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115657547909.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158149262272211512",
"path": "../anon_data/chunk1/.../IM-2261-0070.dcm",
"shape": [62, 512, 512],
"artery": 1,
"artery_prob": 0.9999938011169434
},
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115712699912.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158149652332211512",
"path": "../anon_data/chunk1/.../IM-2262-0063.dcm",
"shape": [54, 512, 512],
"artery": 1,
"artery_prob": 0.999855637550354
}
],
"videos_other": [
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115128658897.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158146391912211512",
"path": "../anon_data/chunk1/.../IM-2255-0035.dcm",
"shape": [33, 512, 512],
"artery": 0,
"artery_prob": 0.2866898477077484
},
{
"series_uid": "1.3.46.670589.28.26690171363123020190823115132067898.2.2",
"sop_uid": "1.3.46.670589.28.266901713631230201908231158146771972211512",
"path": "../anon_data/chunk1/.../IM-2256-0035.dcm",
"shape": [32, 512, 512],
"artery": 0,
"artery_prob": 0.3251875042915344
}
]
}
]
Field descriptions:
| Field | Type | Description |
|---|---|---|
study_uid |
str | Unique study identifier (same across all videos for this patient) |
manufacturer |
str | Imaging equipment manufacturer |
device_model |
str | Equipment model |
syntax |
float | Total SYNTAX score (LCA + RCA) |
syntax_left |
float | SYNTAX score for left coronary artery |
syntax_right |
float | SYNTAX score for right coronary artery |
bin_syntax |
int | Binary classification label |
videos |
list[object] | All video projections (unfiltered) |
videos_left |
list[object] | Projections classified as left coronary (artery=0, high confidence) |
videos_right |
list[object] | Projections classified as right coronary (artery=1, high confidence) |
videos_other |
list[object] | Low-confidence projections (excluded from training); typically ~5β7% of total |
Video object fields:
| Field | Type | Description |
|---|---|---|
series_uid |
str | Unique series identifier (DICOM) |
sop_uid |
str | Unique SOP instance UID (DICOM) |
path |
str | Relative path to DICOM video file |
shape |
list[int] | Video dimensions: [T, H, W] |
artery |
int | Artery classification: 0 = left, 1 = right |
artery_prob |
float | Model confidence (0β1); threshold typically 0.5 for videos_left/videos_right inclusion |
Test Set Format (Multi-Expert Annotations)
Test data (test_philips_100.json, test_siemens_20.json) follows Format 2 with additional expert annotations:
[
{
"study_uid": "...",
"manufacturer": "Philips",
"device_model": "Allura Clarity",
"syntax": 18.5,
"syntax_left": 10.0,
"syntax_right": 8.5,
"mean_syntax": 18.5,
"expert0": 18,
"expert1": 19,
"expert2": 17,
"expert3": 19,
"expert4": 20,
"expert5": 18,
"videos_left": [...],
"videos_right": [...],
"videos_other": [...]
}
]
Additional fields for test data:
| Field | Type | Description |
|---|---|---|
mean_syntax |
float | Consensus SYNTAX score (mean of 6 experts) |
expert0βexpert5 |
float | Individual expert SYNTAX score (6 independent cardiologists) |
Note: expert0 typically represents aggregated/consensus annotations, while expert1βexpert5 are individual readers.
DICOM Video Format
Each DICOM file contains:
- Dimensions: [T, H, W] = [frames, 512, 512] at 15 fps
- Data type:
uint8(8-bit grayscale) - Frame count: Typically 15β60 frames per projection
- Resolution: 512Γ512 pixels (standard for X-ray angiography)
Data Splits
| Set | Files | Size | Equipment | Artery Classification |
|---|---|---|---|---|
| Backbone training | folds/fold{0-4}_train.json |
~8,000 videos | Philips | Single video |
| Backbone validation | folds/fold{0-4}_eval.json |
~1,000 videos | Philips | Single video |
| RNN training | rnn_folds/rnn_fold{0-4}_train.json |
~2,200 studies | Philips | Multi-view (patient) |
| RNN validation | rnn_folds/rnn_fold{0-4}_eval.json |
~290 studies | Philips | Multi-view (patient) |
| Test 1 (Philips) | test_philips_100.json |
100 studies | Philips Allura Clarity | 6 experts |
| Test 2 (Siemens) | test_siemens_20.json |
20 studies | Siemens AXIOM-Artis | 6 experts |
SYNTAX > 0 prevalence: 39.26% in training data
Results
Baseline Model Performance (5-Fold Ensemble)
| Metric | Test Set (Philips) | Test Set (Siemens) | Description |
|---|---|---|---|
| Pearson r | 0.814 | 0.482 | Raw predictions |
| Balanced Accuracy | 0.687 | 0.566 | Pre-calibration, SYNTAX > 22 |
| Post-Calibration | |||
| Pearson r | 0.816 | 0.482 | After scaling |
| Balanced Accuracy | 0.825 | 0.643 | After scaling, SYNTAX > 22 |
Key findings:
- Strong performance on in-distribution (Philips) test data
- Domain shift effect on out-of-distribution (Siemens) data
- Calibration effective for Philips, limited benefit for Siemens
- Inter-observer variability constrains upper bound on accuracy
Model Architecture
- Backbone: R3D-18 (video ResNet) pre-trained on Kinetics-400
- Head (default): LSTM with mean-pooling aggregation + linear regressor
- Output: 2 channels (classification logit + log-transformed regression)
- Loss: Weighted BCE (classification) + scaled MSE (regression)
Weights & Checkpoints
Pre-trained weights available in repository:
backbone_weights/:.ptfiles for 5 folds Γ 2 arteries- Example:
RightBinSyntax_R3D_full_fold00.pt
- Example:
full_model_weights/:.ptfiles for ensemble variants- Example:
RightBinSyntax_R3D_fold00_lstm_mean_post_best.pt
- Example:
scaling_coeffs/: Calibration parameters (a, b) per fold
Citation
If you use this dataset or code, please cite:
@dataset{cardiosyntax_v2_2025,
title={CardioSyntax v2: Angiographic dataset for SYNTAX score estimation},
author={MesserMMP and collaborators},
year={2025},
url={https://huggingface.co/datasets/MesserMMP/coronary-angiography-syntax}
}
Related Work
- SYNTAX Score Definition: Sianos et al., EuroIntervention (2005)
- Original CardioSyntax: Ponomarchuk et al., arXiv:2407.19894 (2024)
- Video Understanding: Hara et al., CVPR (2018) β R3D architecture
License
CC0 1.0 (Public Domain)
Contact & Support
For issues, questions, or contributions, please refer to the HuggingFace repository discussions.
Last updated: January 2026