Datasets¶
DeepECG-Kit provides 5 ECG datasets with automatic downloading, standardized interfaces, and preprocessing utilities.
Available Datasets¶
| Registry Name | Class | Leads | Classes | Sampling rate | Download |
|---|---|---|---|---|---|
af-classification |
AFClassificationDataset |
1 | 4 (Normal, AF, Other, Noisy) | 300 Hz, 10 s segments | about 100 MB |
mitbih-afdb |
MITBIHAFDBDataset |
1 (2 with use_both_leads) |
4, or 2 with binary_classification |
300 Hz, 10 s segments | about 640 MB |
ltafdb |
LTAFDBDataset |
1 (2 with use_both_leads) |
4, or 2 with binary_classification |
300 Hz, 10 s segments | about 3.7 GB |
unified-af |
UnifiedAFDataset |
1 | 4, or 2 with binary_classification |
300 Hz, 10 s segments | the three above |
ptbxl |
PTBXLDataset |
12 | 5 superclasses | 500 Hz, 10 s recordings | about 3.2 GB |
The sampling rate is each dataset's default: recordings are resampled to it from their native
rate (300 Hz for PhysioNet 2017, 250 Hz for MIT-BIH AFDB, 128 Hz for LTAFDB) so that the AF
datasets share an input shape. Pass sampling_rate to change it. deepecg list-datasets prints
the same table together with where each dataset is stored and whether it has been downloaded.
In their four-class mode MIT-BIH AFDB and LTAFDB label segments Normal, AF, AFL or J from the
rhythm annotations, and every other annotated rhythm (ventricular tachycardia, bigeminy and the
rest, which LTAFDB has many of) counts as Normal. With binary_classification the two classes are
AF and everything else, which is what that grouping amounts to.
ECGDataModule¶
ECGDataModule handles dataset creation, train/val/test splitting, and DataLoader construction:
from deepecgkit.datasets import ECGDataModule
from deepecgkit.registry import get_dataset
dataset_class = get_dataset("af-classification")
data_module = ECGDataModule(
dataset_class=dataset_class,
batch_size=32,
val_split=0.2,
test_split=0.1,
seed=42,
stratify=True,
download=True,
)
data_module.setup(stage="fit")
train_loader = data_module.train_dataloader()
val_loader = data_module.val_dataloader()
Options specific to one dataset go in dataset_kwargs:
data_module = ECGDataModule(
dataset_class=get_dataset("ltafdb"),
dataset_kwargs={"binary_classification": True, "segment_duration_seconds": 30.0},
download=True,
)
Key Parameters¶
| Parameter | Default | Description |
|---|---|---|
dataset_class |
None | Dataset class to instantiate |
dataset |
None | Pre-instantiated dataset (alternative to dataset_class) |
data_dir |
None | Data directory (uses dataset default if None) |
batch_size |
32 | Batch size for DataLoaders |
val_split |
0.2 | Validation fraction |
test_split |
0.1 | Test fraction |
num_workers |
0 | DataLoader workers |
seed |
42 | Random seed for splitting |
stratify |
True | Stratified splitting based on labels |
download |
False | Download dataset if not present |
sampling_rate |
None | Sampling rate to resample to (Hz); None keeps the dataset's own default |
dataset_kwargs |
None | Extra keyword arguments for the dataset class |
After setup(), data_module.sampling_rate holds the rate in effect and
data_module.describe() returns everything a checkpoint records about the data: dataset name,
preprocessing, class names, sample shape and split protocol.
num_workers defaults to 0 because the built-in datasets keep their signals in memory, which
makes worker processes pure overhead. If you raise it in a script on macOS or Windows, guard the
script with if __name__ == "__main__":.
Using the Registry¶
Look up datasets by their registry name:
from deepecgkit.registry import get_dataset, get_dataset_info, get_dataset_names
print(get_dataset_names())
info = get_dataset_info("af-classification")
print(f"Channels: {info['input_channels']}, Classes: {info['num_classes']}")
dataset_class = get_dataset("af-classification")
Auto-Download¶
Datasets are downloaded on first use when download=True (the CLI always passes it). They are
stored under ~/.deepecgkit/datasets, one folder per dataset; set the DEEPECGKIT_HOME
environment variable to move that root, or pass data_dir for a single dataset. unified-af
keeps nothing of its own and reads its three sources from their own folders, so a copy fetched
for one of them is shared; its data_dir is the folder that holds those three folders. A source
that cannot be loaded is an error rather than a smaller dataset; skip_unavailable=True builds it
from the sources that do load.
Files come from PhysioNet's open-data mirror, with the archives on physionet.org as the
fallback. Only what the loaders read is fetched. A download that is interrupted resumes where
it stopped the next time the dataset is requested, and a directory left incomplete is detected
and completed rather than loaded as it is. Use force_download=True (CLI: --force-download)
to discard a local copy and fetch it again. That deletes the dataset's folder first, so a
data_dir of your own is only cleared when it is empty or recognisably a copy of the dataset;
anything else is refused rather than deleted.
from deepecgkit.datasets import PTBXLDataset
PTBXLDataset.is_downloaded() # is a usable copy present?
PTBXLDataset.get_default_data_dir() # where it lives
PTBXLDataset.download_to() # fetch it without loading it
BaseECGDataset¶
All datasets inherit from BaseECGDataset, which extends torch.utils.data.Dataset and defines the interface:
__getitem__(idx)→(signal_tensor, label_tensor), the signal shaped(leads, samples)__len__()→ number of samplesclass_names/num_classes→ the label vocabularyget_class_distribution()→ sample count per classgroup_ids→ one identifier per sample, used to keep a recording or patient inside one splitget_preprocessing()→ sampling rate, segment length and normalisation of the samples
Preprocessing Utilities¶
ECGStandardizer¶
Resamples and normalizes ECG signals of shape (leads, samples):
from deepecgkit.datasets import ECGStandardizer
standardizer = ECGStandardizer(target_sampling_rate=500, normalization="zscore")
signal = standardizer(raw_signal, original_sampling_rate=360)
ECGSegmenter¶
Segments long ECG recordings into fixed-length windows:
from deepecgkit.datasets import ECGSegmenter
segmenter = ECGSegmenter(segment_duration_seconds=10.0, sampling_rate=300, overlap=0.5)
segments, start_indices = segmenter.segment(long_signal)
RhythmAnnotationExtractor¶
Converts WFDB rhythm annotations to a label per signal sample, and then per segment:
from deepecgkit.datasets import RhythmAnnotationExtractor
extractor = RhythmAnnotationExtractor(sampling_rate=300)
labels = extractor.extract_labels(annotation, signal_length=long_signal.shape[-1])
segment_labels = extractor.segment_with_labels(labels, start_indices, segmenter.segment_length)
Preparing a recording for a trained model¶
prepare_recording applies a dataset's preprocessing to a recording that is not part of it —
the same resampling, segmenting and normalising, in the same order. Checkpoints record the
preprocessing of their training data, so this is how a new recording is made ready for one:
from deepecgkit.datasets import prepare_recording, read_ecg_recording_with_rate
signal, rate = read_ecg_recording_with_rate("record.hea") # rate is None for .npy and .csv
prepared = prepare_recording(signal, dataset.get_preprocessing(), source_sampling_rate=rate)
prepared.segments # (num_segments, leads, samples), ready for the model
prepared.start_seconds # where each segment starts in the recording
prepared.notes # what was applied, in words
Adding Your Own Dataset¶
Subclass BaseECGDataset, fill the standard storage attributes in _load_data, and register
the class:
import numpy as np
from deepecgkit.datasets import BaseECGDataset
from deepecgkit.registry import register_dataset
@register_dataset("my-dataset", input_channels=1, description="My recordings")
class MyDataset(BaseECGDataset):
CLASS_LABELS = ["Normal", "Abnormal"]
def _load_data(self):
self.signals = [...] # one float32 array of shape (leads, samples) per sample
self.labels = [...] # one integer class index per sample
self.record_names = [...] # one source-recording name per sample
record_names matters: samples sharing a name are kept in the same split, which is what stops
segments of one recording from landing in both train and test. Implement _download(cls,
data_dir, verbose) if the data can be fetched automatically. Constructor arguments named
sampling_rate, normalization, segment_duration_seconds, binary_classification or
multi_label are picked up by the matching CLI flags.
The class works with ECGDataModule(dataset_class=MyDataset, data_dir=...) straight away, and
with the CLI through --import.