| # 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 |
|
|
| ```bash |
| # 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): |
|
|
| ```bash |
| 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`: `left` or `right` (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: |
|
|
| ```bash |
| 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 features |
| - `lstm_mean`: LSTM with mean pooling of hidden states |
| - `lstm_last`: LSTM, use last hidden state |
| - `gru_mean`, `gru_last`: GRU variants |
| - `bert_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 `.pt` files (flag, default: True) |
| - `--backbone-logdir`: Alternative: load backbone from Lightning logs |
| - `--rnn-folds-dir`: Directory with `rnn_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 `.pt` instead of `.ckpt` (flag) |
|
|
| **Output:** Full model checkpoints in `--logdir/{artery}BinSyntax_R3D_fold{fold:02d}_{variant}_post/...` |
|
|
| ## Inference |
|
|
| Run ensemble inference on test datasets: |
|
|
| ```bash |
| 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 `-d` count) |
| - `-p, --postfixes`: Suffixes for result files (must match `-d` count) |
| - `-r, --dataset-root`: Dataset root (default: `.`) |
| - `--model-dir`: Directory with full model `.pt` weights (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 `.pt` raw 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](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` |
|
|
| ```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` |
| |
| ```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: |
|
|
| ```json |
| [ |
| { |
| "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/`: `.pt` files for 5 folds × 2 arteries |
| - Example: `RightBinSyntax_R3D_full_fold00.pt` |
| - `full_model_weights/`: `.pt` files for ensemble variants |
| - Example: `RightBinSyntax_R3D_fold00_lstm_mean_post_best.pt` |
| - `scaling_coeffs/`: Calibration parameters (a, b) per fold |
|
|
| ## Citation |
|
|
| If you use this dataset or code, please cite: |
|
|
| ```bibtex |
| @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](https://huggingface.co/MesserMMP/coronary-syntax-prediction/discussions). |
|
|
| *** |
| |
| **Last updated:** January 2026 |