Skip to content
R4nzerPublic

About

Reference implementation of TRACE: Tractable Routing Autoencoder for Clinical ECG — ontology-partitioned latent space with hard routing, reproducible on PTB-XL, Georgia and CPSC2018

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TRACE: Tractable Routing Autoencoder for Clinical ECG


TRACE is a 12-lead ECG autoencoder whose 32-dimensional clinical latent space is partitioned by a literature-derived diagnostic ontology (perfusion / structure / conduction), with each subspace routed by design to its own diagnostic head. On the paper's three cohorts it reaches 91.97 macro AUC / 72.59 macro F1 on PTB-XL, 87.31 / 67.40 on Georgia and 89.34 / 64.54 on CPSC2018, and it stores 3.93M parameters including the reconstruction pathway (1.87M in the path that produces a diagnosis). This repository is the reference tree that produced those numbers for the paper TRACE: Tractable Routing Autoencoder for Clinical ECG.

🔄 Pipeline overview

TRACE architecture

The TRACE architecture: residual 1D backbone, system inference network, ontology-partitioned clinical latent with its routed heads, and the reconstruction pathway.

TRACE implementation stack

The concrete layer stack behind each stage above, with detail panels for the residual block, the backbone, one diagnostic head and the upsampling stack.

📊 Key results

TRACE across cohorts

Cohort Macro AUC Macro F1 Labels
PTB-XL 91.97 ± 0.05 72.59 ± 0.13 NORM, MI, STTC, CD, HYP
Georgia 87.31 ± 0.09 67.40 ± 1.60 NORM, MI, STTC, CD, HYP
CPSC2018 89.34 ± 0.43 64.54 ± 2.41 NORM, AF, I-AVB, LBBB, RBBB, PAC, PVC, STD, STE

Class-wise results

Per-class AUC and macro F1 for TRACE across the three cohorts.

PTB-XL comparison

Macro AUC / macro F1 in percent, sorted by AUC.

Model Macro AUC Macro F1
TRACE (ours) 91.97 72.59
Inception1D 91.81 71.89
ECGFounder (frozen probe) 91.43 70.06
ResNet1D 91.41 72.22
FCN 91.13 71.88
ConceptBottleneck 90.89 72.02
Transformer1D 90.20 70.25
HeartLang (frozen probe) 89.58 65.59
1D-MAE 89.43 68.90
1D-SimCLR 88.79 67.69

The two foundation models are also probed on the other cohorts: ECGFounder 86.38 / 65.42 on Georgia and 95.75 / 77.67 on CPSC2018; HeartLang 80.74 / 55.06 and 88.36 / 57.32.

Model sizes

Component Parameters
TRACE, as stored 3.93M
— diagnostic path 1.87M
— reconstruction path 2.06M
ECGFounder 30.8M
HeartLang (ST-ECGFormer) 44.6M

The reconstruction path does not change any prediction.

Evidence for the routing contract

  • Linear probes on the latent subspaces separate the diagonal (subspace to its own head) from the off-diagonal pairing.
  • Targeted erasure drives the erased subspace's own head to chance (macro AUC 50.00) while the non-target heads hold their baseline values.
  • Capacity-matched permutation control holds every head at 8 input dimensions, so routing and capacity are decoupled.
  • Mechanism-layer zeroing of the depolarization and repolarization pathways, with R-peak-anchored QRS and ST-T windows, moves the reconstruction without moving the diagnosis.

Linear probes

Linear probes on the latent subspaces: diagonal pairings against their off-diagonal counterparts.

Ablations

Ablations: unstructured latent, loss components, capacity-matched permutation control, latent dimensions, orthogonality weight and reconstruction fidelity.

Reconstruction overlays, sample index 42

Reconstruction overlays, sample index 100

Reconstruction overlays for two PTB-XL test recordings over the 10-second segment: original in blue and reconstruction in red for all 12 leads, two per row. Top: sample index 42; bottom: sample index 100.

📚 Data

Three public 12-lead ECG cohorts, all distributed by PhysioNet under CC BY 4.0. None of them is bundled with this repository, and data/ is git-ignored.

Cohort PhysioNet record Version Records Role
PTB-XL ptb-xl 1.0.3 20,008 label-filtered Main cohort, 5 superclasses
Georgia challenge-2020 group georgia 1.0.2 10,344 Cross-cohort evaluation, same 5 superclasses
CPSC2018 challenge-2020 group cpsc_2018 1.0.2 6,877 Rhythm-dominant 9-label cohort

Dataset overview

Split sizes, PTB-XL superclass prevalence, and Georgia and CPSC2018 label prevalence.

Expected layout. --data-root defaults to ./data; each loader reads its own subdirectory.

