Skip to main content

ECGEN-Eval

Evaluation metrics and interactive HTML reports for synthetic ECG quality assessment. Part of the ECGEN research suite (ECGEN-FM, ECGEN-VAE, Pulse2Pulse).

Installation

pip install -e .           # core
pip install -e ".[dev]"    # + pytest, black, ruff

Optional dependencies:

pip install wfdb           # for loading PTB-XL / WFDB records
pip install scipy          # for Welch PSD, resampling, and R-peak detection (recommended)
pip install dtaidistance   # fast Cython DTW (falls back to pure-Python)

# Clinical feature extraction (Task 1.2)
pip install -e ".[features]"   # installs neurokit2 + scipy
# or individually:
pip install neurokit2 scipy

Quick start

import numpy as np
from ecgen_eval import load_ecg_npy, AVAILABLE_METRICS

# Load model outputs (shape: N × L × T)
real  = load_ecg_npy("real_ecgs.npy",  label="PTB-XL")
synth = load_ecg_npy("synth_ecgs.npy", label="ECGEN-FM")

# Compute a metric
mmd = AVAILABLE_METRICS["mmd"]()
result = mmd.compute(real.data, synth.data, lead_names=real.lead_names)
print(f"MMD = {result.score:.6f}")
print(result.per_lead_scores)

Data loaders

Function Input Use case
load_ecg_npy(path) .npy / .npz (N, L, T) ECGEN-FM, ECGEN-VAE, Pulse2Pulse outputs
load_ecg_wfdb(dir) WFDB .hea/.dat records PTB-XL and PhysioNet datasets
load_ecg_folder(path) auto-detected Any of the above + CSV

load_ecg_folder auto-detects the format based on file extensions.

Metrics

All metrics are available via AVAILABLE_METRICS["key"]() and return a MetricResult with .score, .per_lead_scores, and .extra.

Original metrics

Key Name Lower / Higher Paper
mmd Maximum Mean Discrepancy Lower ↓ Gretton et al., JMLR 2012
dtw Dynamic Time Warping Lower ↓ Berndt & Clifford, KDD 1994
prd Percent Root-mean-square Difference Lower ↓ Zigel et al., IEEE TBME 2000
psd PSD Divergence (Jensen-Shannon) Lower ↓ Welch, IEEE Trans Audio 1967 · Applied: Golany & Radinsky, AAAI 2019
fd Fréchet Distance Lower ↓ Heusel et al., NeurIPS 2017 · Applied: Thambawita et al., Sci Rep 2021

New metrics (literature review 2018 – 2025)

Key Name Lower / Higher Paper
psnr Peak Signal-to-Noise Ratio Higher ↑ Huynh-Thu & Ghanbari, Electron Lett 2008
ssim Structural Similarity Index (1-D) Higher ↑ Wang et al., IEEE TIP 2004 · ECG: Hayn et al., Physiol Meas 2018
swd Sliced Wasserstein Distance Lower ↓ Rabin et al., LNCS 2011 · Kolouri et al., NeurIPS 2019
mae Mean Absolute Error (nearest-neighbour) Lower ↓ Zigel et al., IEEE TBME 2000
hrv Heart Rate Variability (SDNN/rMSSD/pNN50) Lower ↓ Task Force ESC/NASPE, Circulation 1996
pr_dist Distribution Precision & Recall Higher ↑ Kynkäänniemi et al., NeurIPS 2019
sqi Signal Quality Index (QRS template) Higher ↑ Clifford et al., CinC 2017
nn_dist Nearest-Neighbour Distance (memorisation) Lower ↓ Meehan et al., AISTATS 2020
heart_rate Heart Rate Distribution (JS divergence) Lower ↓ Pan & Tompkins, IEEE TBME 1985 · Applied: Golany & Radinsky, AAAI 2019
spectral_entropy Spectral Entropy Divergence Lower ↓ Inouye et al., EEG Clin Neurophysiol 1991

Feature Extraction

Extract 10 clinical ECG features per recording and compare their distributions between real and synthetic datasets.

from ecgen_eval import load_ecg_npy
from ecgen_eval.features import extract_features

real  = load_ecg_npy("real_ecgs.npy",  label="PTB-XL")
synth = load_ecg_npy("synth_ecgs.npy", label="ECGEN-FM")

# Returns (beat_df, summary_df)
_, real_feat  = extract_features(real)
_, synth_feat = extract_features(synth)

print(real_feat[["rr_mean_ms", "hr_bpm", "qrs_duration_ms",
                  "qt_interval_ms", "hrv_sdnn_ms"]].describe())

Extracted features (one value per recording):

Feature Column Description
RR interval rr_mean_ms, rr_std_ms Mean and std of peak-to-peak intervals
Heart rate hr_bpm 60 000 / mean(RR)
PR interval pr_interval_ms P-onset to QRS-onset
QRS duration qrs_duration_ms Width of QRS complex
QT interval qt_interval_ms QRS-onset to T-end
QTc (Bazett) qtc_bazett_ms QT / √RR(s)
ST deviation st_deviation_mv Mean ST voltage vs isoelectric line
T-wave amplitude t_amplitude_mv Signed T-peak amplitude
P-wave duration p_wave_duration_ms P-onset to P-offset
HRV hrv_sdnn_ms, hrv_rmssd_ms, hrv_pnn50 SDNN, RMSSD, pNN50

2D and 3D distribution plots

from ecgen_eval.visualization.feature_plots import (
    plot_feature_2d,
    plot_feature_pair_grid,
    plot_feature_3d,
    plot_feature_triple_grid,
)

# Single 2D scatter + KDE contour
fig = plot_feature_2d(real_feat, synth_feat, "rr_mean_ms", "hr_bpm")
fig.show()

