Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2cf92f6
feat(datasets): add PTB-XL ECG loader and YAML config
AxelNoun Aug 12, 2026
f4cf33e
feat: add PTB-XL 5-superclass task and strat_fold split
AxelNoun Aug 12, 2026
95ec5c4
test: add PTB-XL synthetic fixtures and end-to-end coverage
AxelNoun Aug 12, 2026
2d10c69
docs: add PTB-XL API pages and quickstart example
AxelNoun Aug 12, 2026
6e9ef58
fix(datasets): isolate PTB-XL cache identity and write metadata atomi…
AxelNoun Aug 18, 2026
00f9591
fix(tasks): keep PTB-XL task cache keys stable and emit integer ages
AxelNoun Aug 18, 2026
5a2ba11
refactor(datasets): avoid decoding samples in split_by_strat_fold
AxelNoun Aug 18, 2026
ce76939
test: cover PTB-XL set_task, cache isolation, and age sentinels
AxelNoun Aug 18, 2026
be6fb1c
docs: correct PTB-XL v1.0.3 counts, label order, and API toctree
AxelNoun Aug 18, 2026
b1e1891
fix(datasets): keep split_by_strat_fold re-export alias for ruff F401
AxelNoun Aug 18, 2026
a8fb194
fix(datasets): keep PTBXLDataset.root as the user data path
AxelNoun Aug 18, 2026
fe1007f
feat(datasets): add check_patient_disjoint to split_by_strat_fold
AxelNoun Aug 18, 2026
7cc2b9d
Merge remote-tracking branch 'upstream/master' into feat/ptbxl-dataset
AxelNoun Oct 6, 2026
0fccca0
docs(datasets): describe the post-#1265 cache key in PTBXLDataset
AxelNoun Oct 6, 2026
f4d5d07
Merge remote-tracking branch 'upstream/master' into feat/ptbxl-dataset
AxelNoun Oct 7, 2026
2271703
Fusionner upstream/master (4a173f7) dans feat/ptbxl-dataset
AxelNoun Oct 26, 2026
a53669a
docs(ptbxl): correct sex encoding to 0 = male, 1 = female
AxelNoun Oct 26, 2026
2cc8539
test(ptbxl): cover the 500 Hz records500 path
AxelNoun Oct 26, 2026
5d874df
docs(ptbxl): clarify version support, cache-dir write order, example …
AxelNoun Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api/datasets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ Available Datasets
datasets/pyhealth.datasets.ChestXray14Dataset
datasets/pyhealth.datasets.PhysioNetDeIDDataset
datasets/pyhealth.datasets.EEGBCIDataset
PTB-XL Dataset <datasets/pyhealth.datasets.PTBXLDataset>
datasets/pyhealth.datasets.TUABDataset
datasets/pyhealth.datasets.TUEVDataset
datasets/pyhealth.datasets.ClinVarDataset
Expand Down
34 changes: 34 additions & 0 deletions docs/api/datasets/pyhealth.datasets.PTBXLDataset.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
pyhealth.datasets.PTBXLDataset
==============================

Overview
--------

PTB-XL is a large publicly available 12-lead ECG dataset from PhysioNet
(version 1.0.3). It contains **21,799** clinical ECG records of 10 seconds
from **18,869** patients, with multi-label SCP-ECG statements, official
stratified folds (``strat_fold``), and demographic / site / device metadata
suited to shift-aware evaluation. Only v1.0.3 is supported (v1.0.1 figures
of 21,837 records / 18,885 patients come from Wagner et al., Scientific
Data 2020, and must not be mixed with the PhysioNet v1.0.3 counts).

For more information see `PhysioNet PTB-XL v1.0.3
<https://physionet.org/content/ptb-xl/1.0.3/>`_ and Wagner et al.,
`Scientific Data 2020 <https://www.nature.com/articles/s41597-020-0495-6>`_.

Optional dependency: install waveform I/O with ``pip install 'pyhealth[ptbxl]'``.

``PTBXLDataset.root`` is the version directory passed to the constructor.
Derived metadata CSVs are written under ``metadata_cache_dir`` (default
``~/.cache/pyhealth/datasets/ptbxl/``), not into ``root``, so read-only
data mounts stay untouched. ``BaseDataset.load_table`` concatenates
``root / file_path`` and cannot take an absolute path; PTB-XL overrides
``load_table`` to read the derived CSV from the metadata cache.

API Reference
-------------

.. autoclass:: pyhealth.datasets.PTBXLDataset
:members:
:undoc-members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/api/tasks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ Available Tasks
Sleep Staging (SleepEDF) <tasks/pyhealth.tasks.SleepStagingSleepEDF>
Temple University EEG Tasks <tasks/pyhealth.tasks.temple_university_EEG_tasks>
EEGBCI Tasks <tasks/pyhealth.tasks.eegbci>
PTB-XL Tasks <tasks/pyhealth.tasks.ptbxl>
Sleep Staging v2 <tasks/pyhealth.tasks.sleep_staging_v2>
Benchmark EHRShot <tasks/pyhealth.tasks.benchmark_ehrshot>
ChestX-ray14 Binary Classification <tasks/pyhealth.tasks.ChestXray14BinaryClassification>
Expand Down
24 changes: 24 additions & 0 deletions docs/api/tasks/pyhealth.tasks.ptbxl.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
pyhealth.tasks.ptbxl
====================