data/
├── ptb-xl/                 # dataset ptbxl
│   ├── ptbxl_database.csv  # metadata + scp_codes labels
│   └── records500/         # WFDB records named by the filename_hr column
├── georgia/                # dataset georgia; any directory holding the records
│   ├── <record>.hea        # header carrying the "# Dx:" SNOMED-CT line
│   └── <record>.mat        # 12-lead signal
└── CPSC2018/               # dataset cpsc
    ├── REFERENCE.csv       # Recording, First_label, Second_label, Third_label
    └── ecg_signals/        # <record>.mat or <record>.npy; falls back to the root

🖥️ Environment

  • Python 3.12, PyTorch 2.5.1, CUDA 12.4, Ubuntu 22.04, one NVIDIA RTX 4090 (24 GB). Seeds are 42 / 2025 / 2026 throughout.
  • pip install -r requirements.txt installs the runtime dependencies (torch, numpy, scipy, pandas, scikit-learn, scikit-multilearn, wfdb, matplotlib, tqdm).
  • A GPU is needed for training; evaluation, the analyses that read trained weights and every figure script run on CPU.

🚀 Quick Start

Every command runs from the repository root, and the steps are a chain: each one reads what the previous steps wrote. Steps 1 and 2 apply to every cohort; steps 3 to 9 only need the cohort whose numbers you want.

1. Install

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

2. Download the cohorts

# PTB-XL 1.0.3
wget -r -N -c -np https://physionet.org/files/ptb-xl/1.0.3/

# Challenge 2020 v1.0.2, which carries both the Georgia and the CPSC2018 groups
wget -r -N -c -np https://physionet.org/files/challenge-2020/1.0.2/

Arrange them as described under Data above, then set --data-root accordingly on every later command. Everything downstream writes under --output-root (./outputs by default) and reads it back.

3. Build the PTB-XL cache

Every PTB-XL analysis and ablation below reads preprocessed signals from <output-root>/ptbxl/ptbxl_cache/{train,val,test}.npz. That cache is a required input: prepare_ptbxl_cache is its only producer, and no consumer decodes the records on the fly when it is missing.

python src/prepare_ptbxl_cache.py --data-root ./data --output-root ./outputs

It decodes the official folds through trace.data.PTBXLDataset and writes X, y and mask per split; expect a few minutes. Re-run with --force to rebuild a split that already exists. Skip this step only if you run nothing that reads PTB-XL test signals (steps 4, 5, 9 and 10).

4. Train TRACE

python src/train.py --dataset ptbxl --data-root ./data --output-root ./outputs
python src/train.py --dataset georgia --data-root ./data --output-root ./outputs
python src/train.py --dataset cpsc --data-root ./data --output-root ./outputs

5. Evaluate TRACE

python src/evaluate.py --dataset ptbxl --data-root ./data --output-root ./outputs
python src/evaluate.py --dataset georgia --data-root ./data --output-root ./outputs
python src/evaluate.py --dataset cpsc --data-root ./data --output-root ./outputs

6. Train the baselines

python src/baselines/train_baselines.py --dataset ptbxl --data-root ./data --output-root ./outputs

7. Evaluate the baselines

python src/baselines/eval_baselines.py --dataset ptbxl --data-root ./data --output-root ./outputs

8. Foundation-model probes

python src/foundation/ecgfounder_probe.py --dataset ptbxl --data-root ./data \
    --ecgfounder-root <path-to-ecgfounder> --ckpt <path-to-ecgfounder-checkpoint>

python src/foundation/heartlang_probe.py --dataset ptbxl --data-root ./data \
    --heartlang-root <path-to-heartlang> \
    --ckpt <path-to-heartlang>/checkpoints/pretrain/MIMIC-IV/checkpoint-200.pth

9. Analyses

# routing contract: linear probes and targeted subspace erasure
python src/analyses/probes.py --dataset ptbxl --data-root ./data --output-root ./outputs
python src/analyses/erasure.py --dataset ptbxl --data-root ./data --output-root ./outputs

# mechanism layer: zeroing the depolarization and repolarization pathways
python src/analyses/perturbation.py --dataset ptbxl --data-root ./data --output-root ./outputs

# robustness
python src/analyses/robustness.py --dataset ptbxl --data-root ./data --output-root ./outputs \
    --models TRACE FCN ResNet1D Inception1D Transformer1D ConceptBottleneck 1D-MAE 1D-SimCLR

# robustness of the two frozen-feature foundation probes
python src/analyses/foundation_robustness.py --dataset ptbxl --data-root ./data \
    --models ECGFounder --ecgfounder-root /path/to/ECGFounder-main \
    --ecgfounder-ckpt /path/to/12_lead_ECGFounder.pth \
    --split-seed 42 --seeds 42 2025 2026 --out foundation_rob_ptbxl.csv

# reconstruction fidelity and the overlay export
python src/analyses/reconstruction.py --dataset ptbxl --output-root ./outputs

# parameter counts for the model-size table, the only analysis that needs no trained weights
python src/analyses/count_params.py --dataset ptbxl --output-root ./outputs

