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.
Files changed (315) hide show
  1. physioex-2.0.4/CHANGELOG.md +145 -0
  2. {physioex-2.0.2 → physioex-2.0.4}/PKG-INFO +3 -2
  3. {physioex-2.0.2 → physioex-2.0.4}/README.md +2 -1
  4. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/data.md +3 -2
  5. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/data.md +25 -2
  6. physioex-2.0.4/physioex/__init__.py +1 -0
  7. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/__init__.py +4 -0
  8. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/base.py +107 -16
  9. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/__init__.py +3 -0
  10. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/alzheimers.py +10 -1
  11. physioex-2.0.4/physioex/data/datasets/ccshs.py +200 -0
  12. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/channels_table.md +25 -0
  13. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/sleepedf.py +7 -0
  14. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/stages.py +2 -0
  15. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/wsc.py +200 -69
  16. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/events.py +1 -1
  17. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/modality.py +4 -0
  18. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/readers/annotations.py +5 -3
  19. physioex-2.0.4/physioex/data/sampling.py +91 -0
  20. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_datasets.py +8 -0
  21. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/protosleepnet.py +14 -3
  22. {physioex-2.0.2 → physioex-2.0.4}/tests/data/test_modality.py +10 -0
  23. {physioex-2.0.2 → physioex-2.0.4}/tests/test_alzheimers_parkinsons.py +21 -7
  24. physioex-2.0.4/tests/test_ccshs.py +181 -0
  25. physioex-2.0.4/tests/test_channel_acc_tracking.py +87 -0
  26. physioex-2.0.4/tests/test_data_layer_hardening.py +218 -0
  27. {physioex-2.0.2 → physioex-2.0.4}/tests/test_phase_b_datasets.py +14 -0
  28. physioex-2.0.4/tests/test_sampling.py +55 -0
  29. {physioex-2.0.2 → physioex-2.0.4}/tests/test_wsc_dataset.py +156 -0
  30. physioex-2.0.2/CHANGELOG.md +0 -73
  31. physioex-2.0.2/physioex/__init__.py +0 -1
  32. {physioex-2.0.2 → physioex-2.0.4}/.claudeignore +0 -0
  33. {physioex-2.0.2 → physioex-2.0.4}/.github/copilot-instructions.md +0 -0
  34. {physioex-2.0.2 → physioex-2.0.4}/.github/workflows/black.yml +0 -0
  35. {physioex-2.0.2 → physioex-2.0.4}/.github/workflows/ci.yml +0 -0
  36. {physioex-2.0.2 → physioex-2.0.4}/.github/workflows/publish.yaml +0 -0
  37. {physioex-2.0.2 → physioex-2.0.4}/.github/workflows/test.yml +0 -0
  38. {physioex-2.0.2 → physioex-2.0.4}/.gitignore +0 -0
  39. {physioex-2.0.2 → physioex-2.0.4}/LICENSE +0 -0
  40. {physioex-2.0.2 → physioex-2.0.4}/MANIFEST.in +0 -0
  41. {physioex-2.0.2 → physioex-2.0.4}/REPO_MAP.txt +0 -0
  42. {physioex-2.0.2 → physioex-2.0.4}/docs/_ext/related_works.py +0 -0
  43. {physioex-2.0.2 → physioex-2.0.4}/docs/_static/custom.css +0 -0
  44. {physioex-2.0.2 → physioex-2.0.4}/docs/api/index.md +0 -0
  45. {physioex-2.0.2 → physioex-2.0.4}/docs/assets/images/data/sequence_viz.png +0 -0
  46. {physioex-2.0.2 → physioex-2.0.4}/docs/assets/images/logo.psd +0 -0
  47. {physioex-2.0.2 → physioex-2.0.4}/docs/assets/images/logo.svg +0 -0
  48. {physioex-2.0.2 → physioex-2.0.4}/docs/assets/images/logo_bar.svg +0 -0
  49. {physioex-2.0.2 → physioex-2.0.4}/docs/conf.py +0 -0
  50. {physioex-2.0.2 → physioex-2.0.4}/docs/index.md +0 -0
  51. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/explain.md +0 -0
  52. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/models.md +0 -0
  53. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/overview.md +0 -0
  54. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/library/train.md +0 -0
  55. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/architecture/testing/overview.md +0 -0
  56. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/contribute.md +0 -0
  57. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/explain/explain.md +0 -0
  58. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/preprocess.md +0 -0
  59. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/train/cli.md +0 -0
  60. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/train/networks/chambon2018.md +0 -0
  61. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/train/networks.md +0 -0
  62. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/train/train.md +0 -0
  63. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/unavailable.md +0 -0
  64. {physioex-2.0.2 → physioex-2.0.4}/docs/pages/user_guide.md +0 -0
  65. {physioex-2.0.2 → physioex-2.0.4}/docs/related/_thumbs/_placeholder.svg +0 -0
  66. {physioex-2.0.2 → physioex-2.0.4}/docs/related/fetch_citations.py +0 -0
  67. {physioex-2.0.2 → physioex-2.0.4}/docs/related/index.md +0 -0
  68. {physioex-2.0.2 → physioex-2.0.4}/docs/related/works.yaml +0 -0
  69. {physioex-2.0.2 → physioex-2.0.4}/examples/__init__.py +0 -0
  70. {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/__init__.py +0 -0
  71. {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/compare_strategies.py +0 -0
  72. {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/extract_all_concepts.py +0 -0
  73. {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/report.py +0 -0
  74. {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/utils.py +0 -0
  75. {physioex-2.0.2 → physioex-2.0.4}/examples/analyze_csd_concepts/visualize_concepts.py +0 -0
  76. {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/README.md +0 -0
  77. {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/explain_csd.py +0 -0
  78. {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/train_probe.py +0 -0
  79. {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/utils.py +0 -0
  80. {physioex-2.0.2 → physioex-2.0.4}/examples/explain/conceptualspectraldecomposition/visualize.py +0 -0
  81. {physioex-2.0.2 → physioex-2.0.4}/examples/explain/lrp_seqsleepnet_n3.py +0 -0
  82. {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/bendr/extract_embeddings.py +0 -0
  83. {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/biot/extract_embeddings.py +0 -0
  84. {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/cbramod/extract_embeddings.py +0 -0
  85. {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/labram/extract_embeddings.py +0 -0
  86. {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/neurolm/extract_embeddings.py +0 -0
  87. {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/reve/extract_embeddings.py +0 -0
  88. {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/sjepa/extract_embeddings.py +0 -0
  89. {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/sleepfm/extract_embeddings.py +0 -0
  90. {physioex-2.0.2 → physioex-2.0.4}/examples/foundation/tfc/extract_embeddings.py +0 -0
  91. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/extract_embeddings.py +0 -0
  92. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/extract_embeddings.sbatch +0 -0
  93. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/quickstart.py +0 -0
  94. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/smoke.sbatch +0 -0
  95. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/test_pretrained.py +0 -0
  96. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/train.py +0 -0
  97. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/chambon2018/train.sbatch +0 -0
  98. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/coresleep-kontras/extract_embeddings.py +0 -0
  99. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/coresleep-kontras/train.py +0 -0
  100. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/extract_embeddings.py +0 -0
  101. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/quickstart.py +0 -0
  102. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/smoke.sbatch +0 -0
  103. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/test_pretrained.py +0 -0
  104. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/test_pretrained.sbatch +0 -0
  105. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/train.py +0 -0
  106. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/lseqsleepnet-phan/train.sbatch +0 -0
  107. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/protosleepnet/README.md +0 -0
  108. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/protosleepnet/quickstart.py +0 -0
  109. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/extract_embeddings.py +0 -0
  110. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/extract_embeddings.sbatch +0 -0
  111. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/quickstart.py +0 -0
  112. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/test.py +0 -0
  113. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/test_pretrained.py +0 -0
  114. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/test_pretrained.sbatch +0 -0
  115. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/test_stages_sites.py +0 -0
  116. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/train.py +0 -0
  117. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/train.sbatch +0 -0
  118. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/train_shhs.py +0 -0
  119. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/seqsleepnet-phan/train_shhs.sbatch +0 -0
  120. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/extract_embeddings.py +0 -0
  121. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/extract_embeddings.sbatch +0 -0
  122. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/quickstart.py +0 -0
  123. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/smoke.sbatch +0 -0
  124. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/test_pretrained.py +0 -0
  125. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/test_pretrained.sbatch +0 -0
  126. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/train.py +0 -0
  127. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/sleeptransformer-phan/train.sbatch +0 -0
  128. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.py +0 -0
  129. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.sbatch +0 -0
  130. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/quickstart.py +0 -0
  131. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/smoke.sbatch +0 -0
  132. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/test_pretrained.py +0 -0
  133. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/test_pretrained.sbatch +0 -0
  134. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/train.py +0 -0
  135. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tinysleepnet-supratak/train.sbatch +0 -0
  136. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/extract_embeddings.py +0 -0
  137. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/quickstart.py +0 -0
  138. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/smoke.sbatch +0 -0
  139. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/test_pretrained.py +0 -0
  140. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/train.py +0 -0
  141. {physioex-2.0.2 → physioex-2.0.4}/examples/pretrained/tsinalis-2016/train.sbatch +0 -0
  142. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/notebook.ipynb +0 -0
  143. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_barplot.png +0 -0
  144. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_hypnogram.png +0 -0
  145. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_barplot.png +0 -0
  146. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_hypnogram.png +0 -0
  147. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell17_ig_tiny.png +0 -0
  148. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell19_ig_seq.png +0 -0
  149. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell19b_attr_vs_psd.png +0 -0
  150. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell5_raw_n3_psd.png +0 -0
  151. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/outputs/cell7_spec_n3_psd.png +0 -0
  152. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/run_notebook.py +0 -0
  153. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/script.pdf +0 -0
  154. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/slides.pdf +0 -0
  155. {physioex-2.0.2 → physioex-2.0.4}/lectures/2026-06-24_ugent_xai/util.py +0 -0
  156. {physioex-2.0.2 → physioex-2.0.4}/lectures/README.md +0 -0
  157. {physioex-2.0.2 → physioex-2.0.4}/lectures/assets/kuleuven_logo.png +0 -0
  158. {physioex-2.0.2 → physioex-2.0.4}/lectures/assets/physioex_logo.png +0 -0
  159. {physioex-2.0.2 → physioex-2.0.4}/lectures/assets/physioex_logo.svg +0 -0
  160. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/cache.py +0 -0
  161. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/collate.py +0 -0
  162. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datareader.py +0 -0
  163. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/dataset.py +0 -0
  164. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/_nsrr.py +0 -0
  165. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/_splits/shhs.json +0 -0
  166. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/dcsm.py +0 -0
  167. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/hmc.py +0 -0
  168. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/hpap.py +0 -0
  169. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/mass.py +0 -0
  170. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/mesa.py +0 -0
  171. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/mros.py +0 -0
  172. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/parkinsons.py +0 -0
  173. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/datasets/shhs.py +0 -0
  174. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/multi.py +0 -0
  175. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/pipeline.py +0 -0
  176. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/presets.py +0 -0
  177. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/readers/__init__.py +0 -0
  178. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/readers/edf.py +0 -0
  179. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/__init__.py +0 -0
  180. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/bandpass.py +0 -0
  181. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/highpass.py +0 -0
  182. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/identity.py +0 -0
  183. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/normalize.py +0 -0
  184. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/notch.py +0 -0
  185. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/resample.py +0 -0
  186. {physioex-2.0.2 → physioex-2.0.4}/physioex/data/steps/spectrogram.py +0 -0
  187. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/__init__.py +0 -0
  188. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/__init__.py +0 -0
  189. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/csd.py +0 -0
  190. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/multichannel_sg.py +0 -0
  191. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/report.py +0 -0
  192. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/sleep_bands.py +0 -0
  193. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/foundational/specificity.py +0 -0
  194. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/__init__.py +0 -0
  195. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/_functional.py +0 -0
  196. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/_rules.py +0 -0
  197. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/attributor.py +0 -0
  198. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/canonizers.py +0 -0
  199. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/composites.py +0 -0
  200. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/diagnostics.py +0 -0
  201. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/model.py +0 -0
  202. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/pooling.py +0 -0
  203. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/recurrent.py +0 -0
  204. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/lrp/transformer.py +0 -0
  205. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/__init__.py +0 -0
  206. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/filters.py +0 -0
  207. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/functionizer.py +0 -0
  208. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/gradients.py +0 -0
  209. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/metrics/__init__.py +0 -0
  210. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/spectralgradients.py +0 -0
  211. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/vidft.py +0 -0
  212. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/posthoc/vistdft.py +0 -0
  213. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/__init__.py +0 -0
  214. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/local.py +0 -0
  215. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/posthoc/__init__.py +0 -0
  216. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/posthoc/nmf.py +0 -0
  217. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/posthoc/utils.py +0 -0
  218. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/posthoc/vq.py +0 -0
  219. {physioex-2.0.2 → physioex-2.0.4}/physioex/explain/prototypes/reconstruct.py +0 -0
  220. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/__init__.py +0 -0
  221. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/bendr.py +0 -0
  222. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/biot.py +0 -0
  223. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/cbramod.py +0 -0
  224. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/chambon2018.py +0 -0
  225. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/coresleep.py +0 -0
  226. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/embed.py +0 -0
  227. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_base.py +0 -0
  228. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_channel_mapping.py +0 -0
  229. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_checkpoints.py +0 -0
  230. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/foundation_preproc.py +0 -0
  231. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/labram.py +0 -0
  232. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/lseqsleepnet.py +0 -0
  233. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/neurolm.py +0 -0
  234. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/pretrained.py +0 -0
  235. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/reve.py +0 -0
  236. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/seqsleepnet.py +0 -0
  237. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/sjepa.py +0 -0
  238. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/sleep_tokenizer.py +0 -0
  239. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/sleepfm.py +0 -0
  240. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/sleeptransformer.py +0 -0
  241. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/tfc.py +0 -0
  242. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/tinysleepnet.py +0 -0
  243. {physioex-2.0.2 → physioex-2.0.4}/physioex/models/tsinalis.py +0 -0
  244. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/__init__.py +0 -0
  245. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/__init__.py +0 -0
  246. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/_common.py +0 -0
  247. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/finetune.py +0 -0
  248. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/test.py +0 -0
  249. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/bin/train.py +0 -0
  250. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/logger.py +0 -0
  251. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/metrics.py +0 -0
  252. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/models/__init__.py +0 -0
  253. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/models/check_table.csv +0 -0
  254. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/models/load.py +0 -0
  255. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/multidevicetrainer.py +0 -0
  256. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/progress.py +0 -0
  257. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/stats.py +0 -0
  258. {physioex-2.0.2 → physioex-2.0.4}/physioex/train/trainer.py +0 -0
  259. {physioex-2.0.2 → physioex-2.0.4}/pyproject.toml +0 -0
  260. {physioex-2.0.2 → physioex-2.0.4}/tests/__init__.py +0 -0
  261. {physioex-2.0.2 → physioex-2.0.4}/tests/conftest.py +0 -0
  262. {physioex-2.0.2 → physioex-2.0.4}/tests/data/test_base_smoke.py +0 -0
  263. {physioex-2.0.2 → physioex-2.0.4}/tests/data/test_multi.py +0 -0
  264. {physioex-2.0.2 → physioex-2.0.4}/tests/data/test_readers_annotations.py +0 -0
  265. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/foundational/test_sleep_bands.py +0 -0
  266. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/foundational/test_specificity.py +0 -0
  267. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/__init__.py +0 -0
  268. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_functional.py +0 -0
  269. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_lrp.py +0 -0
  270. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_model.py +0 -0
  271. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_real_models.py +0 -0
  272. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_recurrent.py +0 -0
  273. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/lrp/test_transformer.py +0 -0
  274. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/__init__.py +0 -0
  275. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_gradients.py +0 -0
  276. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_metrics.py +0 -0
  277. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_spectralgradients.py +0 -0
  278. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_vidft.py +0 -0
  279. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/posthoc/test_vistdft.py +0 -0
  280. {physioex-2.0.2 → physioex-2.0.4}/tests/explain/prototypes/posthoc/test_nmf_vq.py +0 -0
  281. {physioex-2.0.2 → physioex-2.0.4}/tests/factories/__init__.py +0 -0
  282. {physioex-2.0.2 → physioex-2.0.4}/tests/factories/edf.py +0 -0
  283. {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_architectures.py +0 -0
  284. {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_foundation_base.py +0 -0
  285. {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_foundation_encoders.py +0 -0
  286. {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_foundation_preproc.py +0 -0
  287. {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_pretrained_embed_helpers.py +0 -0
  288. {physioex-2.0.2 → physioex-2.0.4}/tests/models/test_sleep_tokenizer.py +0 -0
  289. {physioex-2.0.2 → physioex-2.0.4}/tests/test_api_surface.py +0 -0
  290. {physioex-2.0.2 → physioex-2.0.4}/tests/test_cache.py +0 -0
  291. {physioex-2.0.2 → physioex-2.0.4}/tests/test_cache_enabled.py +0 -0
  292. {physioex-2.0.2 → physioex-2.0.4}/tests/test_channel_resolution.py +0 -0
  293. {physioex-2.0.2 → physioex-2.0.4}/tests/test_cli_workflows.py +0 -0
  294. {physioex-2.0.2 → physioex-2.0.4}/tests/test_dataset_config.py +0 -0
  295. {physioex-2.0.2 → physioex-2.0.4}/tests/test_e2e_smoke.py +0 -0
  296. {physioex-2.0.2 → physioex-2.0.4}/tests/test_events.py +0 -0
  297. {physioex-2.0.2 → physioex-2.0.4}/tests/test_explain_fixes.py +0 -0
  298. {physioex-2.0.2 → physioex-2.0.4}/tests/test_label_hardening.py +0 -0
  299. {physioex-2.0.2 → physioex-2.0.4}/tests/test_load_model.py +0 -0
  300. {physioex-2.0.2 → physioex-2.0.4}/tests/test_mass_dataset.py +0 -0
  301. {physioex-2.0.2 → physioex-2.0.4}/tests/test_metrics_and_packaging.py +0 -0
  302. {physioex-2.0.2 → physioex-2.0.4}/tests/test_modality_dropout.py +0 -0
  303. {physioex-2.0.2 → physioex-2.0.4}/tests/test_phase_c_datasets.py +0 -0
  304. {physioex-2.0.2 → physioex-2.0.4}/tests/test_pipeline.py +0 -0
  305. {physioex-2.0.2 → physioex-2.0.4}/tests/test_presets.py +0 -0
  306. {physioex-2.0.2 → physioex-2.0.4}/tests/test_raw_dataset_integration.py +0 -0
  307. {physioex-2.0.2 → physioex-2.0.4}/tests/test_reading_modes.py +0 -0
  308. {physioex-2.0.2 → physioex-2.0.4}/tests/test_stages_dataset.py +0 -0
  309. {physioex-2.0.2 → physioex-2.0.4}/tests/test_trainer_enhancements.py +0 -0
  310. {physioex-2.0.2 → physioex-2.0.4}/tests/test_trainer_fixes.py +0 -0
  311. {physioex-2.0.2 → physioex-2.0.4}/tests/test_trainer_integration.py +0 -0
  312. {physioex-2.0.2 → physioex-2.0.4}/tests/test_wake_trimming.py +0 -0
  313. {physioex-2.0.2 → physioex-2.0.4}/tests/train/test_logger.py +0 -0
  314. {physioex-2.0.2 → physioex-2.0.4}/tests/train/test_progress.py +0 -0
  315. {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.2
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 12 concrete datasets are resolved by name from a **string registry** in
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 12 concrete datasets:
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
- return EDFHeader.from_dict(cached)
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
- header = self._read_edf_header(spec)
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
- Only genuinely corrupt EDFs (header unreadable) are skipped
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
- return np.asarray(memmap).astype(np.int64, copy=False)
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
- meta = {
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
- return labels
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]