Overview
--------

Task helpers for PTB-XL multi-label ECG diagnosis. This module currently
implements the official **5-diagnostic-superclass** classification task
(``NORM``, ``MI``, ``STTC``, ``CD``, ``HYP``) by aggregating diagnostic SCP
statements via ``scp_statements.csv``.

Empty superclass label sets (≈400 records on v1.0.1, Wagner et al.
Table 9; mainly pacemaker ECGs) are dropped by default. On the full
v1.0.3 corpus, ``MultiLabelProcessor`` emits labels in alphabetical
order: ``CD, HYP, MI, NORM, STTC``. Official fold splitting is provided
by :func:`pyhealth.datasets.split_by_strat_fold`.

API Reference
-------------

.. automodule:: pyhealth.tasks.ptbxl
:members:
:undoc-members:
:show-inheritance:
79 changes: 79 additions & 0 deletions examples/ecg/ptbxl/ptbxl_superclass_quickstart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""PTB-XL 5-superclass multi-label quickstart.

Requires::

pip install 'pyhealth[ptbxl]'

Download PTB-XL v1.0.3 from https://physionet.org/content/ptb-xl/1.0.3/
and point ``--root`` at the extracted version directory (contains
``ptbxl_database.csv``, ``scp_statements.csv``, ``records100/``).

Example::

python examples/ecg/ptbxl/ptbxl_superclass_quickstart.py \\
--root /data/ptb-xl/1.0.3 --dev
"""

from __future__ import annotations

import argparse
from pathlib import Path

from pyhealth.datasets import PTBXLDataset, split_by_strat_fold
from pyhealth.tasks import PTBXLSuperclassClassification


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--root",
type=Path,
required=True,
help="PTB-XL version root (ptbxl_database.csv + records*/)",
)
parser.add_argument(
"--sampling-rate",
type=int,
default=100,
choices=(100, 500),
help="Waveform sampling rate (default: 100 Hz / filename_lr)",
)
parser.add_argument(
"--dev",
action="store_true",
help="Limit patients via BaseDataset.dev mode",
)
args = parser.parse_args()

dataset = PTBXLDataset(
root=str(args.root),
sampling_rate=args.sampling_rate,
dev=args.dev,
)
task = PTBXLSuperclassClassification(
scp_statements_path=str(args.root / "scp_statements.csv"),
)
samples = dataset.set_task(task)
# Without ``folds=``, fold values are read through ``samples[i]``, which
# decodes every stored sample, signal included, once before splitting.
train, val, test = split_by_strat_fold(samples)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] Quickstart decodes every sample to read fold numbers

split_by_strat_fold(samples) without folds= calls dataset[i] for every sample, which decodes all 12-lead signals just to read strat_fold. The inline comment explains this, but a quickstart could show the cheaper folds= path or note the cost. Non-blocking.

print(
f"samples={len(samples)} "
f"train={len(train)} val={len(val)} test={len(test)}"
)
if len(samples):
first = samples[0]
print(
"first sample:",
{
"patient_id": first["patient_id"],
"record_id": first["record_id"],
"labels": first["labels"],
"strat_fold": first["strat_fold"],
"signal_shape": tuple(first["signal"].shape),
},
)


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions pyhealth/datasets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ def __init__(self, *args, **kwargs):
split_by_sample_conformal,
split_by_sample_conformal_tuh,
split_by_sample_tuh,
split_by_strat_fold as split_by_strat_fold,
split_by_visit,
split_by_visit_conformal,
)
from .eegbci import EEGBCIDataset as EEGBCIDataset # noqa: E402
from .ptbxl import PTBXLDataset as PTBXLDataset
from .tuab import TUABDataset
from .tuev import TUEVDataset
from .utils import (
Expand Down
22 changes: 22 additions & 0 deletions pyhealth/datasets/configs/ptbxl.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: "1.0.3" # PhysioNet PTB-XL v1.0.3 only; earlier releases are not supported.
tables:
records:
# Template only: PTBXLDataset writes a resolved config whose file_path
# is the derived cache CSV (rate + data-root hash) before BaseDataset init.
file_path: "ptbxl-pyhealth-100hz.csv"
patient_id: "patient_id"
# recording_date from ptbxl_database.csv (e.g. 1984-11-09 09:17:34)
timestamp: "recording_date"
timestamp_format: "%Y-%m-%d %H:%M:%S"
attributes:
- "record_id"
- "signal_file"
- "sampling_rate"
- "strat_fold"
- "age"
- "age_is_censored"
- "age_is_missing"
- "sex" # 0 = male, 1 = female (Wagner et al., Sci Data 2020, Table 2)
- "site"
- "device"
- "scp_codes"
Loading
Loading