Lobachevsky University ECG Database (LUDB)

Open Completed

Quick facts

Format12-lead · 10 s · 500 Hz · manually annotated waves
Patients200
Records200
Leads12
LicenseODC-By 1.0
OriginNizhny Novgorod City Hospital No. 5 / Lobachevsky University — Russia

Overview

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 distribution (all 200 records)

RhythmRecords
Sinus rhythm142
Sinus bradycardia24
Atrial fibrillation14
Sinus arrhythmia7
Sinus tachycardia4
Atrial flutter, typical3
Irregular sinus rhythm2
four further rhythms1 each

Diagnosis categories — how much of the data each covers

CategoryRecords with a findingDistinct labels
Rhythms2009
Electric axis of the heart1905
Hypertrophies1427
Conduction abnormalities668
Ischemia5122
Non-specific repolarization abnormalities496
Extrasystolies1416
Cardiac pacing106
Other states91

About those counts

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:

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.

Validation summary (500 Hz)

VersionRecordsNote
original200all records, with is_valid + quality_issues
clean200100% pass rate — no check fired on any record
excluded0

Building the splits

ecgbench splits --dataset ludb --data-path /path/to/ludb/1.0.1/

Loading with ECGBench

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')