10. Ablations

# unstructured latent
python src/ablations/train_unstructured.py --dataset ptbxl --data-root ./data --output-root ./outputs
python src/ablations/train_unstructured.py --dataset georgia --data-root ./data --output-root ./outputs
python src/ablations/eval_unstructured.py --dataset ptbxl --data-root ./data --output-root ./outputs

# loss components
python src/ablations/component_ablation.py --dataset ptbxl --data-root ./data --output-root ./outputs

# capacity-matched permutation control
python src/ablations/train_permutation.py --dataset ptbxl --data-root ./data --output-root ./outputs --assignment true
python src/ablations/train_permutation.py --dataset ptbxl --data-root ./data --output-root ./outputs --assignment A
python src/ablations/train_permutation.py --dataset ptbxl --data-root ./data --output-root ./outputs --assignment B
python src/ablations/train_permutation.py --dataset ptbxl --data-root ./data --output-root ./outputs --assignment C
python src/ablations/eval_permutation.py --dataset ptbxl --data-root ./data --output-root ./outputs
# all four assignments must be trained first; eval_permutation evaluates the four by
# default and silently skips any whose checkpoint is missing

# latent dimensions
python src/ablations/dim_sweep.py --dataset ptbxl --data-root ./data --output-root ./outputs

# orthogonality weight
python src/ablations/lambda_sweep.py --dataset ptbxl --data-root ./data --output-root ./outputs

11. Figures

python figures/georgia_stats.py        # needs the Georgia .hea headers; writes figures/source/georgia_stats.csv
python figures/make_dataset_figure.py  # dataset figure
python figures/make_classwise_group.py # class-wise figure
python figures/make_probe_2x2.py       # probing figure
python figures/make_ablation_group.py  # ablation figure
python figures/plot_recon_overlay.py   # reconstruction-overlay figure

📁 Repository structure

├── src/
│   ├── trace/                          Library: models.py, baselines.py,
│   │                                   data.py, metrics.py, config.py
│   ├── prepare_ptbxl_cache.py          The only producer of ptbxl_cache/; every
│   │                                   PTB-XL consumer below requires that cache
│   ├── train.py                        Train TRACE on PTB-XL, Georgia or CPSC2018
│   ├── evaluate.py                     Main-table row, per-class thresholds, class-wise table
│   ├── baselines/
│   │   ├── train_baselines.py          FCN, ResNet1D, Inception1D, Transformer1D,
│   │   │                               ConceptBottleneck, 1D-MAE, 1D-SimCLR
│   │   └── eval_baselines.py           Baseline rows of the main and robustness tables
│   ├── ablations/
│   │   ├── train_unstructured.py       Unstructured-latent ablation
│   │   ├── eval_unstructured.py        Its evaluation
│   │   ├── component_ablation.py       Loss-component ablation
│   │   ├── train_permutation.py        Capacity-matched permutation control
│   │   ├── eval_permutation.py         Fold-10 optimal-threshold evaluation of the control
│   │   ├── dim_sweep.py                Latent-dimension sweep
│   │   └── lambda_sweep.py             Orthogonality-weight sweep
│   ├── analyses/
│   │   ├── probes.py                   Linear probes on the latent subspaces
│   │   ├── erasure.py                  Targeted subspace erasure
│   │   ├── perturbation.py             Mechanism-layer pathway zeroing
│   │   ├── robustness.py               Four-condition robustness evaluation
│   │   ├── foundation_robustness.py    The same four conditions for the frozen probes
│   │   ├── reconstruction.py           Reconstruction fidelity and overlay export
│   │   └── count_params.py             Parameter counts
│   └── foundation/
│       ├── ecgfounder_probe.py         ECGFounder frozen-feature linear probe
│       └── heartlang_probe.py          HeartLang frozen-feature linear probe
├── figures/
│   ├── architecture.png                Architecture figure, drawn by the authors
│   ├── module_stack.png                Implementation-stack figure, drawn by the authors
│   ├── georgia_stats.py  make_dataset_figure.py  make_classwise_group.py
│   ├── make_probe_2x2.py  make_ablation_group.py  plot_recon_overlay.py
│   ├── source/                         Reference CSV tables the figure scripts read
│   └── output/                         Figures generated by the scripts above
├── requirements.txt
├── LICENSE
└── README.md

⚖️ License

MIT — see LICENSE. The datasets keep their own licences: PTB-XL and Challenge 2020 v1.0.2 each state CC BY 4.0, which requires attribution when the data are reused, so cite the dataset records alongside this repository. The third-party foundation-model weights keep the terms of their own releases.

About

Reference implementation of TRACE: Tractable Routing Autoencoder for Clinical ECG — ontology-partitioned latent space with hard routing, reproducible on PTB-XL, Georgia and CPSC2018

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages