physioex 2.0.2__tar.gz → 2.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- physioex-2.0.4/CHANGELOG.md +145 -0
- {physioex-2.0.2 → physioex-2.0.4}/PKG-INFO +3 -2
- {physioex-2.0.2 → physioex-2.0.4}/README.md +2 -1
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/data.md +3 -2
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/data.md +25 -2
- physioex-2.0.4/physioex/__init__.py +1 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/__init__.py +4 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/base.py +107 -16
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/__init__.py +3 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/alzheimers.py +10 -1
- physioex-2.0.4/physioex/data/datasets/ccshs.py +200 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/channels_table.md +25 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/sleepedf.py +7 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/stages.py +2 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/wsc.py +200 -69
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/events.py +1 -1
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/modality.py +4 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/readers/annotations.py +5 -3
- physioex-2.0.4/physioex/data/sampling.py +91 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_datasets.py +8 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/protosleepnet.py +14 -3
- {physioex-2.0.2 → physioex-2.0.4}/tests/data/test_modality.py +10 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_alzheimers_parkinsons.py +21 -7
- physioex-2.0.4/tests/test_ccshs.py +181 -0
- physioex-2.0.4/tests/test_channel_acc_tracking.py +87 -0
- physioex-2.0.4/tests/test_data_layer_hardening.py +218 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_phase_b_datasets.py +14 -0
- physioex-2.0.4/tests/test_sampling.py +55 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_wsc_dataset.py +156 -0
- physioex-2.0.2/CHANGELOG.md +0 -73
- physioex-2.0.2/physioex/__init__.py +0 -1
- {physioex-2.0.2 → physioex-2.0.4}/.claudeignore +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/.github/copilot-instructions.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/.github/workflows/black.yml +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/.github/workflows/ci.yml +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/.github/workflows/publish.yaml +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/.github/workflows/test.yml +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/.gitignore +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/LICENSE +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/MANIFEST.in +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/REPO_MAP.txt +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/_ext/related_works.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/_static/custom.css +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/api/index.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/assets/images/data/sequence_viz.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/assets/images/logo.psd +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/assets/images/logo.svg +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/assets/images/logo_bar.svg +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/conf.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/index.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/explain.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/models.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/overview.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/train.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/testing/overview.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/contribute.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/explain/explain.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/preprocess.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/train/cli.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/train/networks/chambon2018.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/train/networks.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/train/train.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/unavailable.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/pages/user_guide.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/related/_thumbs/_placeholder.svg +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/related/fetch_citations.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/related/index.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/docs/related/works.yaml +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/compare_strategies.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/extract_all_concepts.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/report.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/utils.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/visualize_concepts.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/README.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/explain_csd.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/train_probe.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/utils.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/visualize.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/explain/lrp_seqsleepnet_n3.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/bendr/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/biot/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/cbramod/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/labram/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/neurolm/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/reve/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/sjepa/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/sleepfm/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/tfc/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/extract_embeddings.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/quickstart.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/smoke.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/test_pretrained.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/train.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/train.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/coresleep-kontras/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/coresleep-kontras/train.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/quickstart.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/smoke.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/test_pretrained.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/test_pretrained.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/train.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/train.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/protosleepnet/README.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/protosleepnet/quickstart.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/extract_embeddings.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/quickstart.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/test.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/test_pretrained.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/test_pretrained.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/test_stages_sites.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/train.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/train.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/train_shhs.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/train_shhs.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/extract_embeddings.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/quickstart.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/smoke.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/test_pretrained.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/test_pretrained.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/train.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/train.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/quickstart.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/smoke.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/test_pretrained.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/test_pretrained.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/train.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/train.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/extract_embeddings.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/quickstart.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/smoke.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/test_pretrained.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/train.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/train.sbatch +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/notebook.ipynb +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_barplot.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_hypnogram.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_barplot.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_hypnogram.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell17_ig_tiny.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell19_ig_seq.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell19b_attr_vs_psd.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell5_raw_n3_psd.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell7_spec_n3_psd.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/run_notebook.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/script.pdf +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/slides.pdf +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/util.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/README.md +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/assets/kuleuven_logo.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/assets/physioex_logo.png +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/lectures/assets/physioex_logo.svg +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/cache.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/collate.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datareader.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/dataset.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/_nsrr.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/_splits/shhs.json +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/dcsm.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/hmc.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/hpap.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/mass.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/mesa.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/mros.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/parkinsons.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/shhs.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/multi.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/pipeline.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/presets.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/readers/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/readers/edf.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/bandpass.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/highpass.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/identity.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/normalize.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/notch.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/resample.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/spectrogram.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/csd.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/multichannel_sg.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/report.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/sleep_bands.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/specificity.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/_functional.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/_rules.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/attributor.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/canonizers.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/composites.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/diagnostics.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/model.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/pooling.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/recurrent.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/transformer.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/filters.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/functionizer.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/gradients.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/metrics/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/spectralgradients.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/vidft.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/vistdft.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/local.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/posthoc/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/posthoc/nmf.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/posthoc/utils.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/posthoc/vq.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/reconstruct.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/bendr.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/biot.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/cbramod.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/chambon2018.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/coresleep.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/embed.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_base.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_channel_mapping.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_checkpoints.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_preproc.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/labram.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/lseqsleepnet.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/neurolm.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/pretrained.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/reve.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/seqsleepnet.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/sjepa.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/sleep_tokenizer.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/sleepfm.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/sleeptransformer.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/tfc.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/tinysleepnet.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/models/tsinalis.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/_common.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/finetune.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/test.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/train.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/logger.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/metrics.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/models/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/models/check_table.csv +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/models/load.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/multidevicetrainer.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/progress.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/stats.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/physioex/train/trainer.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/pyproject.toml +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/conftest.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/data/test_base_smoke.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/data/test_multi.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/data/test_readers_annotations.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/foundational/test_sleep_bands.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/foundational/test_specificity.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_functional.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_lrp.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_model.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_real_models.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_recurrent.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_transformer.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_gradients.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_metrics.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_spectralgradients.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_vidft.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_vistdft.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/explain/prototypes/posthoc/test_nmf_vq.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/factories/__init__.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/factories/edf.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_architectures.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_foundation_base.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_foundation_encoders.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_foundation_preproc.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_pretrained_embed_helpers.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_sleep_tokenizer.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_api_surface.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_cache.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_cache_enabled.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_channel_resolution.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_cli_workflows.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_dataset_config.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_e2e_smoke.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_events.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_explain_fixes.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_label_hardening.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_load_model.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_mass_dataset.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_metrics_and_packaging.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_modality_dropout.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_phase_c_datasets.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_pipeline.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_presets.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_raw_dataset_integration.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_reading_modes.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_stages_dataset.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_trainer_enhancements.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_trainer_fixes.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_trainer_integration.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/test_wake_trimming.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/train/test_logger.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/train/test_progress.py +0 -0
- {physioex-2.0.2 → physioex-2.0.4}/tests/train/test_stats.py +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to PhysioEx are documented here. Versions follow the
|
|
4
|
+
`physioex.__version__` string (read dynamically by the build) and the `v*` git tags
|
|
5
|
+
that publish to PyPI.
|
|
6
|
+
|
|
7
|
+
## 2.0.4 — 2026-10-16
|
|
8
|
+
|
|
9
|
+
Data-layer fixes found by an audit of what physioex reads against the raw files of every dataset. Label caches of
|
|
10
|
+
Sleep-EDF, WSC and Alzheimers are recomputed on first use (`LABELS_REVISION` 2, 2 and 3). Cached WSC events are not
|
|
11
|
+
versioned: delete `events/<subject>.json` of the 26 allscore nights whose events changed.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **WSC `.allscore.txt`:** labels and events are anchored at `START RECORDING`. A `STAGE - NO STAGE` line timed before it
|
|
15
|
+
moved the time origin back by hours and shifted every later label (45 of 743 files). Midnight is a backward jump of
|
|
16
|
+
the clock, `PAUSED` to `RECORDING RESUMED` stretches are removed (the EDF has no samples there), and labels or events
|
|
17
|
+
past the end of the EDF are dropped. A file without `START RECORDING` yields no labels, as before.
|
|
18
|
+
- **Sleep-EDF:** the hypnogram always runs to 24 h; its tail is cut at the end of the PSG instead of being paired with
|
|
19
|
+
zero padding (in one night 614 epochs of `Wake`).
|
|
20
|
+
- **Alzheimers:** labels past the end of a truncated EDF are dropped.
|
|
21
|
+
- **MESA:** `*_Off` electrode-status flags (1 Hz) are classified `OTHER`, not EEG/EOG/EMG.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **WSC events from `.sco.txt`** (the 1827 Gamma-system nights; only the 743 Twin-system `.allscore.txt` nights had
|
|
25
|
+
events). Same event types as the allscore parser. `Scan # x2` counts samples at twice the EDF rate; a few files write
|
|
26
|
+
the scan with two extra digits (the clock column is used) and 548 write the length ×100 with a negative sign.
|
|
27
|
+
|
|
28
|
+
## 2.0.3 — 2026-10-15
|
|
29
|
+
|
|
30
|
+
CCSHS dataset, opt-in utilities for multi-dataset training, and three data-layer fixes. STAGES and Alzheimers label
|
|
31
|
+
caches are recomputed on first use, and event counts per type change (see "`artifact` event type").
|
|
32
|
+
|
|
33
|
+
### Added — CCSHS dataset (`physioex.data.datasets.ccshs`)
|
|
34
|
+
|
|
35
|
+
`get_dataset("ccshs")`: Cleveland Children's Sleep and Health Study (NSRR `ccshs-trec`, 515 recordings). EEG
|
|
36
|
+
`(C3, A2)` / `(C4, A1)` and EOG `(LOC, ROC)` at 128 Hz, chin EMG at 256 Hz; stage 3 and 4 merged to N3,
|
|
37
|
+
Movement/unscored to -1, labels indexed by event onset, unknown stage vocabulary raises. Eleven recordings are
|
|
38
|
+
excluded by default (`CCSHSDataset.EXCLUDED_SUBJECTS`, `exclude_subjects=[]` keeps them): nine whose stage minutes
|
|
39
|
+
disagree with the NSRR summary table and two without a row in it. Metadata is read from
|
|
40
|
+
`datasets/ccshs-trec-dataset-*.csv` whatever its version. Tests in `tests/test_ccshs.py`.
|
|
41
|
+
|
|
42
|
+
### Added — site-balanced sampling (`physioex.data.sampling`)
|
|
43
|
+
|
|
44
|
+
`site_balanced_weights` / `make_site_sampler` (also exported from `physioex.data`): sampling weights that give every
|
|
45
|
+
site the same mass and, inside a site, every subject the same mass, for training on collections of datasets of very
|
|
46
|
+
different sizes with `MultiDataset`. Tests in `tests/test_sampling.py`.
|
|
47
|
+
|
|
48
|
+
### Added — opt-in channel-accuracy tracking during training (`ChannelMixer`)
|
|
49
|
+
|
|
50
|
+
`ChannelMixer.track_train_acc` and `acc_momentum` update the per-channel accuracy that drives the accuracy-weighted
|
|
51
|
+
modality dropout from the training step too (EMA of the clean per-channel logits). Off by default, so the released
|
|
52
|
+
models and existing training runs behave exactly as before. Tests in `tests/test_channel_acc_tracking.py`.
|
|
53
|
+
|
|
54
|
+
### Fixed — subjects without any requested channel (`BasePhysioDataset`)
|
|
55
|
+
|
|
56
|
+
A subject for which none of the requested channels can be resolved was returned as a block of zeros with the wrong
|
|
57
|
+
shape (`(L,)` instead of `(L, F, T)`), which made `dict_collate_fn` fail on any batch containing it. Its zeros now have
|
|
58
|
+
the per-epoch shape of the pipeline output, like the zero-filled channels of subjects that miss only some channels, and
|
|
59
|
+
a warning names the subject. Subjects are still never dropped.
|
|
60
|
+
|
|
61
|
+
### Fixed — stale cached labels (`BasePhysioDataset.LABELS_REVISION`)
|
|
62
|
+
|
|
63
|
+
Cached labels had no version, so after a fix of a label reader the old caches kept serving the wrong labels without any
|
|
64
|
+
warning. Datasets now declare `LABELS_REVISION` (default 1); the revision is stored with the cached labels and a cache
|
|
65
|
+
with a lower revision is recomputed (with a warning), or used with a warning when the source files are not available.
|
|
66
|
+
Caches without the key count as revision 1, so existing caches of unchanged readers stay valid. `STAGESDataset` and
|
|
67
|
+
`AlzheimersDataset` are at revision 2 (timestamp-based alignment of STAGES labels, onset-indexed Alzheimer labels):
|
|
68
|
+
caches written before those fixes are recomputed on first use. Cached EDF headers that predate the `start_sec` field
|
|
69
|
+
(STAGES labels are aligned to the EDF start time with it) are probed again, or used with a warning when the EDF is not
|
|
70
|
+
available.
|
|
71
|
+
|
|
72
|
+
### Changed — `artifact` event type
|
|
73
|
+
|
|
74
|
+
"SpO2 artifact" intervals (an unreliable oximeter signal, not a physiological event) were counted as `respiratory` by
|
|
75
|
+
the NSRR parser and as `desaturation` by the STAGES parser. They now have their own type `artifact`; the concept text is
|
|
76
|
+
unchanged. Event counts per type change accordingly, for example in CCSHS `respiratory` drops from 51 915 to 15 040
|
|
77
|
+
and `artifact` has 36 875.
|
|
78
|
+
|
|
79
|
+
## 2.0.2 — 2026-10-10
|
|
80
|
+
|
|
81
|
+
### Fixed — accuracy-weighted modality dropout (`physioex.models.protosleepnet`)
|
|
82
|
+
|
|
83
|
+
`ZeroEmbeddingDropout` now samples the channels to occlude with probability
|
|
84
|
+
proportional to their accuracy, `p_c = a_c / sum_j a_j`, so the channels the
|
|
85
|
+
model relies on most are dropped most often. This is the rule the released
|
|
86
|
+
ProtoSleepNet and ProtoSleepTransformer models were trained with; a later
|
|
87
|
+
refactoring had inverted the sampling weights to `1 - a_c`. When no accuracy
|
|
88
|
+
estimate is available yet the sampling is uniform. Unit tests in
|
|
89
|
+
`tests/test_modality_dropout.py`.
|
|
90
|
+
|
|
91
|
+
## 2.0.1 — 2026-09-13
|
|
92
|
+
|
|
93
|
+
### Added — Layer-wise Relevance Propagation (`physioex.explain.lrp`)
|
|
94
|
+
|
|
95
|
+
A conservation-based attribution family (Bach et al. 2015) covering the whole
|
|
96
|
+
model zoo. Install with the new optional extra `pip install "physioex[explain]"`
|
|
97
|
+
(Zennit only; no LXT dependency).
|
|
98
|
+
|
|
99
|
+
- **Two entry points**, both seeding the target neuron with its logit so that
|
|
100
|
+
`Σ R ≈ f_c(x)`, both returning a per-sample `ConservationReport` with
|
|
101
|
+
`return_report=True`:
|
|
102
|
+
- `LRP` — Zennit composites for feed-forward / CNN models (Tsinalis,
|
|
103
|
+
Chambon2018): ε on dense, γ on convolutions, **w²** on the first layer
|
|
104
|
+
(unbounded EEG, not the pixel z-box), BatchNorm canonized; αβ / flat / z-box
|
|
105
|
+
configurable via `physioex_composite`.
|
|
106
|
+
- `ModelLRP` — whole-model LRP for recurrent, attention and transformer
|
|
107
|
+
architectures (TinySleepNet, SeqSleepNet, L-SeqSleepNet, SleepTransformer,
|
|
108
|
+
CoReSleep, ProtoSleepNet): **Arras signal-take** rule for LSTM/GRU at cell
|
|
109
|
+
level (`LRPLSTM`/`LRPGRU`, drop-in subclasses with a bit-identical forward,
|
|
110
|
+
`gate_rule="signal_take" | "uniform"`), **CP-LRP** for attention (value
|
|
111
|
+
path, exact conservation; Ali et al. 2022), identity rule for
|
|
112
|
+
LayerNorm/activations, proportional rule for residual sums, ε for linear
|
|
113
|
+
leaves and PhysioEx's learnable filterbank, CP-LRP adapters for the softmax
|
|
114
|
+
poolings (`AttentionPooling`, `AttentionLayer`, `ChannelMixer`).
|
|
115
|
+
- `prepare_model_for_lrp` rewrites a deep copy of the model; plain `+` residuals
|
|
116
|
+
written in a model's own `forward` are redirected to the proportional rule at
|
|
117
|
+
runtime (`patch_residuals`); `audit_lrp_coverage` / `strict=True` report any
|
|
118
|
+
parametric leaf left on plain autograd; `register_lrp_adapter` adds custom
|
|
119
|
+
blocks. Signed, dtype-safe stabiliser `z + ε·sign z` everywhere (fp16-safe).
|
|
120
|
+
- Docs: entry-point/rule matrix, how to read conservation and choose ε (ε must be
|
|
121
|
+
scaled to the activations: measured on the pretrained SeqSleepNet, 1e-6 inflates
|
|
122
|
+
relevance 2–3×, 1e-2 conserves), unsupported cases, and a literature note on
|
|
123
|
+
LRP vs path-based attribution (Deep Taylor, Integrated Gradients,
|
|
124
|
+
DeepLIFT/DeepSHAP). `physioex.explain.lrp` is in the API reference.
|
|
125
|
+
- Example: `examples/explain/lrp_seqsleepnet_n3.py` explains the N3 logit of the
|
|
126
|
+
pretrained `seqsleepnet-phan` on MASS training sequences with Saliency,
|
|
127
|
+
Input×Gradient, Integrated Gradients and LRP under different rule assignments.
|
|
128
|
+
- Tests: 91 new tests (`tests/explain/lrp/`), including forward-equivalence to
|
|
129
|
+
the fused modules, exact conservation on bias-free blocks and models, and all
|
|
130
|
+
six real architectures end-to-end with full rule coverage.
|
|
131
|
+
|
|
132
|
+
### Known limitations
|
|
133
|
+
|
|
134
|
+
CP-LRP assigns zero relevance to the attention query/key path by design (full
|
|
135
|
+
AttnLRP is not implemented); bias relevance is absorbed (LRP-ε convention);
|
|
136
|
+
attention masks, `batch_first=False` transformers, explicit RNN initial states
|
|
137
|
+
and `proj_size` raise; ProtoSleepNet's VQ path runs under `no_grad`. Foundation
|
|
138
|
+
encoders and frequency-domain (DFT/STFT) LRP are planned follow-ups.
|
|
139
|
+
|
|
140
|
+
## 2.0.0 — 2026-07-15
|
|
141
|
+
|
|
142
|
+
Ground-up release: raw-EDF lazy data layer with 12 dataset loaders, 9 pretrained
|
|
143
|
+
foundation encoders behind a uniform `encode()` interface, hand-written `Trainer`,
|
|
144
|
+
pytest suite and Sphinx documentation. Full notes in the
|
|
145
|
+
[v2.0.0 GitHub Release](https://github.com/guidogagl/physioex/releases/tag/v2.0.0).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: physioex
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: A python package for explainable sleep staging via deep learning
|
|
5
5
|
Project-URL: Homepage, https://github.com/guidogagl/physioex
|
|
6
6
|
Project-URL: Documentation, https://guidogagl.github.io/physioex
|
|
@@ -105,7 +105,7 @@ linear probing): `CBraMod`, `BENDR`, `LaBraM`, `BIOT`, `SleepFM`, `TFC`, `REVE`,
|
|
|
105
105
|
## Supported datasets
|
|
106
106
|
|
|
107
107
|
All datasets are resolved by name via `get_dataset(name)`; `available_datasets()` returns:
|
|
108
|
-
`alzheimers`, `dcsm`, `hmc`, `hpap`, `mass`, `mesa`, `mros`, `parkinsons`, `shhs`, `sleepedf`, `stages`, `wsc`.
|
|
108
|
+
`alzheimers`, `ccshs`, `dcsm`, `hmc`, `hpap`, `mass`, `mesa`, `mros`, `parkinsons`, `shhs`, `sleepedf`, `stages`, `wsc`.
|
|
109
109
|
|
|
110
110
|
### Publicly Available:
|
|
111
111
|
|
|
@@ -125,6 +125,7 @@ These datasets can be obtained from the NSRR archive. Once downloaded, point Phy
|
|
|
125
125
|
- [MrOS (The Osteoporotic Fractures in Men Study)](https://sleepdata.org/datasets/mros), is a multicenter study comprising 2,911 PSG recordings from men aged 65 years or older, enrolled at six clinical centers. PSG recordings were conducted in home settings and included C3/A2 and C4/A1 EEGs, chin EMG, and left-right EOG, all sampled at 256 Hz.
|
|
126
126
|
- [WSC (The Wisconsin Sleep Cohort)](https://sleepdata.org/datasets/wsc) A longitudinal study of the causes, consequences, and natural history of sleep disorders using overnight in-laboratory sleep recordings gathered at the University of Wisconsin, United States, with a baseline sample of 1,500 subjects assessed at four-year intervals. The study consists of multiple visits with overnight PSG data acquisition. PSG recordings included C3/M2 EEG, EMG, and left-right EOG, all sampled at 200 Hz.
|
|
127
127
|
- [STAGES (Stanford Technology Analytics and Genomics in Sleep)](https://sleepdata.org/datasets/stages), 1,914 recordings across 13 clinical sites with a bipolar EEG montage (C4-M1 / C3-M2 equivalent), EOG and chin EMG, plus rich respiratory / arousal / PLM event annotations. Selectable by site and by recording (`first` / `second` / `all`).
|
|
128
|
+
- [CCSHS (Cleveland Children's Sleep and Health Study)](https://sleepdata.org/datasets/ccshs) (`ccshs`), 515 children and adolescents with one in-home PSG each (NSRR `ccshs-trec`): C3/A2 and C4/A1 EEG (128 Hz), LOC/ROC EOG (128 Hz) and chin EMG (256 Hz), stages scored by Rechtschaffen & Kales and merged to N3. Eleven recordings whose hypnogram cannot be reconciled with the NSRR summary table are excluded by default (`exclude_subjects=[]` keeps them).
|
|
128
129
|
- [HomePAP](https://sleepdata.org/datasets/homepap) (`hpap`), a positive-airway-pressure study in NSRR format, with three subsets — `lab-full`, `lab-split` and `home` — selectable via the `subset` parameter.
|
|
129
130
|
|
|
130
131
|
### Others
|
|
@@ -46,7 +46,7 @@ linear probing): `CBraMod`, `BENDR`, `LaBraM`, `BIOT`, `SleepFM`, `TFC`, `REVE`,
|
|
|
46
46
|
## Supported datasets
|
|
47
47
|
|
|
48
48
|
All datasets are resolved by name via `get_dataset(name)`; `available_datasets()` returns:
|
|
49
|
-
`alzheimers`, `dcsm`, `hmc`, `hpap`, `mass`, `mesa`, `mros`, `parkinsons`, `shhs`, `sleepedf`, `stages`, `wsc`.
|
|
49
|
+
`alzheimers`, `ccshs`, `dcsm`, `hmc`, `hpap`, `mass`, `mesa`, `mros`, `parkinsons`, `shhs`, `sleepedf`, `stages`, `wsc`.
|
|
50
50
|
|
|
51
51
|
### Publicly Available:
|
|
52
52
|
|
|
@@ -66,6 +66,7 @@ These datasets can be obtained from the NSRR archive. Once downloaded, point Phy
|
|
|
66
66
|
- [MrOS (The Osteoporotic Fractures in Men Study)](https://sleepdata.org/datasets/mros), is a multicenter study comprising 2,911 PSG recordings from men aged 65 years or older, enrolled at six clinical centers. PSG recordings were conducted in home settings and included C3/A2 and C4/A1 EEGs, chin EMG, and left-right EOG, all sampled at 256 Hz.
|
|
67
67
|
- [WSC (The Wisconsin Sleep Cohort)](https://sleepdata.org/datasets/wsc) A longitudinal study of the causes, consequences, and natural history of sleep disorders using overnight in-laboratory sleep recordings gathered at the University of Wisconsin, United States, with a baseline sample of 1,500 subjects assessed at four-year intervals. The study consists of multiple visits with overnight PSG data acquisition. PSG recordings included C3/M2 EEG, EMG, and left-right EOG, all sampled at 200 Hz.
|
|
68
68
|
- [STAGES (Stanford Technology Analytics and Genomics in Sleep)](https://sleepdata.org/datasets/stages), 1,914 recordings across 13 clinical sites with a bipolar EEG montage (C4-M1 / C3-M2 equivalent), EOG and chin EMG, plus rich respiratory / arousal / PLM event annotations. Selectable by site and by recording (`first` / `second` / `all`).
|
|
69
|
+
- [CCSHS (Cleveland Children's Sleep and Health Study)](https://sleepdata.org/datasets/ccshs) (`ccshs`), 515 children and adolescents with one in-home PSG each (NSRR `ccshs-trec`): C3/A2 and C4/A1 EEG (128 Hz), LOC/ROC EOG (128 Hz) and chin EMG (256 Hz), stages scored by Rechtschaffen & Kales and merged to N3. Eleven recordings whose hypnogram cannot be reconciled with the NSRR summary table are excluded by default (`exclude_subjects=[]` keeps them).
|
|
69
70
|
- [HomePAP](https://sleepdata.org/datasets/homepap) (`hpap`), a positive-airway-pressure study in NSRR format, with three subsets — `lab-full`, `lab-split` and `home` — selectable via the `subset` parameter.
|
|
70
71
|
|
|
71
72
|
### Others
|
|
@@ -67,13 +67,14 @@ classDiagram
|
|
|
67
67
|
_NSRRBaseDataset <|-- MrOSDataset
|
|
68
68
|
_NSRRBaseDataset <|-- SHHSDataset
|
|
69
69
|
_NSRRBaseDataset <|-- HPAPDataset
|
|
70
|
+
_NSRRBaseDataset <|-- CCSHSDataset
|
|
70
71
|
MultiDataset o-- "N" BasePhysioDataset
|
|
71
72
|
BasePhysioDataset ..> SubjectSpec : yields
|
|
72
73
|
```
|
|
73
74
|
|
|
74
|
-
The
|
|
75
|
+
The 13 concrete datasets are resolved by name from a **string registry** in
|
|
75
76
|
`physioex/data/datasets/__init__.py` — `get_dataset(name)` / `available_datasets()`;
|
|
76
|
-
keys: `hmc, sleepedf, dcsm, mesa, mros, hpap, wsc, mass, alzheimers, parkinsons, shhs, stages`.
|
|
77
|
+
keys: `hmc, sleepedf, dcsm, mesa, mros, hpap, ccshs, wsc, mass, alzheimers, parkinsons, shhs, stages`.
|
|
77
78
|
|
|
78
79
|
### Legacy (deprecated)
|
|
79
80
|
|
|
@@ -19,13 +19,13 @@ see the [data-layer architecture](architecture/library/data.md) page.
|
|
|
19
19
|
|
|
20
20
|
## Resolving a dataset by name
|
|
21
21
|
|
|
22
|
-
`physioex.data.datasets` exposes a registry of the
|
|
22
|
+
`physioex.data.datasets` exposes a registry of the 13 concrete datasets:
|
|
23
23
|
|
|
24
24
|
```python
|
|
25
25
|
from physioex.data.datasets import get_dataset, available_datasets
|
|
26
26
|
|
|
27
27
|
available_datasets()
|
|
28
|
-
# ['alzheimers', 'dcsm', 'hmc', 'hpap', 'mass', 'mesa', 'mros',
|
|
28
|
+
# ['alzheimers', 'ccshs', 'dcsm', 'hmc', 'hpap', 'mass', 'mesa', 'mros',
|
|
29
29
|
# 'parkinsons', 'shhs', 'sleepedf', 'stages', 'wsc']
|
|
30
30
|
|
|
31
31
|
HMCDataset = get_dataset("hmc") # the class, not an instance
|
|
@@ -98,6 +98,29 @@ dcsm = get_dataset("dcsm")(channels=["EEG"], pipelines="raw", sequence_length=21
|
|
|
98
98
|
merged = MultiDataset([hmc, dcsm])
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
### Balancing sites with very different sizes
|
|
102
|
+
|
|
103
|
+
When the merged datasets differ a lot in size, uniform sampling lets the largest one dominate. `make_site_sampler`
|
|
104
|
+
gives every *site* the same total probability mass and, inside a site, every *subject* the same mass:
|
|
105
|
+
`p(window) ∝ 1 / (N_s · W_u)` with `N_s` the number of training subjects of the site and `W_u` the number of windows of
|
|
106
|
+
the subject. Several wrapped datasets can form one site (for example the visits of one cohort): give them the same
|
|
107
|
+
name in `site_of_dataset`.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from torch.utils.data import DataLoader, Subset
|
|
111
|
+
from physioex.data import dict_collate_fn, make_site_sampler
|
|
112
|
+
|
|
113
|
+
train_idx = ... # flat MultiDataset indices of the training windows
|
|
114
|
+
sampler, report = make_site_sampler(
|
|
115
|
+
merged, train_idx, site_of_dataset=["hmc", "dcsm"], num_samples=100_000, seed=0
|
|
116
|
+
)
|
|
117
|
+
loader = DataLoader(Subset(merged, train_idx), batch_size=32, sampler=sampler, collate_fn=dict_collate_fn)
|
|
118
|
+
report["hmc"] # {'subjects': ..., 'windows': ..., 'mass': 0.5}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`site_balanced_weights(merged, train_idx, site_of_dataset)` returns the weights (summing to 1) if you want to build
|
|
122
|
+
your own sampler.
|
|
123
|
+
|
|
101
124
|
## Building a DataLoader
|
|
102
125
|
|
|
103
126
|
Because items are dicts, use `dict_collate_fn` as the loader's `collate_fn`:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.4"
|
|
@@ -29,6 +29,7 @@ from physioex.data.steps import (
|
|
|
29
29
|
from physioex.data.presets import get_preset, available_presets, PRESETS
|
|
30
30
|
from physioex.data.base import BasePhysioDataset, SubjectSpec
|
|
31
31
|
from physioex.data.multi import MultiDataset
|
|
32
|
+
from physioex.data.sampling import site_balanced_weights, make_site_sampler
|
|
32
33
|
from physioex.data.cache import ChannelCache, SCHEMA_VERSION
|
|
33
34
|
from physioex.data.collate import dict_collate_fn, stack_channels, is_dict_batch
|
|
34
35
|
from physioex.data.readers import (
|
|
@@ -67,6 +68,9 @@ __all__ = [
|
|
|
67
68
|
"BasePhysioDataset",
|
|
68
69
|
"SubjectSpec",
|
|
69
70
|
"MultiDataset",
|
|
71
|
+
# Sampling
|
|
72
|
+
"site_balanced_weights",
|
|
73
|
+
"make_site_sampler",
|
|
70
74
|
# Cache
|
|
71
75
|
"ChannelCache",
|
|
72
76
|
"SCHEMA_VERSION",
|
|
@@ -105,6 +105,11 @@ class BasePhysioDataset(Dataset):
|
|
|
105
105
|
# Epoch length in seconds (30 for humans, 4 for mouse)
|
|
106
106
|
DEFAULT_EPOCH_LENGTH_SEC: float = 30.0
|
|
107
107
|
|
|
108
|
+
#: Revision of the label reader. Cached labels store the revision they were computed with; a cache with a
|
|
109
|
+
#: LOWER revision is stale (the reader changed how labels are built) and is recomputed. Caches without the
|
|
110
|
+
#: key count as revision 1. Bump it in a subclass whenever a fix changes the labels it returns.
|
|
111
|
+
LABELS_REVISION: int = 1
|
|
112
|
+
|
|
108
113
|
def __init__(
|
|
109
114
|
self,
|
|
110
115
|
root: Optional[str] = None,
|
|
@@ -594,6 +599,7 @@ class BasePhysioDataset(Dataset):
|
|
|
594
599
|
"""Return the EDF header for a subject, using on-disk cache when
|
|
595
600
|
valid. When ``self.cache_enabled`` is False, the header is always
|
|
596
601
|
probed from the source file and no disk I/O is performed."""
|
|
602
|
+
stale_header = None
|
|
597
603
|
if self.cache_enabled:
|
|
598
604
|
hpath = self.cache.header_path(self.DATASET_NAME, spec.subject_id)
|
|
599
605
|
src_mtime = None
|
|
@@ -611,11 +617,24 @@ class BasePhysioDataset(Dataset):
|
|
|
611
617
|
or abs(float(cached.get("source_mtime", 0)) - src_mtime)
|
|
612
618
|
< 1e-3
|
|
613
619
|
):
|
|
614
|
-
|
|
620
|
+
if "start_sec" in cached:
|
|
621
|
+
return EDFHeader.from_dict(cached)
|
|
622
|
+
# Written before ``start_sec`` existed (label readers such as STAGES need it
|
|
623
|
+
# to align the hypnogram to the EDF start): probe the header again.
|
|
624
|
+
stale_header = cached
|
|
615
625
|
except Exception:
|
|
616
626
|
pass
|
|
617
627
|
|
|
618
|
-
|
|
628
|
+
try:
|
|
629
|
+
header = self._read_edf_header(spec)
|
|
630
|
+
except Exception as exc:
|
|
631
|
+
if stale_header is None:
|
|
632
|
+
raise
|
|
633
|
+
logger.warning(
|
|
634
|
+
f"[{self.DATASET_NAME}] {spec.subject_id}: cached header predates the 'start_sec' field and "
|
|
635
|
+
f"cannot be refreshed ({type(exc).__name__}: {exc}); using the cached header"
|
|
636
|
+
)
|
|
637
|
+
return EDFHeader.from_dict(stale_header)
|
|
619
638
|
|
|
620
639
|
if self.cache_enabled:
|
|
621
640
|
try:
|
|
@@ -634,8 +653,10 @@ class BasePhysioDataset(Dataset):
|
|
|
634
653
|
unconditionally available; subjects missing a channel get it
|
|
635
654
|
zero-filled at access time.
|
|
636
655
|
|
|
637
|
-
Subjects are **never dropped** due to missing channels
|
|
638
|
-
|
|
656
|
+
Subjects are **never dropped** due to missing channels (missing
|
|
657
|
+
channels are zero-filled with the shape the pipeline produces; a
|
|
658
|
+
subject with none of the requested channels gets a warning and all
|
|
659
|
+
zeros). Only genuinely corrupt EDFs (header unreadable) are skipped
|
|
639
660
|
when ``skip_corrupt=True``.
|
|
640
661
|
"""
|
|
641
662
|
# Phase 1: probe all headers
|
|
@@ -692,6 +713,13 @@ class BasePhysioDataset(Dataset):
|
|
|
692
713
|
f"{missing} not available (will be zero-filled)"
|
|
693
714
|
)
|
|
694
715
|
|
|
716
|
+
if self.channels and all(rc is None for rc in resolved):
|
|
717
|
+
warnings.warn(
|
|
718
|
+
f"[{self.DATASET_NAME}] Subject {spec.subject_id}: none of the requested channels "
|
|
719
|
+
f"{list(self.channels)} is available (EDF channels: {list(header.available_channels)}); "
|
|
720
|
+
f"its signals will be all zeros"
|
|
721
|
+
)
|
|
722
|
+
|
|
695
723
|
self._resolved[spec.subject_id] = resolved
|
|
696
724
|
|
|
697
725
|
# ------------------------------------------------------------------
|
|
@@ -711,30 +739,93 @@ class BasePhysioDataset(Dataset):
|
|
|
711
739
|
if lp.exists() and lmeta.exists():
|
|
712
740
|
try:
|
|
713
741
|
memmap, meta = self.cache.load_memmap(lp)
|
|
714
|
-
|
|
742
|
+
cached = np.asarray(memmap).astype(np.int64, copy=False)
|
|
743
|
+
cached_rev = int((meta or {}).get("labels_revision", 1))
|
|
744
|
+
if cached_rev >= self.LABELS_REVISION:
|
|
745
|
+
return cached
|
|
746
|
+
stale_rev = cached_rev
|
|
715
747
|
except Exception as exc:
|
|
716
748
|
logger.warning(
|
|
717
749
|
f"Corrupt label cache for {spec.subject_id}: {exc}; "
|
|
718
750
|
f"rebuilding"
|
|
719
751
|
)
|
|
752
|
+
stale_rev = None
|
|
753
|
+
if stale_rev is not None:
|
|
754
|
+
# cache written by an older label reader: recompute if the source is available
|
|
755
|
+
try:
|
|
756
|
+
fresh = self._sanitize_labels(
|
|
757
|
+
np.asarray(self._read_subject_labels(spec), dtype=np.int16)
|
|
758
|
+
)
|
|
759
|
+
except Exception as exc:
|
|
760
|
+
logger.warning(
|
|
761
|
+
f"[{self.DATASET_NAME}] {spec.subject_id}: cached labels have revision "
|
|
762
|
+
f"{stale_rev} < {self.LABELS_REVISION} (stale) and cannot be recomputed "
|
|
763
|
+
f"({type(exc).__name__}: {exc}); using the cached labels"
|
|
764
|
+
)
|
|
765
|
+
return cached
|
|
766
|
+
if not getattr(self, "_warned_stale_labels", False):
|
|
767
|
+
logger.warning(
|
|
768
|
+
f"[{self.DATASET_NAME}] cached labels have revision {stale_rev} < "
|
|
769
|
+
f"{self.LABELS_REVISION} (the label reader changed): recomputing them"
|
|
770
|
+
)
|
|
771
|
+
self._warned_stale_labels = True
|
|
772
|
+
self._save_labels_cache(lp, spec, fresh)
|
|
773
|
+
return fresh.astype(np.int64, copy=False)
|
|
720
774
|
|
|
721
775
|
labels = self._read_subject_labels(spec)
|
|
722
776
|
labels = self._sanitize_labels(np.asarray(labels, dtype=np.int16))
|
|
723
777
|
|
|
724
778
|
if self.cache_enabled:
|
|
725
|
-
|
|
726
|
-
"dataset": self.DATASET_NAME,
|
|
727
|
-
"subject_id": spec.subject_id,
|
|
728
|
-
"n_epochs": int(labels.shape[0]),
|
|
729
|
-
"sanitized_to": "AASM_5_class",
|
|
730
|
-
}
|
|
731
|
-
try:
|
|
732
|
-
self.cache.atomic_save_array(lp, labels, meta)
|
|
733
|
-
except Exception as exc:
|
|
734
|
-
logger.warning(f"Could not cache labels for {spec.subject_id}: {exc}")
|
|
779
|
+
self._save_labels_cache(lp, spec, labels)
|
|
735
780
|
|
|
736
781
|
return labels.astype(np.int64, copy=False)
|
|
737
782
|
|
|
783
|
+
def _pipeline_output_shape(self) -> Tuple[int, ...]:
|
|
784
|
+
"""Per-epoch output shape of the pipeline (e.g. ``(29, 129)`` for the spectrogram presets).
|
|
785
|
+
|
|
786
|
+
Used to zero-fill a subject for which no requested channel could be resolved: its zeros must have the
|
|
787
|
+
same trailing shape as the signals of the other subjects, or batches mixing them cannot be stacked.
|
|
788
|
+
"""
|
|
789
|
+
cached = getattr(self, "_fallback_out_shape", None)
|
|
790
|
+
if cached is not None:
|
|
791
|
+
return cached
|
|
792
|
+
shape: Tuple[int, ...] = ()
|
|
793
|
+
try:
|
|
794
|
+
fs = 100.0
|
|
795
|
+
req = self.channels[0] if self.channels else "EEG"
|
|
796
|
+
stub = ResolvedChannel(
|
|
797
|
+
request=req,
|
|
798
|
+
physical=req,
|
|
799
|
+
modality=infer_channel_modality(str(req)).name,
|
|
800
|
+
fs_in=fs,
|
|
801
|
+
is_differential=False,
|
|
802
|
+
)
|
|
803
|
+
compiled = self._pipeline_for_channel(stub).compile(fs)
|
|
804
|
+
n = int(round(self.epoch_length_sec * fs))
|
|
805
|
+
out = self._epoch_and_run_pipeline(
|
|
806
|
+
np.zeros(n, dtype=np.float32), fs, compiled, stub
|
|
807
|
+
)
|
|
808
|
+
shape = tuple(out.shape[1:])
|
|
809
|
+
except Exception as exc: # fall back to the historical 1-D zeros
|
|
810
|
+
logger.warning(
|
|
811
|
+
f"[{self.DATASET_NAME}] could not infer the pipeline output shape: {exc}"
|
|
812
|
+
)
|
|
813
|
+
self._fallback_out_shape = shape
|
|
814
|
+
return shape
|
|
815
|
+
|
|
816
|
+
def _save_labels_cache(self, lp, spec: SubjectSpec, labels: np.ndarray) -> None:
|
|
817
|
+
meta = {
|
|
818
|
+
"dataset": self.DATASET_NAME,
|
|
819
|
+
"subject_id": spec.subject_id,
|
|
820
|
+
"n_epochs": int(labels.shape[0]),
|
|
821
|
+
"sanitized_to": "AASM_5_class",
|
|
822
|
+
"labels_revision": int(self.LABELS_REVISION),
|
|
823
|
+
}
|
|
824
|
+
try:
|
|
825
|
+
self.cache.atomic_save_array(lp, labels, meta)
|
|
826
|
+
except Exception as exc:
|
|
827
|
+
logger.warning(f"Could not cache labels for {spec.subject_id}: {exc}")
|
|
828
|
+
|
|
738
829
|
def _load_or_compute_channel(
|
|
739
830
|
self, spec: SubjectSpec, resolved: ResolvedChannel
|
|
740
831
|
) -> np.ndarray:
|
|
@@ -1190,7 +1281,7 @@ class BasePhysioDataset(Dataset):
|
|
|
1190
1281
|
|
|
1191
1282
|
# Fill in missing channels with zeros (same shape as resolved ones)
|
|
1192
1283
|
if _ref_shape is None:
|
|
1193
|
-
_ref_shape = (requested_len,)
|
|
1284
|
+
_ref_shape = (requested_len,) + self._pipeline_output_shape()
|
|
1194
1285
|
for key in channel_order:
|
|
1195
1286
|
if key not in signals_raw:
|
|
1196
1287
|
signals_raw[key] = np.zeros(_ref_shape, dtype=np.float32)
|
|
@@ -15,6 +15,7 @@ from physioex.data.datasets.alzheimers import AlzheimersDataset
|
|
|
15
15
|
from physioex.data.datasets.parkinsons import ParkinsonsDataset
|
|
16
16
|
from physioex.data.datasets.shhs import SHHSDataset
|
|
17
17
|
from physioex.data.datasets.stages import STAGESDataset
|
|
18
|
+
from physioex.data.datasets.ccshs import CCSHSDataset
|
|
18
19
|
|
|
19
20
|
REGISTRY = {
|
|
20
21
|
"hmc": HMCDataset,
|
|
@@ -29,6 +30,7 @@ REGISTRY = {
|
|
|
29
30
|
"parkinsons": ParkinsonsDataset,
|
|
30
31
|
"shhs": SHHSDataset,
|
|
31
32
|
"stages": STAGESDataset,
|
|
33
|
+
"ccshs": CCSHSDataset,
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
|
|
@@ -55,6 +57,7 @@ __all__ = [
|
|
|
55
57
|
"ParkinsonsDataset",
|
|
56
58
|
"SHHSDataset",
|
|
57
59
|
"STAGESDataset",
|
|
60
|
+
"CCSHSDataset",
|
|
58
61
|
"REGISTRY",
|
|
59
62
|
"get_dataset",
|
|
60
63
|
"available_datasets",
|
|
@@ -41,6 +41,9 @@ class AlzheimersDataset(BasePhysioDataset):
|
|
|
41
41
|
|
|
42
42
|
DATASET_NAME = "alzheimers"
|
|
43
43
|
DATASET_SUBDIR = "AlzheimerData"
|
|
44
|
+
#: 2: labels are indexed by event onset (gaps stay -1); revision-1 caches are compacted and misaligned.
|
|
45
|
+
#: 3: labels past the end of the EDF are dropped (truncated recordings).
|
|
46
|
+
LABELS_REVISION = 3
|
|
44
47
|
DEFAULT_EPOCH_LENGTH_SEC = 30.0
|
|
45
48
|
|
|
46
49
|
# Channel preference lists.
|
|
@@ -206,4 +209,10 @@ class AlzheimersDataset(BasePhysioDataset):
|
|
|
206
209
|
for epoch_idx, stage in rows:
|
|
207
210
|
labels[epoch_idx] = stage
|
|
208
211
|
|
|
209
|
-
|
|
212
|
+
# Some EDFs are shorter than the annotated night (truncated recordings): labels
|
|
213
|
+
# past the end of the signal are dropped instead of being paired with zero padding.
|
|
214
|
+
import pyedflib
|
|
215
|
+
|
|
216
|
+
with pyedflib.EdfReader(str(spec.edf_path)) as f:
|
|
217
|
+
n_signal = int(f.getFileDuration() // self.epoch_length_sec)
|
|
218
|
+
return labels[:n_signal]
|