How ai4healthuol/ecg-fm-benchmarking works
This repository is a benchmarking and training framework for ECG-centric time-series foundation models, with some support for EEG and movement signals. The architecture is organized around a shared clinical time-series core, plus dataset-specific configuration and model wrappers for evaluating many pretrained checkpoints on many downstream tasks.
1. Top-level workflow
The repo has two main execution paths:
- Benchmark/evaluation via main_lite.py, main_lite_ecg.py, main_lite_eeg.py, main_lite_movement.py
- Pretraining via pretrain.py
These scripts sit on top of the clinical_ts package and are the main entry points for running experiments.
2. Data ingestion and preprocessing
Raw datasets are not stored in code; the repository expects preprocessed dataset artifacts produced externally, mainly through:
- preprocess_ecg_dataset.ipynb
- mimic_preprocessing.py
The data pipeline is driven by dataset-specific YAML configs under code/conf/data and code/clinical_ts/models/conf/data. The README also points to label-mapping spreadsheets and MIMIC-IV-ECG CSVs, which are part of the data preparation boundary.
3. Clinical time-series core package
code/clinical_ts is the central library. It contains the reusable building blocks shared across all tasks:
- data/ for dataset objects, transforms, and utilities
- models/ for supervised and foundation-model wrappers
- loss/ and metric/ for task objectives and evaluation
- task/ for task-specific orchestration
- utils/ for preprocessing, scheduling, evaluation, and helper functions
This is the main abstraction layer that separates dataset handling, model definition, and experiment execution.
4. Dataset layer
time_series_dataset.py, time_series_dataset_transforms.py, and time_series_dataset_utils.py define how signals and labels are loaded, normalized, sliced, and augmented. The dataset layer appears designed to support multiple modalities and multiple task types, with configuration rather than hard-coded dataset logic.
5. Task orchestration
code/clinical_ts/task/ecg.py and the main_lite_* scripts likely assemble the full experiment for a specific modality/task family. This layer is where a dataset, model, loss, and metrics are bound together into a runnable benchmark. It is the boundary between generic infrastructure and ECG-specific benchmark logic.
6. Model families
The repository supports both classic supervised architectures and foundation-model adapters:
- Lightweight supervised backbones such as basic_conv1d, inception1d, xresnet1d, s4_model, s42
- A common template_model.py / template_modules.py pattern for modular model construction
- Specialized ECG foundation model wrappers under models/ecg_foundation_models/
This separation suggests a plug-in style architecture where downstream tasks can swap encoders while keeping the rest of the pipeline stable.
7. Foundation model implementations
The ECG foundation-model area contains multiple model-specific subpackages:
- ecg_cpc
- ecg_jepa
- hubert_ecg
- st_mem
- merl
- ecgfm_ked
- ecg_founder
These packages each encapsulate their own backbone/config/utility code, indicating that pretrained checkpoints from different research groups are integrated through wrappers rather than a single unified backbone implementation.
8. Shared encoder/head abstractions
The clinical_ts/ts/ and clinical_ts/utils/heads.py modules indicate a decomposition into:
- an encoder/backbone that converts time series to latent representations
- a head that maps representations to classification/regression outputs
This is important for diagramming because the benchmark likely reuses the same task head interface across many model types and datasets.
9. Losses and metrics
loss/supervised.py and loss/selfsupervised.py split objective functions by training regime. metric/base.py and utility evaluators under utils/ provide task-specific evaluation for classification, regression, and clinical benchmark reporting. This supports both pretraining objectives and downstream fine-tuning/evaluation.
10. Configuration system
The repository uses many YAML configuration files rather than hard-coded experiment setup:
- global/run configs under code/conf
- model/data configs under code/clinical_ts/models/conf
- one data config per dataset/split/subject group for the benchmark
The architecture is configuration-driven, which is critical for reproducing the benchmark across 12 datasets and 26 tasks.
11. Experimental infrastructure
There are two native extensions under code/extensions/:
- cauchy/ with C++/CUDA code and benchmarking/tuning scripts
- kernels/
These are performance-oriented compute boundaries, likely used for accelerating specialized kernels in some models. They are optional infrastructure rather than core application logic, but they matter if you are drawing a diagram that includes training-time acceleration.
12. External assets and checkpoints
The benchmark depends on externally downloaded pretrained checkpoints for ECG-CPC, ECGFounder, ECG-JEPA, ST-MEM, MERL, ECGFM-KED, HuBERT-ECG, and ECG-FM. The repo is therefore not just training code; it is also a model-evaluation harness over third-party pretrained assets.
13. Output and evaluation boundary
The benchmark is focused on downstream performance reporting across many tasks, including regression and classification. The output boundary is therefore experiment results rather than an application UI or inference service. The presence of Confidence_Interval.xlsx suggests some postprocessing/statistical analysis outside the main training loop.
14. Runtime and tooling
The project is Python-based and appears designed for scripted batch execution, with sbatch run.sh in the README indicating HPC/Slurm usage. Environment files (env.yaml, ecg_fm_env.yaml) define the runtime dependencies, and the repo is organized for reproducible offline experimentation rather than interactive serving.
Overall, the architecture is: raw clinical time-series data -> preprocessing -> dataset/config loading -> shared time-series pipeline -> chosen model family/backbone -> task head -> loss/metrics -> benchmark results. The main architectural boundary is between dataset/task orchestration and the interchangeable model implementations, especially the multiple foundation-model adapters.
Main entry points: fm_ecg.py ECG FM · ecg.py ECG task · time_series_dataset.py Datasets · template_model.py Template model
Components 32
Entry points
Benchmark and training scripts
- Main lite · Generic benchmark/evaluation runner
code/main_lite.py - ECG lite · ECG-specific evaluation runner
code/main_lite_ecg.py - EEG lite · EEG evaluation runner
code/main_lite_eeg.py - Move lite · Movement-signal evaluation runner
code/main_lite_movement.py - Pretrain · Pretraining workflow entry point
code/pretrain.py
Clinical TS core
Shared dataset, model, task, loss, metric, and utility layers
- Datasets · Time-series dataset loading and sampling
code/clinical_ts/data/time_series_dataset.py - Transforms · Signal preprocessing and augmentation transforms
code/clinical_ts/data/time_series_dataset_transforms.py - Data utils · Dataset helpers and normalization/slicing utilities
code/clinical_ts/data/time_series_dataset_utils.py - ECG task · Assembles ECG benchmark experiments
code/clinical_ts/task/ecg.py - Template model · Reusable model construction pattern
code/clinical_ts/template_model.py - Encoder · Shared time-series encoder abstraction
code/clinical_ts/ts/encoder.py - Task head · Maps embeddings to outputs
code/clinical_ts/ts/head.py - Losses · Supervised and self-supervised objectives
code/clinical_ts/loss/supervised.py - Metrics · Base metrics and evaluation helpers
code/clinical_ts/metric/base.py
Foundation models
ECG foundation-model adapters and model families
- Conv1D · Lightweight convolutional backbone family
code/clinical_ts/models/basic_conv1d.py - S4 family · S4-based model wrappers and modules
code/clinical_ts/models/s4_model.py - XResNet · 1D ResNet-style backbone
code/clinical_ts/models/xresnet1d.py - ECG FM · ECG foundation-model integration layer
code/clinical_ts/models/fm_ecg.py - EEG FM · EEG foundation-model integration layer
code/clinical_ts/models/fm_eeg.py - Move FM · Movement foundation-model integration layer
code/clinical_ts/models/fm_movement.py - Founder · ECGFounder checkpoint wrapper
code/clinical_ts/models/ecg_foundation_models/ecg_founder.py - ECG CPC · ECG-CPC integration and IO
code/clinical_ts/models/ecg_foundation_models/ecg_cpc/basic_io.py - ECG JEPA · ECG-JEPA wrapper and utilities
code/clinical_ts/models/ecg_foundation_models/ecg_jepa/ecg_jepa.py - HuBERT ECG · HuBERT-ECG integration and classification wrapper
code/clinical_ts/models/ecg_foundation_models/hubert_ecg/hubert_ecg.py - ST-MEM · ST-MEM integration and encoders
code/clinical_ts/models/ecg_foundation_models/st_mem/st_mem.py - MERL · MERL backbone variants
code/clinical_ts/models/ecg_foundation_models/merl/resnet1d.py - ECGFM-KED · KED-based ECGFM integration
code/clinical_ts/models/ecg_foundation_models/ecgfm_ked.py
Configuration
Run and dataset configuration files
- Run config · Top-level benchmark configuration
code/conf/config_cpc_ecg_s4_heedb.yaml - Data configs · Dataset and split configuration set
code/conf/data/ecg_heedb_S0001-1987.yaml - Model cfg · Model/data config namespace for clinical_ts
code/clinical_ts/models/conf/data/ecg_ptbxl.yaml
Connections 27
- Main lite → ECG task: orchestrates · Generic benchmark runner delegates task assembly
- ECG lite → ECG task: orchestrates · ECG runner assembles ECG benchmark experiments
- EEG lite → EEG FM: selects · EEG runner uses modality-specific wrappers
- Move lite → Move FM: selects · Movement runner uses modality-specific wrappers
- Pretrain → Template model: builds · Pretraining uses shared model scaffolding
- Pretrain → Losses: optimizes · Training loop applies self-supervised or supervised objectives
- ECG task → Datasets: loads · Task binds datasets into benchmark runs
- ECG task → Transforms: applies · Task uses preprocessing and augmentation
- ECG task → Metrics: reports · Task evaluates downstream performance
- Datasets → Data utils: uses · Dataset layer relies on helpers for slicing and normalization
- Datasets → Data configs: configured by · Dataset loading is driven by dataset YAMLs
- Template model → Encoder: composes · Template model wires encoder and head
- Template model → Task head: composes · Template model wires prediction head
- Encoder → Conv1D: backs · Shared encoder abstraction can host conv backbones
- Encoder → S4 family: backs · Shared encoder abstraction can host S4 backbones
- Task head → Losses: trains with · Predictions are optimized through shared loss layer
- ECG FM → Founder: wraps · ECG wrapper exposes founder checkpoint integration
- ECG FM → ECG CPC: wraps · ECG wrapper exposes CPC integration
- ECG FM → ECG JEPA: wraps · ECG wrapper exposes JEPA integration
- ECG FM → HuBERT ECG: wraps · ECG wrapper exposes HuBERT-ECG integration
- ECG FM → ST-MEM: wraps · ECG wrapper exposes ST-MEM integration
- ECG FM → MERL: wraps · ECG wrapper exposes MERL integration
- ECG FM → ECGFM-KED: wraps · ECG wrapper exposes ECGFM-KED integration
- ECG FM → Run config: configured by · Runs are driven by top-level YAML config
- ECG FM → Model cfg: configured by · Model wrappers consume shared model/data config
- Cauchy → Main lite: accelerates · Optional native compute acceleration for experiments
- Kernels → Main lite: accelerates · Optional kernel package supports training/eval performance