MesserMMP's picture
Update README.md
8fb4298 verified
|
Raw
History Blame Contribute Delete
20.6 kB
# 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