Repository navigation
Add PTB-XL dataset support (ECG) #1187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
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 f4cf33e
feat: add PTB-XL 5-superclass task and strat_fold split
AxelNoun 95ec5c4
test: add PTB-XL synthetic fixtures and end-to-end coverage
AxelNoun 2d10c69
docs: add PTB-XL API pages and quickstart example
AxelNoun 6e9ef58
fix(datasets): isolate PTB-XL cache identity and write metadata atomi…
AxelNoun 00f9591
fix(tasks): keep PTB-XL task cache keys stable and emit integer ages
AxelNoun 5a2ba11
refactor(datasets): avoid decoding samples in split_by_strat_fold
AxelNoun ce76939
test: cover PTB-XL set_task, cache isolation, and age sentinels
AxelNoun be6fb1c
docs: correct PTB-XL v1.0.3 counts, label order, and API toctree
AxelNoun b1e1891
fix(datasets): keep split_by_strat_fold re-export alias for ruff F401
AxelNoun a8fb194
fix(datasets): keep PTBXLDataset.root as the user data path
AxelNoun fe1007f
feat(datasets): add check_patient_disjoint to split_by_strat_fold
AxelNoun 7cc2b9d
Merge remote-tracking branch 'upstream/master' into feat/ptbxl-dataset
AxelNoun 0fccca0
docs(datasets): describe the post-#1265 cache key in PTBXLDataset
AxelNoun f4d5d07
Merge remote-tracking branch 'upstream/master' into feat/ptbxl-dataset
AxelNoun 2271703
Fusionner upstream/master (4a173f7) dans feat/ptbxl-dataset
AxelNoun a53669a
docs(ptbxl): correct sex encoding to 0 = male, 1 = female
AxelNoun 2cc8539
test(ptbxl): cover the 500 Hz records500 path
AxelNoun 5d874df
docs(ptbxl): clarify version support, cache-dir write order, example …
AxelNoun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
| 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() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.