# Grid of default feature pairs
fig = plot_feature_pair_grid(real_feat, synth_feat)
fig.show()

# Interactive 3D scatter
fig = plot_feature_3d(real_feat, synth_feat, "rr_mean_ms", "hr_bpm", "qrs_duration_ms")
fig.show()

CLI

# Run all metrics and generate interactive HTML report
ecgen-eval eval \
  --real   /path/to/real_ecgs.npy \
  --synthetic /path/to/model1_ecgs.npy \
  --synthetic /path/to/model2_ecgs.npy \
  --output report.html

# Include clinical feature distributions in the report (requires neurokit2)
ecgen-eval eval \
  --real /path/to/real_ecgs.npy \
  --synthetic /path/to/synth_ecgs.npy \
  --feature-plots \
  --output report.html

# Extract features only — save summary CSV
ecgen-eval features \
  --real /path/to/real_ecgs.npy \
  --synthetic /path/to/synth_ecgs.npy \
  --output features.csv

# List available metrics
ecgen-eval list-metrics

# ECG waveform comparison only (no metrics)
ecgen-eval visualize \
  --real /path/to/real.npy \
  --synthetic /path/to/synth.npy \
  --compare-mode overlay

Key options for eval:

  • --metrics mmd prd psd psnr hrv — run a subset of metrics
  • --feature-plots — add clinical feature extraction + 2D/3D distribution plots
  • --fs 500 — sampling frequency
  • --n-samples 5 — ECG samples shown in gallery
  • --offline — embed Plotly.js for offline viewing
  • --max-samples 500 — cap loaded recordings per folder

Report

The HTML report includes:

  • Dataset summary table
  • Interactive ECG gallery (overlay / side-by-side / grid)
  • Per-metric: score table, per-lead bar chart, PSD overlay (PSD metric), violin plot (DTW)
  • Radar chart summarising all metrics
  • Metrics summary table with Download CSV button
  • Clinical Feature Summary table (mean ± std, real vs synthetic) — with --feature-plots
  • 2D feature distribution plots (scatter + KDE contours) — with --feature-plots
  • 3D feature distribution plots (interactive rotation) — with --feature-plots
  • References / citations

Running tests

pytest tests/ -v

Project structure

ecgen_eval/
├── data/
│   ├── dataset.py          ECGDataset container
│   └── loader.py           npy / WFDB / CSV loaders
├── features/               Clinical ECG feature extraction (optional: neurokit2)
│   ├── __init__.py         Exports FeatureExtractor, extract_features
│   ├── extractor.py        FeatureExtractor class → (beat_df, summary_df)
│   ├── qrs_detection.py    R-peak detection (neurokit2 primary, scipy fallback)
│   ├── interval_features.py  RR, HR, PR, QRS, QT, QTc, P-wave duration
│   ├── amplitude_features.py ST deviation, T-wave amplitude
│   ├── hrv_features.py     SDNN, RMSSD, pNN50
│   └── utils.py            Bandpass filter, peak helpers
├── metrics/
│   ├── base.py             BaseMetric + MetricResult
│   ├── mmd.py              MMD
│   ├── dtw.py              DTW
│   ├── prd.py              PRD
│   ├── psd.py              PSD divergence
│   ├── fd.py               Fréchet Distance
│   ├── psnr.py             PSNR
│   ├── ssim.py             1-D SSIM
│   ├── swd.py              Sliced Wasserstein Distance
│   ├── mae.py              MAE / RMSE
│   ├── hrv.py              HRV statistics (SDNN, rMSSD, pNN50)
│   ├── pr_dist.py          Distribution Precision & Recall
│   ├── sqi.py              Signal Quality Index
│   ├── nn_dist.py          Nearest-Neighbour Distance
│   ├── heart_rate.py       Heart Rate Distribution
│   └── spectral_entropy.py Spectral Entropy Divergence
├── visualization/
│   ├── ecg_paper.py        ECG graph-paper figure factory
│   ├── ecg_waveform.py     Waveform comparison plots
│   ├── metric_plots.py     Bar, violin, PSD overlay, radar
│   └── feature_plots.py    2D/3D clinical feature distribution plots
├── report/
│   └── html_report.py      HTML report generator
└── cli.py                  Click CLI entry point (eval, visualize, features, list-metrics)
tests/
├── conftest.py
├── test_dataset.py
├── test_loader.py
├── test_metrics.py
└── test_features.py
docs/
└── ecg_features_review.md  Literature review of the 10 clinical features

Release files for ecgen-eval 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ecgen-eval 0.5.0
File Size Uploaded
ecgen_eval-0.5.0.tar.gz 2.3 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for ecgen-eval 0.5.0
File Interpreter ABI Platform
ecgen_eval-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 2.4 MB

Release files / ecgen_eval-0.5.0.tar.gz

Download URL ecgen_eval-0.5.0.tar.gz
Size 2.3 MB
Tags Source
SHA-256 checksum
How to use checksums
94030bd88a677bfcfc540f16f0087f5259bf1dda27fd76a03192d8771b448c06
BLAKE2b-256 checksum
How to use checksums
fa1576c3a7c659cbf8492c2cd5b3a605595ebc13dd47cf78a78ac0669f734ebd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 6, 2026.

Transparency log

Release files / ecgen_eval-0.5.0-py3-none-any.whl

Download URL ecgen_eval-0.5.0-py3-none-any.whl
Size 101.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
78e99f18681d34d2d1cbf965263053a90a4adc28f0c51fcaf75af80a59c268b1
BLAKE2b-256 checksum
How to use checksums
58fdf1fd989aa4a1f4c63355baeceff343fe6be751ad309276b7087713fa317a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page