physioex 2.0.2__tar.gz → 2.0.3__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 (314) hide show
  1. {physioex-2.0.2 → physioex-2.0.3}/CHANGELOG.md +51 -0
  2. {physioex-2.0.2 → physioex-2.0.3}/PKG-INFO +3 -2
  3. {physioex-2.0.2 → physioex-2.0.3}/README.md +2 -1
  4. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/architecture/library/data.md +3 -2
  5. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/data.md +25 -2
  6. physioex-2.0.3/physioex/__init__.py +1 -0
  7. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/__init__.py +4 -0
  8. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/base.py +107 -16
  9. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/__init__.py +3 -0
  10. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/alzheimers.py +2 -0
  11. physioex-2.0.3/physioex/data/datasets/ccshs.py +200 -0
  12. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/channels_table.md +25 -0
  13. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/stages.py +2 -0
  14. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/events.py +1 -1
  15. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/readers/annotations.py +5 -3
  16. physioex-2.0.3/physioex/data/sampling.py +91 -0
  17. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/foundation_datasets.py +8 -0
  18. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/protosleepnet.py +14 -3
  19. physioex-2.0.3/tests/test_ccshs.py +181 -0
  20. physioex-2.0.3/tests/test_channel_acc_tracking.py +87 -0
  21. physioex-2.0.3/tests/test_data_layer_hardening.py +207 -0
  22. physioex-2.0.3/tests/test_sampling.py +55 -0
  23. physioex-2.0.2/physioex/__init__.py +0 -1
  24. {physioex-2.0.2 → physioex-2.0.3}/.claudeignore +0 -0
  25. {physioex-2.0.2 → physioex-2.0.3}/.github/copilot-instructions.md +0 -0
  26. {physioex-2.0.2 → physioex-2.0.3}/.github/workflows/black.yml +0 -0
  27. {physioex-2.0.2 → physioex-2.0.3}/.github/workflows/ci.yml +0 -0
  28. {physioex-2.0.2 → physioex-2.0.3}/.github/workflows/publish.yaml +0 -0
  29. {physioex-2.0.2 → physioex-2.0.3}/.github/workflows/test.yml +0 -0
  30. {physioex-2.0.2 → physioex-2.0.3}/.gitignore +0 -0
  31. {physioex-2.0.2 → physioex-2.0.3}/LICENSE +0 -0
  32. {physioex-2.0.2 → physioex-2.0.3}/MANIFEST.in +0 -0
  33. {physioex-2.0.2 → physioex-2.0.3}/REPO_MAP.txt +0 -0
  34. {physioex-2.0.2 → physioex-2.0.3}/docs/_ext/related_works.py +0 -0
  35. {physioex-2.0.2 → physioex-2.0.3}/docs/_static/custom.css +0 -0
  36. {physioex-2.0.2 → physioex-2.0.3}/docs/api/index.md +0 -0
  37. {physioex-2.0.2 → physioex-2.0.3}/docs/assets/images/data/sequence_viz.png +0 -0
  38. {physioex-2.0.2 → physioex-2.0.3}/docs/assets/images/logo.psd +0 -0
  39. {physioex-2.0.2 → physioex-2.0.3}/docs/assets/images/logo.svg +0 -0
  40. {physioex-2.0.2 → physioex-2.0.3}/docs/assets/images/logo_bar.svg +0 -0
  41. {physioex-2.0.2 → physioex-2.0.3}/docs/conf.py +0 -0
  42. {physioex-2.0.2 → physioex-2.0.3}/docs/index.md +0 -0
  43. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/architecture/library/explain.md +0 -0
  44. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/architecture/library/models.md +0 -0
  45. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/architecture/library/overview.md +0 -0
  46. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/architecture/library/train.md +0 -0
  47. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/architecture/testing/overview.md +0 -0
  48. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/contribute.md +0 -0
  49. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/explain/explain.md +0 -0
  50. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/preprocess.md +0 -0
  51. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/train/cli.md +0 -0
  52. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/train/networks/chambon2018.md +0 -0
  53. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/train/networks.md +0 -0
  54. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/train/train.md +0 -0
  55. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/unavailable.md +0 -0
  56. {physioex-2.0.2 → physioex-2.0.3}/docs/pages/user_guide.md +0 -0
  57. {physioex-2.0.2 → physioex-2.0.3}/docs/related/_thumbs/_placeholder.svg +0 -0
  58. {physioex-2.0.2 → physioex-2.0.3}/docs/related/fetch_citations.py +0 -0
  59. {physioex-2.0.2 → physioex-2.0.3}/docs/related/index.md +0 -0
  60. {physioex-2.0.2 → physioex-2.0.3}/docs/related/works.yaml +0 -0
  61. {physioex-2.0.2 → physioex-2.0.3}/examples/__init__.py +0 -0
  62. {physioex-2.0.2 → physioex-2.0.3}/examples/analyze_csd_concepts/__init__.py +0 -0
  63. {physioex-2.0.2 → physioex-2.0.3}/examples/analyze_csd_concepts/compare_strategies.py +0 -0
  64. {physioex-2.0.2 → physioex-2.0.3}/examples/analyze_csd_concepts/extract_all_concepts.py +0 -0
  65. {physioex-2.0.2 → physioex-2.0.3}/examples/analyze_csd_concepts/report.py +0 -0
  66. {physioex-2.0.2 → physioex-2.0.3}/examples/analyze_csd_concepts/utils.py +0 -0
  67. {physioex-2.0.2 → physioex-2.0.3}/examples/analyze_csd_concepts/visualize_concepts.py +0 -0
  68. {physioex-2.0.2 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/README.md +0 -0
  69. {physioex-2.0.2 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/explain_csd.py +0 -0
  70. {physioex-2.0.2 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/train_probe.py +0 -0
  71. {physioex-2.0.2 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/utils.py +0 -0
  72. {physioex-2.0.2 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/visualize.py +0 -0
  73. {physioex-2.0.2 → physioex-2.0.3}/examples/explain/lrp_seqsleepnet_n3.py +0 -0
  74. {physioex-2.0.2 → physioex-2.0.3}/examples/foundation/bendr/extract_embeddings.py +0 -0
  75. {physioex-2.0.2 → physioex-2.0.3}/examples/foundation/biot/extract_embeddings.py +0 -0
  76. {physioex-2.0.2 → physioex-2.0.3}/examples/foundation/cbramod/extract_embeddings.py +0 -0
  77. {physioex-2.0.2 → physioex-2.0.3}/examples/foundation/labram/extract_embeddings.py +0 -0
  78. {physioex-2.0.2 → physioex-2.0.3}/examples/foundation/neurolm/extract_embeddings.py +0 -0
  79. {physioex-2.0.2 → physioex-2.0.3}/examples/foundation/reve/extract_embeddings.py +0 -0
  80. {physioex-2.0.2 → physioex-2.0.3}/examples/foundation/sjepa/extract_embeddings.py +0 -0
  81. {physioex-2.0.2 → physioex-2.0.3}/examples/foundation/sleepfm/extract_embeddings.py +0 -0
  82. {physioex-2.0.2 → physioex-2.0.3}/examples/foundation/tfc/extract_embeddings.py +0 -0
  83. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/chambon2018/extract_embeddings.py +0 -0
  84. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/chambon2018/extract_embeddings.sbatch +0 -0
  85. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/chambon2018/quickstart.py +0 -0
  86. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/chambon2018/smoke.sbatch +0 -0
  87. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/chambon2018/test_pretrained.py +0 -0
  88. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/chambon2018/train.py +0 -0
  89. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/chambon2018/train.sbatch +0 -0
  90. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/coresleep-kontras/extract_embeddings.py +0 -0
  91. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/coresleep-kontras/train.py +0 -0
  92. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/extract_embeddings.py +0 -0
  93. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/quickstart.py +0 -0
  94. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/smoke.sbatch +0 -0
  95. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/test_pretrained.py +0 -0
  96. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/test_pretrained.sbatch +0 -0
  97. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/train.py +0 -0
  98. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/train.sbatch +0 -0
  99. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/protosleepnet/README.md +0 -0
  100. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/protosleepnet/quickstart.py +0 -0
  101. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/extract_embeddings.py +0 -0
  102. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/extract_embeddings.sbatch +0 -0
  103. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/quickstart.py +0 -0
  104. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/test.py +0 -0
  105. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/test_pretrained.py +0 -0
  106. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/test_pretrained.sbatch +0 -0
  107. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/test_stages_sites.py +0 -0
  108. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/train.py +0 -0
  109. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/train.sbatch +0 -0
  110. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/train_shhs.py +0 -0
  111. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/train_shhs.sbatch +0 -0
  112. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/extract_embeddings.py +0 -0
  113. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/extract_embeddings.sbatch +0 -0
  114. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/quickstart.py +0 -0
  115. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/smoke.sbatch +0 -0
  116. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/test_pretrained.py +0 -0
  117. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/test_pretrained.sbatch +0 -0
  118. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/train.py +0 -0
  119. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/train.sbatch +0 -0
  120. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.py +0 -0
  121. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.sbatch +0 -0
  122. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/quickstart.py +0 -0
  123. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/smoke.sbatch +0 -0
  124. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/test_pretrained.py +0 -0
  125. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/test_pretrained.sbatch +0 -0
  126. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/train.py +0 -0
  127. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/train.sbatch +0 -0
  128. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/extract_embeddings.py +0 -0
  129. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/quickstart.py +0 -0
  130. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/smoke.sbatch +0 -0
  131. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/test_pretrained.py +0 -0
  132. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/train.py +0 -0
  133. {physioex-2.0.2 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/train.sbatch +0 -0
  134. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/notebook.ipynb +0 -0
  135. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_barplot.png +0 -0
  136. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_hypnogram.png +0 -0
  137. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_barplot.png +0 -0
  138. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_hypnogram.png +0 -0
  139. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell17_ig_tiny.png +0 -0
  140. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell19_ig_seq.png +0 -0
  141. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell19b_attr_vs_psd.png +0 -0
  142. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell5_raw_n3_psd.png +0 -0
  143. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell7_spec_n3_psd.png +0 -0
  144. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/run_notebook.py +0 -0
  145. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/script.pdf +0 -0
  146. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/slides.pdf +0 -0
  147. {physioex-2.0.2 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/util.py +0 -0
  148. {physioex-2.0.2 → physioex-2.0.3}/lectures/README.md +0 -0
  149. {physioex-2.0.2 → physioex-2.0.3}/lectures/assets/kuleuven_logo.png +0 -0
  150. {physioex-2.0.2 → physioex-2.0.3}/lectures/assets/physioex_logo.png +0 -0
  151. {physioex-2.0.2 → physioex-2.0.3}/lectures/assets/physioex_logo.svg +0 -0
  152. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/cache.py +0 -0
  153. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/collate.py +0 -0
  154. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datareader.py +0 -0
  155. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/dataset.py +0 -0
  156. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/_nsrr.py +0 -0
  157. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/_splits/shhs.json +0 -0
  158. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/dcsm.py +0 -0
  159. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/hmc.py +0 -0
  160. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/hpap.py +0 -0
  161. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/mass.py +0 -0
  162. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/mesa.py +0 -0
  163. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/mros.py +0 -0
  164. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/parkinsons.py +0 -0
  165. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/shhs.py +0 -0
  166. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/sleepedf.py +0 -0
  167. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/datasets/wsc.py +0 -0
  168. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/modality.py +0 -0
  169. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/multi.py +0 -0
  170. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/pipeline.py +0 -0
  171. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/presets.py +0 -0
  172. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/readers/__init__.py +0 -0
  173. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/readers/edf.py +0 -0
  174. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/steps/__init__.py +0 -0
  175. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/steps/bandpass.py +0 -0
  176. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/steps/highpass.py +0 -0
  177. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/steps/identity.py +0 -0
  178. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/steps/normalize.py +0 -0
  179. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/steps/notch.py +0 -0
  180. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/steps/resample.py +0 -0
  181. {physioex-2.0.2 → physioex-2.0.3}/physioex/data/steps/spectrogram.py +0 -0
  182. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/__init__.py +0 -0
  183. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/foundational/__init__.py +0 -0
  184. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/foundational/csd.py +0 -0
  185. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/foundational/multichannel_sg.py +0 -0
  186. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/foundational/report.py +0 -0
  187. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/foundational/sleep_bands.py +0 -0
  188. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/foundational/specificity.py +0 -0
  189. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/__init__.py +0 -0
  190. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/_functional.py +0 -0
  191. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/_rules.py +0 -0
  192. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/attributor.py +0 -0
  193. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/canonizers.py +0 -0
  194. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/composites.py +0 -0
  195. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/diagnostics.py +0 -0
  196. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/model.py +0 -0
  197. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/pooling.py +0 -0
  198. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/recurrent.py +0 -0
  199. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/lrp/transformer.py +0 -0
  200. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/posthoc/__init__.py +0 -0
  201. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/posthoc/filters.py +0 -0
  202. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/posthoc/functionizer.py +0 -0
  203. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/posthoc/gradients.py +0 -0
  204. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/posthoc/metrics/__init__.py +0 -0
  205. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/posthoc/spectralgradients.py +0 -0
  206. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/posthoc/vidft.py +0 -0
  207. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/posthoc/vistdft.py +0 -0
  208. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/prototypes/__init__.py +0 -0
  209. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/prototypes/local.py +0 -0
  210. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/prototypes/posthoc/__init__.py +0 -0
  211. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/prototypes/posthoc/nmf.py +0 -0
  212. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/prototypes/posthoc/utils.py +0 -0
  213. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/prototypes/posthoc/vq.py +0 -0
  214. {physioex-2.0.2 → physioex-2.0.3}/physioex/explain/prototypes/reconstruct.py +0 -0
  215. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/__init__.py +0 -0
  216. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/bendr.py +0 -0
  217. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/biot.py +0 -0
  218. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/cbramod.py +0 -0
  219. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/chambon2018.py +0 -0
  220. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/coresleep.py +0 -0
  221. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/embed.py +0 -0
  222. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/foundation_base.py +0 -0
  223. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/foundation_channel_mapping.py +0 -0
  224. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/foundation_checkpoints.py +0 -0
  225. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/foundation_preproc.py +0 -0
  226. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/labram.py +0 -0
  227. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/lseqsleepnet.py +0 -0
  228. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/neurolm.py +0 -0
  229. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/pretrained.py +0 -0
  230. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/reve.py +0 -0
  231. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/seqsleepnet.py +0 -0
  232. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/sjepa.py +0 -0
  233. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/sleep_tokenizer.py +0 -0
  234. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/sleepfm.py +0 -0
  235. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/sleeptransformer.py +0 -0
  236. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/tfc.py +0 -0
  237. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/tinysleepnet.py +0 -0
  238. {physioex-2.0.2 → physioex-2.0.3}/physioex/models/tsinalis.py +0 -0
  239. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/__init__.py +0 -0
  240. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/bin/__init__.py +0 -0
  241. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/bin/_common.py +0 -0
  242. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/bin/finetune.py +0 -0
  243. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/bin/test.py +0 -0
  244. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/bin/train.py +0 -0
  245. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/logger.py +0 -0
  246. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/metrics.py +0 -0
  247. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/models/__init__.py +0 -0
  248. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/models/check_table.csv +0 -0
  249. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/models/load.py +0 -0
  250. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/multidevicetrainer.py +0 -0
  251. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/progress.py +0 -0
  252. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/stats.py +0 -0
  253. {physioex-2.0.2 → physioex-2.0.3}/physioex/train/trainer.py +0 -0
  254. {physioex-2.0.2 → physioex-2.0.3}/pyproject.toml +0 -0
  255. {physioex-2.0.2 → physioex-2.0.3}/tests/__init__.py +0 -0
  256. {physioex-2.0.2 → physioex-2.0.3}/tests/conftest.py +0 -0
  257. {physioex-2.0.2 → physioex-2.0.3}/tests/data/test_base_smoke.py +0 -0
  258. {physioex-2.0.2 → physioex-2.0.3}/tests/data/test_modality.py +0 -0
  259. {physioex-2.0.2 → physioex-2.0.3}/tests/data/test_multi.py +0 -0
  260. {physioex-2.0.2 → physioex-2.0.3}/tests/data/test_readers_annotations.py +0 -0
  261. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/foundational/test_sleep_bands.py +0 -0
  262. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/foundational/test_specificity.py +0 -0
  263. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/lrp/__init__.py +0 -0
  264. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/lrp/test_functional.py +0 -0
  265. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/lrp/test_lrp.py +0 -0
  266. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/lrp/test_model.py +0 -0
  267. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/lrp/test_real_models.py +0 -0
  268. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/lrp/test_recurrent.py +0 -0
  269. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/lrp/test_transformer.py +0 -0
  270. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/posthoc/__init__.py +0 -0
  271. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/posthoc/test_gradients.py +0 -0
  272. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/posthoc/test_metrics.py +0 -0
  273. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/posthoc/test_spectralgradients.py +0 -0
  274. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/posthoc/test_vidft.py +0 -0
  275. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/posthoc/test_vistdft.py +0 -0
  276. {physioex-2.0.2 → physioex-2.0.3}/tests/explain/prototypes/posthoc/test_nmf_vq.py +0 -0
  277. {physioex-2.0.2 → physioex-2.0.3}/tests/factories/__init__.py +0 -0
  278. {physioex-2.0.2 → physioex-2.0.3}/tests/factories/edf.py +0 -0
  279. {physioex-2.0.2 → physioex-2.0.3}/tests/models/test_architectures.py +0 -0
  280. {physioex-2.0.2 → physioex-2.0.3}/tests/models/test_foundation_base.py +0 -0
  281. {physioex-2.0.2 → physioex-2.0.3}/tests/models/test_foundation_encoders.py +0 -0
  282. {physioex-2.0.2 → physioex-2.0.3}/tests/models/test_foundation_preproc.py +0 -0
  283. {physioex-2.0.2 → physioex-2.0.3}/tests/models/test_pretrained_embed_helpers.py +0 -0
  284. {physioex-2.0.2 → physioex-2.0.3}/tests/models/test_sleep_tokenizer.py +0 -0
  285. {physioex-2.0.2 → physioex-2.0.3}/tests/test_alzheimers_parkinsons.py +0 -0
  286. {physioex-2.0.2 → physioex-2.0.3}/tests/test_api_surface.py +0 -0
  287. {physioex-2.0.2 → physioex-2.0.3}/tests/test_cache.py +0 -0
  288. {physioex-2.0.2 → physioex-2.0.3}/tests/test_cache_enabled.py +0 -0
  289. {physioex-2.0.2 → physioex-2.0.3}/tests/test_channel_resolution.py +0 -0
  290. {physioex-2.0.2 → physioex-2.0.3}/tests/test_cli_workflows.py +0 -0
  291. {physioex-2.0.2 → physioex-2.0.3}/tests/test_dataset_config.py +0 -0
  292. {physioex-2.0.2 → physioex-2.0.3}/tests/test_e2e_smoke.py +0 -0
  293. {physioex-2.0.2 → physioex-2.0.3}/tests/test_events.py +0 -0
  294. {physioex-2.0.2 → physioex-2.0.3}/tests/test_explain_fixes.py +0 -0
  295. {physioex-2.0.2 → physioex-2.0.3}/tests/test_label_hardening.py +0 -0
  296. {physioex-2.0.2 → physioex-2.0.3}/tests/test_load_model.py +0 -0
  297. {physioex-2.0.2 → physioex-2.0.3}/tests/test_mass_dataset.py +0 -0
  298. {physioex-2.0.2 → physioex-2.0.3}/tests/test_metrics_and_packaging.py +0 -0
  299. {physioex-2.0.2 → physioex-2.0.3}/tests/test_modality_dropout.py +0 -0
  300. {physioex-2.0.2 → physioex-2.0.3}/tests/test_phase_b_datasets.py +0 -0
  301. {physioex-2.0.2 → physioex-2.0.3}/tests/test_phase_c_datasets.py +0 -0
  302. {physioex-2.0.2 → physioex-2.0.3}/tests/test_pipeline.py +0 -0
  303. {physioex-2.0.2 → physioex-2.0.3}/tests/test_presets.py +0 -0
  304. {physioex-2.0.2 → physioex-2.0.3}/tests/test_raw_dataset_integration.py +0 -0
  305. {physioex-2.0.2 → physioex-2.0.3}/tests/test_reading_modes.py +0 -0
  306. {physioex-2.0.2 → physioex-2.0.3}/tests/test_stages_dataset.py +0 -0
  307. {physioex-2.0.2 → physioex-2.0.3}/tests/test_trainer_enhancements.py +0 -0
  308. {physioex-2.0.2 → physioex-2.0.3}/tests/test_trainer_fixes.py +0 -0
  309. {physioex-2.0.2 → physioex-2.0.3}/tests/test_trainer_integration.py +0 -0
  310. {physioex-2.0.2 → physioex-2.0.3}/tests/test_wake_trimming.py +0 -0
  311. {physioex-2.0.2 → physioex-2.0.3}/tests/test_wsc_dataset.py +0 -0
  312. {physioex-2.0.2 → physioex-2.0.3}/tests/train/test_logger.py +0 -0
  313. {physioex-2.0.2 → physioex-2.0.3}/tests/train/test_progress.py +0 -0
  314. {physioex-2.0.2 → physioex-2.0.3}/tests/train/test_stats.py +0 -0
@@ -4,6 +4,57 @@ All notable changes to PhysioEx are documented here. Versions follow the
4
4
  `physioex.__version__` string (read dynamically by the build) and the `v*` git tags
5
5
  that publish to PyPI.
6
6
 
7
+ ## 2.0.3 — 2026-10-15
8
+
9
+ CCSHS dataset, opt-in utilities for multi-dataset training, and three data-layer fixes. STAGES and Alzheimers label
10
+ caches are recomputed on first use, and event counts per type change (see "`artifact` event type").
11
+
12
+ ### Added — CCSHS dataset (`physioex.data.datasets.ccshs`)
13
+
14
+ `get_dataset("ccshs")`: Cleveland Children's Sleep and Health Study (NSRR `ccshs-trec`, 515 recordings). EEG
15
+ `(C3, A2)` / `(C4, A1)` and EOG `(LOC, ROC)` at 128 Hz, chin EMG at 256 Hz; stage 3 and 4 merged to N3,
16
+ Movement/unscored to -1, labels indexed by event onset, unknown stage vocabulary raises. Eleven recordings are
17
+ excluded by default (`CCSHSDataset.EXCLUDED_SUBJECTS`, `exclude_subjects=[]` keeps them): nine whose stage minutes
18
+ disagree with the NSRR summary table and two without a row in it. Metadata is read from
19
+ `datasets/ccshs-trec-dataset-*.csv` whatever its version. Tests in `tests/test_ccshs.py`.
20
+
21
+ ### Added — site-balanced sampling (`physioex.data.sampling`)
22
+
23
+ `site_balanced_weights` / `make_site_sampler` (also exported from `physioex.data`): sampling weights that give every
24
+ site the same mass and, inside a site, every subject the same mass, for training on collections of datasets of very
25
+ different sizes with `MultiDataset`. Tests in `tests/test_sampling.py`.
26
+
27
+ ### Added — opt-in channel-accuracy tracking during training (`ChannelMixer`)
28
+
29
+ `ChannelMixer.track_train_acc` and `acc_momentum` update the per-channel accuracy that drives the accuracy-weighted
30
+ modality dropout from the training step too (EMA of the clean per-channel logits). Off by default, so the released
31
+ models and existing training runs behave exactly as before. Tests in `tests/test_channel_acc_tracking.py`.
32
+
33
+ ### Fixed — subjects without any requested channel (`BasePhysioDataset`)
34
+
35
+ A subject for which none of the requested channels can be resolved was returned as a block of zeros with the wrong
36
+ shape (`(L,)` instead of `(L, F, T)`), which made `dict_collate_fn` fail on any batch containing it. Its zeros now have
37
+ the per-epoch shape of the pipeline output, like the zero-filled channels of subjects that miss only some channels, and
38
+ a warning names the subject. Subjects are still never dropped.
39
+
40
+ ### Fixed — stale cached labels (`BasePhysioDataset.LABELS_REVISION`)
41
+
42
+ Cached labels had no version, so after a fix of a label reader the old caches kept serving the wrong labels without any
43
+ warning. Datasets now declare `LABELS_REVISION` (default 1); the revision is stored with the cached labels and a cache
44
+ with a lower revision is recomputed (with a warning), or used with a warning when the source files are not available.
45
+ Caches without the key count as revision 1, so existing caches of unchanged readers stay valid. `STAGESDataset` and
46
+ `AlzheimersDataset` are at revision 2 (timestamp-based alignment of STAGES labels, onset-indexed Alzheimer labels):
47
+ caches written before those fixes are recomputed on first use. Cached EDF headers that predate the `start_sec` field
48
+ (STAGES labels are aligned to the EDF start time with it) are probed again, or used with a warning when the EDF is not
49
+ available.
50
+
51
+ ### Changed — `artifact` event type
52
+
53
+ "SpO2 artifact" intervals (an unreliable oximeter signal, not a physiological event) were counted as `respiratory` by
54
+ the NSRR parser and as `desaturation` by the STAGES parser. They now have their own type `artifact`; the concept text is
55
+ unchanged. Event counts per type change accordingly, for example in CCSHS `respiratory` drops from 51 915 to 15 040
56
+ and `artifact` has 36 875.
57
+
7
58
  ## 2.0.2 — 2026-10-10
8
59
 
9
60
  ### Fixed — accuracy-weighted modality dropout (`physioex.models.protosleepnet`)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: physioex
3
- Version: 2.0.2
3
+ Version: 2.0.3
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.3"
@@ -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,8 @@ 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
+ LABELS_REVISION = 2
44
46
  DEFAULT_EPOCH_LENGTH_SEC = 30.0
45
47
 
46
48
  # Channel preference lists.
@@ -0,0 +1,200 @@
1
+ """CCSHS (Cleveland Children's Sleep and Health Study, NSRR ``ccshs-trec``).
2
+
3
+ Layout on disk::
4
+
5
+ <root>/
6
+ polysomnography/
7
+ edfs/ccshs-trec-1800001.edf
8
+ annotations-events-nsrr/ccshs-trec-1800001-nsrr.xml
9
+ datasets/ccshs-trec-dataset-0.8.0.csv
10
+
11
+ One recording per subject; subject ID = EDF stem (``ccshs-trec-<nsrrid>``).
12
+ EEG C3/C4 are recorded against the contralateral mastoid (A2/A1) and are
13
+ re-referenced by the differential-pair resolution (``("C3", "A2")``).
14
+ EDF is mixed-rate (EEG/EOG 128 Hz, chin EMG 256 Hz).
15
+
16
+ Eleven recordings are excluded by default (``CCSHSDataset.EXCLUDED_SUBJECTS``): nine whose
17
+ per-stage minutes in the NSRR XML disagree with the stage minutes of the NSRR summary
18
+ dataset CSV, and two that have no row in that CSV. Pass ``exclude_subjects=[]`` to keep them.
19
+
20
+ Stage vocabulary in the NSRR XML (``EventType`` ``Stages|Stages``): ``Wake|0``,
21
+ ``Stage 1..4 sleep|1..4``, ``REM sleep|5``, ``Movement|6``. Stage 3 and 4 are
22
+ merged to N3. Movement/unscored -> -1. Any *other* stage concept raises, so
23
+ a vocabulary change cannot silently become "unscored".
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import logging
28
+ import xml.etree.ElementTree as ET
29
+ from pathlib import Path
30
+ from typing import Any, Dict, Iterable, List, Optional
31
+
32
+ import numpy as np
33
+
34
+ from physioex.data.base import SubjectSpec, get_data_root
35
+ from physioex.data.datasets._nsrr import _NSRRBaseDataset
36
+
37
+ logger = logging.getLogger("physioex.data")
38
+
39
+ CCSHS_STAGE_MAP: Dict[str, int] = {
40
+ "Wake|0": 0,
41
+ "Stage 1 sleep|1": 1,
42
+ "Stage 2 sleep|2": 2,
43
+ "Stage 3 sleep|3": 3,
44
+ "Stage 4 sleep|4": 3, # S3 + S4 -> N3
45
+ "REM sleep|5": 4,
46
+ "Movement|6": -1,
47
+ "Unscored|9": -1,
48
+ }
49
+
50
+
51
+ def parse_ccshs_xml(xml_path, epoch_length_sec: float = 30.0) -> Dict[str, Any]:
52
+ """Parse a CCSHS NSRR XML.
53
+
54
+ Returns dict with ``labels`` (int16, indexed by event ONSET, length
55
+ ``floor(recording_duration / epoch)``), ``duration_sec`` (from the
56
+ "Recording Start Time" event, else the end of the last stage event) and
57
+ ``clock_time`` (string or None).
58
+ """
59
+ root = ET.parse(str(xml_path)).getroot()
60
+ events = []
61
+ rec_dur: Optional[float] = None
62
+ clock: Optional[str] = None
63
+ for ev in root.iter("ScoredEvent"):
64
+ etype = (ev.findtext("EventType") or "").strip()
65
+ concept = (ev.findtext("EventConcept") or "").strip()
66
+ if concept == "Recording Start Time":
67
+ rec_dur = float(ev.findtext("Duration"))
68
+ clock = ev.findtext("ClockTime")
69
+ continue
70
+ if not etype.startswith("Stages"):
71
+ continue
72
+ if concept not in CCSHS_STAGE_MAP:
73
+ raise ValueError(f"{xml_path}: unknown stage concept {concept!r}")
74
+ events.append(
75
+ (float(ev.findtext("Start")), float(ev.findtext("Duration")), concept)
76
+ )
77
+ if not events:
78
+ return {
79
+ "labels": np.array([], dtype=np.int16),
80
+ "duration_sec": rec_dur,
81
+ "clock_time": clock,
82
+ }
83
+ end = max(s + d for s, d, _ in events)
84
+ total = rec_dur if rec_dur is not None else end
85
+ n = int(total // epoch_length_sec)
86
+ labels = np.full(n, -1, dtype=np.int16)
87
+ for s, d, c in events:
88
+ i0 = int(round(s / epoch_length_sec))
89
+ i1 = min(int(round((s + d) / epoch_length_sec)), n)
90
+ if i0 < n:
91
+ labels[i0:i1] = CCSHS_STAGE_MAP[c]
92
+ return {"labels": labels, "duration_sec": total, "clock_time": clock}
93
+
94
+
95
+ class CCSHSDataset(_NSRRBaseDataset):
96
+ """Cleveland Children's Sleep and Health Study (~515 children/adolescents)."""
97
+
98
+ DATASET_NAME = "ccshs"
99
+ DATASET_SUBDIR = "ccshs"
100
+
101
+ #: Recordings left out by default. Nine: stage minutes in the XML differ from the NSRR
102
+ #: summary CSV (``ccshs-trec-dataset-*.csv``); two: no CSV row, so the hypnogram cannot be
103
+ #: cross-checked. Subject ids are EDF stems.
104
+ EXCLUDED_SUBJECTS = frozenset(
105
+ f"ccshs-trec-{n}"
106
+ for n in (
107
+ 1800064,
108
+ 1800224,
109
+ 1800306,
110
+ 1800384,
111
+ 1800432,
112
+ 1800458,
113
+ 1800620,
114
+ 1800640,
115
+ 1800873, # CSV-discordant
116
+ 1800109,
117
+ 1800489, # no CSV row
118
+ )
119
+ )
120
+
121
+ CHANNEL_PREFERENCES: Dict[str, List] = {
122
+ "EEG": [
123
+ ("C3", "A2"),
124
+ ("C4", "A1"),
125
+ "C3",
126
+ "C4",
127
+ ],
128
+ "EOG": [
129
+ ("LOC", "ROC"),
130
+ "LOC",
131
+ "ROC",
132
+ ],
133
+ "EMG": [
134
+ ("EMG1", "EMG2"),
135
+ ("EMG1", "EMG3"),
136
+ "EMG1",
137
+ ],
138
+ }
139
+
140
+ def __init__(
141
+ self,
142
+ root: Optional[str] = None,
143
+ exclude_subjects: Optional[Iterable[str]] = None,
144
+ **kwargs,
145
+ ):
146
+ """
147
+ Args:
148
+ root: dataset root (default ``<data_root>/ccshs``).
149
+ exclude_subjects: subject ids (``ccshs-trec-<nsrrid>``) to leave out. ``None`` uses
150
+ :attr:`EXCLUDED_SUBJECTS`; pass ``[]`` to keep every recording.
151
+ """
152
+ if root is None:
153
+ root = str(get_data_root() / self.DATASET_SUBDIR)
154
+ self._exclude = frozenset(
155
+ self.EXCLUDED_SUBJECTS if exclude_subjects is None else exclude_subjects
156
+ )
157
+ self._metadata = self._load_metadata(root)
158
+ super().__init__(root=root, **kwargs)
159
+
160
+ @staticmethod
161
+ def _load_metadata(root: str) -> Dict[str, Dict[str, Any]]:
162
+ # version-independent: ccshs-trec-dataset-<version>.csv (newest version wins)
163
+ found = sorted((Path(root) / "datasets").glob("ccshs-trec-dataset-*.csv"))
164
+ if not found:
165
+ logger.warning(
166
+ "CCSHS metadata CSV not found under %s", Path(root) / "datasets"
167
+ )
168
+ return {}
169
+ csv_path = found[-1]
170
+ import pandas as pd
171
+
172
+ df = pd.read_csv(csv_path, low_memory=False)
173
+ meta: Dict[str, Dict[str, Any]] = {}
174
+ for _, row in df.iterrows():
175
+ key = str(int(row["nsrrid"]))
176
+ meta[key] = {c: (None if pd.isna(v) else v) for c, v in row.items()}
177
+ return meta
178
+
179
+ def _list_subjects(self) -> List[SubjectSpec]:
180
+ root = Path(self.root)
181
+ specs = self._pair_edfs_with_xml(
182
+ root / "polysomnography" / "edfs",
183
+ root / "polysomnography" / "annotations-events-nsrr",
184
+ )
185
+ if self._exclude:
186
+ kept = [s for s in specs if s.subject_id not in self._exclude]
187
+ if len(kept) != len(specs):
188
+ logger.info(
189
+ "CCSHS: %d recordings excluded by default list",
190
+ len(specs) - len(kept),
191
+ )
192
+ specs = kept
193
+ for spec in specs:
194
+ nid = spec.subject_id.replace("ccshs-trec-", "")
195
+ if nid in self._metadata:
196
+ spec.external_meta.update(self._metadata[nid])
197
+ return specs
198
+
199
+ def _read_subject_labels(self, spec: SubjectSpec) -> np.ndarray:
200
+ return parse_ccshs_xml(spec.label_path, self.epoch_length_sec)["labels"]
@@ -426,6 +426,30 @@ Comprehensive reference of available channels, modality types, and sampling freq
426
426
 
427
427
  ---
428
428
 
429
+ ## 13. CCSHS (Cleveland Children's Sleep and Health Study)
430
+
431
+ **Source:** NSRR `ccshs-trec` - 515 children and adolescents, one in-home PSG each
432
+ **Native Sampling Rate:** 128 Hz (EEG, EOG), 256 Hz (chin EMG)
433
+ **Epoch Length:** 30 seconds
434
+ **Stage vocabulary:** Wake, Stage 1-4, REM, Movement (NSRR XML, `Stages|Stages`). Stage 3 and 4 are merged to N3; Movement and unscored epochs are -1.
435
+
436
+ | Channel Name | Modality Type | FS (Hz) | Usability | Notes |
437
+ |--------------|---------------|---------|-----------|-------|
438
+ | C3, C4 | EEG (0) | 128 | ✅ Usable | Recorded against the contralateral mastoid; use the pairs `(C3, A2)` and `(C4, A1)` |
439
+ | A1, A2 | - | 128 | ➖ Reference | Mastoid references, used only to build the pairs |
440
+ | LOC, ROC | EOG (1) | 128 | ✅ Usable | Pair `(LOC, ROC)` |
441
+ | EMG1, EMG2, EMG3 | EMG (2) | 256 | ✅ Usable | Chin EMG, pair `(EMG1, EMG2)` preferred |
442
+
443
+ **Modality Assessment:** ✅ Pair-resolved channels are classified correctly.
444
+
445
+ **Exclusions:** eleven recordings are left out by default (`CCSHSDataset.EXCLUDED_SUBJECTS`): nine whose per-stage minutes in the NSRR XML disagree with the stage minutes in the NSRR summary CSV, and two with no CSV row. Pass `exclude_subjects=[]` to keep them.
446
+
447
+ **Available Modalities for Sleep Staging:** EEG, EOG, EMG
448
+
449
+ **Summary:** Paediatric community cohort with a standard AASM-like montage at a lower sampling rate than the clinical datasets; mixed rates inside one EDF are handled by the per-channel pipeline.
450
+
451
+ ---
452
+
429
453
  ## Cross-Dataset Summary
430
454
 
431
455
  ### Sampling Rate Summary
@@ -444,6 +468,7 @@ Comprehensive reference of available channels, modality types, and sampling freq
444
468
  | WSC | 512 | Uniform high frequency |
445
469
  | Alzheimer's | 200 | Uniform |
446
470
  | Parkinson's | 500 | Highest uniform frequency |
471
+ | CCSHS | 128 (EEG, EOG), 256 (EMG) | Mixed inside one EDF |
447
472
 
448
473
  ### Modality Type Coverage
449
474
 
@@ -148,6 +148,8 @@ class STAGESDataset(BasePhysioDataset):
148
148
 
149
149
  DATASET_NAME = "stages"
150
150
  DATASET_SUBDIR = "stages"
151
+ #: 2: labels are placed by timestamp relative to the EDF start (fix 7b96d20); revision-1 caches are misaligned.
152
+ LABELS_REVISION = 2
151
153
  DEFAULT_EPOCH_LENGTH_SEC = 30.0
152
154
  CHANNEL_PREFERENCES = CHANNEL_PREFERENCES
153
155