| Format | 12-lead · 10 s · 500 Hz · manually annotated waves |
|---|---|
| Patients | 200 |
| Records | 200 |
| Leads | 12 |
| License | ODC-By 1.0 |
| Origin | Nizhny Novgorod City Hospital No. 5 / Lobachevsky University — Russia |
200 twelve-lead ECGs from 200 patients at Nizhny Novgorod City Hospital No. 5, each 10 seconds at 500 Hz. One record per patient, so folds need no grouping.
What sets LUDB apart is its manual delineation: every record has 12 annotation files, one per lead, marking the onset, peak and offset of each P, QRS and T wave — 58,429 annotated waves in total. This is the reference dataset for delineation algorithms rather than classification.
ECGBench splits and loads the signals; it does not model the annotations.
Read them directly where they sit, for example
wfdb.rdann(f"{data_path}/data/1", "ii"), whose symbols are ( for a wave
onset, ) for an offset, and p / N / t for the P, QRS and T peaks.
Lead names are lowercase in every header (i, ii, avr, v1 …).
Select leads by name and the case is handled for you.
| Rhythm | Records |
|---|---|
| Sinus rhythm | 142 |
| Sinus bradycardia | 24 |
| Atrial fibrillation | 14 |
| Sinus arrhythmia | 7 |
| Sinus tachycardia | 4 |
| Atrial flutter, typical | 3 |
| Irregular sinus rhythm | 2 |
| four further rhythms | 1 each |
| Category | Records with a finding | Distinct labels |
|---|---|---|
| Rhythms | 200 | 9 |
| Electric axis of the heart | 190 | 5 |
| Hypertrophies | 142 | 7 |
| Conduction abnormalities | 66 | 8 |
| Ischemia | 51 | 22 |
| Non-specific repolarization abnormalities | 49 | 6 |
| Extrasystolies | 14 | 16 |
| Cardiac pacing | 10 | 6 |
| Other states | 9 | 1 |
Recomputed from ludb.csv, the structured source. The per-record .hea
comments carry the same content flattened into free text and lose the
category grouping, so the CSV is what ECGBench reads.
Two properties of that file matter, and both are handled for you:
Ischemia
looks like 40 classes with at most 4 records each; split into atomic
labels it is 22 findings that co-occur. The counts above are atomised.>89. The loader
exposes age_raw verbatim alongside a numeric age that is NaN there.Only Rhythms covers all 200 records. Everything below Conduction
abnormalities is sparse enough that a classifier has very little to learn
from: Other states has a single label across 9 records, and
Extrasystolies has 16 labels across 14 records — more labels than
records. primary_rhythm pools rhythms under 10 records into OTHER so
10-fold stratification is defined; train on rhythms, which is verbatim.
| Version | Records | Note |
|---|---|---|
| original | 200 | all records, with is_valid + quality_issues |
| clean | 200 | 100% pass rate — no check fired on any record |
| excluded | 0 |
ecgbench splits --dataset ludb --data-path /path/to/ludb/1.0.1/
from ecgbench import ECGDataset
ds = ECGDataset(
"ludb",
split="train",
data_path="/path/to/ludb/1.0.1/",
labels=True,
)
sample = ds[0]
sample["signal"].shape # (12, 5000), millivolts
sample["labels"]["rhythms"] # ['Atrial fibrillation'] — multi-label
sample["labels"]["hypertrophies"] # ['Left atrial hypertrophy', ...]
sample["labels"]["electric_axis"] # 'left axis deviation'
sample["labels"]["age_raw"] # '57' (one record is '>89')
# Lead names are lowercase here; selecting by name is case-insensitive.
two = ECGDataset("ludb", split="train", data_path="/path/to/ludb/1.0.1/",
leads=["II", "V5"])
two.lead_names # ('ii', 'v5')