physioex 2.0.1__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 (315) hide show
  1. physioex-2.0.3/CHANGELOG.md +124 -0
  2. {physioex-2.0.1 → physioex-2.0.3}/PKG-INFO +16 -3
  3. {physioex-2.0.1 → physioex-2.0.3}/README.md +15 -2
  4. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/architecture/library/data.md +3 -2
  5. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/data.md +25 -2
  6. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/explain/explain.md +6 -0
  7. physioex-2.0.3/physioex/__init__.py +1 -0
  8. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/__init__.py +4 -0
  9. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/base.py +107 -16
  10. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/__init__.py +3 -0
  11. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/alzheimers.py +2 -0
  12. physioex-2.0.3/physioex/data/datasets/ccshs.py +200 -0
  13. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/channels_table.md +25 -0
  14. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/stages.py +2 -0
  15. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/events.py +1 -1
  16. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/readers/annotations.py +5 -3
  17. physioex-2.0.3/physioex/data/sampling.py +91 -0
  18. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/foundation_datasets.py +8 -0
  19. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/protosleepnet.py +30 -9
  20. physioex-2.0.3/tests/test_ccshs.py +181 -0
  21. physioex-2.0.3/tests/test_channel_acc_tracking.py +87 -0
  22. physioex-2.0.3/tests/test_data_layer_hardening.py +207 -0
  23. physioex-2.0.3/tests/test_modality_dropout.py +62 -0
  24. physioex-2.0.3/tests/test_sampling.py +55 -0
  25. physioex-2.0.1/CHANGELOG.md +0 -61
  26. physioex-2.0.1/physioex/__init__.py +0 -1
  27. {physioex-2.0.1 → physioex-2.0.3}/.claudeignore +0 -0
  28. {physioex-2.0.1 → physioex-2.0.3}/.github/copilot-instructions.md +0 -0
  29. {physioex-2.0.1 → physioex-2.0.3}/.github/workflows/black.yml +0 -0
  30. {physioex-2.0.1 → physioex-2.0.3}/.github/workflows/ci.yml +0 -0
  31. {physioex-2.0.1 → physioex-2.0.3}/.github/workflows/publish.yaml +0 -0
  32. {physioex-2.0.1 → physioex-2.0.3}/.github/workflows/test.yml +0 -0
  33. {physioex-2.0.1 → physioex-2.0.3}/.gitignore +0 -0
  34. {physioex-2.0.1 → physioex-2.0.3}/LICENSE +0 -0
  35. {physioex-2.0.1 → physioex-2.0.3}/MANIFEST.in +0 -0
  36. {physioex-2.0.1 → physioex-2.0.3}/REPO_MAP.txt +0 -0
  37. {physioex-2.0.1 → physioex-2.0.3}/docs/_ext/related_works.py +0 -0
  38. {physioex-2.0.1 → physioex-2.0.3}/docs/_static/custom.css +0 -0
  39. {physioex-2.0.1 → physioex-2.0.3}/docs/api/index.md +0 -0
  40. {physioex-2.0.1 → physioex-2.0.3}/docs/assets/images/data/sequence_viz.png +0 -0
  41. {physioex-2.0.1 → physioex-2.0.3}/docs/assets/images/logo.psd +0 -0
  42. {physioex-2.0.1 → physioex-2.0.3}/docs/assets/images/logo.svg +0 -0
  43. {physioex-2.0.1 → physioex-2.0.3}/docs/assets/images/logo_bar.svg +0 -0
  44. {physioex-2.0.1 → physioex-2.0.3}/docs/conf.py +0 -0
  45. {physioex-2.0.1 → physioex-2.0.3}/docs/index.md +0 -0
  46. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/architecture/library/explain.md +0 -0
  47. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/architecture/library/models.md +0 -0
  48. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/architecture/library/overview.md +0 -0
  49. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/architecture/library/train.md +0 -0
  50. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/architecture/testing/overview.md +0 -0
  51. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/contribute.md +0 -0
  52. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/preprocess.md +0 -0
  53. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/train/cli.md +0 -0
  54. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/train/networks/chambon2018.md +0 -0
  55. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/train/networks.md +0 -0
  56. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/train/train.md +0 -0
  57. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/unavailable.md +0 -0
  58. {physioex-2.0.1 → physioex-2.0.3}/docs/pages/user_guide.md +0 -0
  59. {physioex-2.0.1 → physioex-2.0.3}/docs/related/_thumbs/_placeholder.svg +0 -0
  60. {physioex-2.0.1 → physioex-2.0.3}/docs/related/fetch_citations.py +0 -0
  61. {physioex-2.0.1 → physioex-2.0.3}/docs/related/index.md +0 -0
  62. {physioex-2.0.1 → physioex-2.0.3}/docs/related/works.yaml +0 -0
  63. {physioex-2.0.1 → physioex-2.0.3}/examples/__init__.py +0 -0
  64. {physioex-2.0.1 → physioex-2.0.3}/examples/analyze_csd_concepts/__init__.py +0 -0
  65. {physioex-2.0.1 → physioex-2.0.3}/examples/analyze_csd_concepts/compare_strategies.py +0 -0
  66. {physioex-2.0.1 → physioex-2.0.3}/examples/analyze_csd_concepts/extract_all_concepts.py +0 -0
  67. {physioex-2.0.1 → physioex-2.0.3}/examples/analyze_csd_concepts/report.py +0 -0
  68. {physioex-2.0.1 → physioex-2.0.3}/examples/analyze_csd_concepts/utils.py +0 -0
  69. {physioex-2.0.1 → physioex-2.0.3}/examples/analyze_csd_concepts/visualize_concepts.py +0 -0
  70. {physioex-2.0.1 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/README.md +0 -0
  71. {physioex-2.0.1 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/explain_csd.py +0 -0
  72. {physioex-2.0.1 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/train_probe.py +0 -0
  73. {physioex-2.0.1 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/utils.py +0 -0
  74. {physioex-2.0.1 → physioex-2.0.3}/examples/explain/conceptualspectraldecomposition/visualize.py +0 -0
  75. {physioex-2.0.1 → physioex-2.0.3}/examples/explain/lrp_seqsleepnet_n3.py +0 -0
  76. {physioex-2.0.1 → physioex-2.0.3}/examples/foundation/bendr/extract_embeddings.py +0 -0
  77. {physioex-2.0.1 → physioex-2.0.3}/examples/foundation/biot/extract_embeddings.py +0 -0
  78. {physioex-2.0.1 → physioex-2.0.3}/examples/foundation/cbramod/extract_embeddings.py +0 -0
  79. {physioex-2.0.1 → physioex-2.0.3}/examples/foundation/labram/extract_embeddings.py +0 -0
  80. {physioex-2.0.1 → physioex-2.0.3}/examples/foundation/neurolm/extract_embeddings.py +0 -0
  81. {physioex-2.0.1 → physioex-2.0.3}/examples/foundation/reve/extract_embeddings.py +0 -0
  82. {physioex-2.0.1 → physioex-2.0.3}/examples/foundation/sjepa/extract_embeddings.py +0 -0
  83. {physioex-2.0.1 → physioex-2.0.3}/examples/foundation/sleepfm/extract_embeddings.py +0 -0
  84. {physioex-2.0.1 → physioex-2.0.3}/examples/foundation/tfc/extract_embeddings.py +0 -0
  85. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/chambon2018/extract_embeddings.py +0 -0
  86. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/chambon2018/extract_embeddings.sbatch +0 -0
  87. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/chambon2018/quickstart.py +0 -0
  88. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/chambon2018/smoke.sbatch +0 -0
  89. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/chambon2018/test_pretrained.py +0 -0
  90. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/chambon2018/train.py +0 -0
  91. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/chambon2018/train.sbatch +0 -0
  92. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/coresleep-kontras/extract_embeddings.py +0 -0
  93. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/coresleep-kontras/train.py +0 -0
  94. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/extract_embeddings.py +0 -0
  95. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/quickstart.py +0 -0
  96. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/smoke.sbatch +0 -0
  97. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/test_pretrained.py +0 -0
  98. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/test_pretrained.sbatch +0 -0
  99. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/train.py +0 -0
  100. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/lseqsleepnet-phan/train.sbatch +0 -0
  101. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/protosleepnet/README.md +0 -0
  102. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/protosleepnet/quickstart.py +0 -0
  103. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/extract_embeddings.py +0 -0
  104. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/extract_embeddings.sbatch +0 -0
  105. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/quickstart.py +0 -0
  106. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/test.py +0 -0
  107. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/test_pretrained.py +0 -0
  108. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/test_pretrained.sbatch +0 -0
  109. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/test_stages_sites.py +0 -0
  110. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/train.py +0 -0
  111. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/train.sbatch +0 -0
  112. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/train_shhs.py +0 -0
  113. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/seqsleepnet-phan/train_shhs.sbatch +0 -0
  114. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/extract_embeddings.py +0 -0
  115. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/extract_embeddings.sbatch +0 -0
  116. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/quickstart.py +0 -0
  117. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/smoke.sbatch +0 -0
  118. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/test_pretrained.py +0 -0
  119. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/test_pretrained.sbatch +0 -0
  120. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/train.py +0 -0
  121. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/sleeptransformer-phan/train.sbatch +0 -0
  122. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.py +0 -0
  123. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.sbatch +0 -0
  124. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/quickstart.py +0 -0
  125. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/smoke.sbatch +0 -0
  126. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/test_pretrained.py +0 -0
  127. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/test_pretrained.sbatch +0 -0
  128. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/train.py +0 -0
  129. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tinysleepnet-supratak/train.sbatch +0 -0
  130. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/extract_embeddings.py +0 -0
  131. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/quickstart.py +0 -0
  132. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/smoke.sbatch +0 -0
  133. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/test_pretrained.py +0 -0
  134. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/train.py +0 -0
  135. {physioex-2.0.1 → physioex-2.0.3}/examples/pretrained/tsinalis-2016/train.sbatch +0 -0
  136. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/notebook.ipynb +0 -0
  137. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_barplot.png +0 -0
  138. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_hypnogram.png +0 -0
  139. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_barplot.png +0 -0
  140. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_hypnogram.png +0 -0
  141. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell17_ig_tiny.png +0 -0
  142. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell19_ig_seq.png +0 -0
  143. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell19b_attr_vs_psd.png +0 -0
  144. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell5_raw_n3_psd.png +0 -0
  145. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/outputs/cell7_spec_n3_psd.png +0 -0
  146. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/run_notebook.py +0 -0
  147. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/script.pdf +0 -0
  148. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/slides.pdf +0 -0
  149. {physioex-2.0.1 → physioex-2.0.3}/lectures/2026-06-24_ugent_xai/util.py +0 -0
  150. {physioex-2.0.1 → physioex-2.0.3}/lectures/README.md +0 -0
  151. {physioex-2.0.1 → physioex-2.0.3}/lectures/assets/kuleuven_logo.png +0 -0
  152. {physioex-2.0.1 → physioex-2.0.3}/lectures/assets/physioex_logo.png +0 -0
  153. {physioex-2.0.1 → physioex-2.0.3}/lectures/assets/physioex_logo.svg +0 -0
  154. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/cache.py +0 -0
  155. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/collate.py +0 -0
  156. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datareader.py +0 -0
  157. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/dataset.py +0 -0
  158. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/_nsrr.py +0 -0
  159. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/_splits/shhs.json +0 -0
  160. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/dcsm.py +0 -0
  161. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/hmc.py +0 -0
  162. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/hpap.py +0 -0
  163. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/mass.py +0 -0
  164. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/mesa.py +0 -0
  165. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/mros.py +0 -0
  166. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/parkinsons.py +0 -0
  167. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/shhs.py +0 -0
  168. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/sleepedf.py +0 -0
  169. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/datasets/wsc.py +0 -0
  170. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/modality.py +0 -0
  171. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/multi.py +0 -0
  172. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/pipeline.py +0 -0
  173. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/presets.py +0 -0
  174. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/readers/__init__.py +0 -0
  175. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/readers/edf.py +0 -0
  176. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/steps/__init__.py +0 -0
  177. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/steps/bandpass.py +0 -0
  178. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/steps/highpass.py +0 -0
  179. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/steps/identity.py +0 -0
  180. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/steps/normalize.py +0 -0
  181. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/steps/notch.py +0 -0
  182. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/steps/resample.py +0 -0
  183. {physioex-2.0.1 → physioex-2.0.3}/physioex/data/steps/spectrogram.py +0 -0
  184. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/__init__.py +0 -0
  185. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/foundational/__init__.py +0 -0
  186. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/foundational/csd.py +0 -0
  187. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/foundational/multichannel_sg.py +0 -0
  188. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/foundational/report.py +0 -0
  189. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/foundational/sleep_bands.py +0 -0
  190. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/foundational/specificity.py +0 -0
  191. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/__init__.py +0 -0
  192. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/_functional.py +0 -0
  193. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/_rules.py +0 -0
  194. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/attributor.py +0 -0
  195. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/canonizers.py +0 -0
  196. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/composites.py +0 -0
  197. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/diagnostics.py +0 -0
  198. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/model.py +0 -0
  199. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/pooling.py +0 -0
  200. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/recurrent.py +0 -0
  201. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/lrp/transformer.py +0 -0
  202. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/posthoc/__init__.py +0 -0
  203. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/posthoc/filters.py +0 -0
  204. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/posthoc/functionizer.py +0 -0
  205. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/posthoc/gradients.py +0 -0
  206. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/posthoc/metrics/__init__.py +0 -0
  207. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/posthoc/spectralgradients.py +0 -0
  208. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/posthoc/vidft.py +0 -0
  209. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/posthoc/vistdft.py +0 -0
  210. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/prototypes/__init__.py +0 -0
  211. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/prototypes/local.py +0 -0
  212. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/prototypes/posthoc/__init__.py +0 -0
  213. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/prototypes/posthoc/nmf.py +0 -0
  214. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/prototypes/posthoc/utils.py +0 -0
  215. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/prototypes/posthoc/vq.py +0 -0
  216. {physioex-2.0.1 → physioex-2.0.3}/physioex/explain/prototypes/reconstruct.py +0 -0
  217. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/__init__.py +0 -0
  218. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/bendr.py +0 -0
  219. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/biot.py +0 -0
  220. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/cbramod.py +0 -0
  221. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/chambon2018.py +0 -0
  222. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/coresleep.py +0 -0
  223. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/embed.py +0 -0
  224. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/foundation_base.py +0 -0
  225. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/foundation_channel_mapping.py +0 -0
  226. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/foundation_checkpoints.py +0 -0
  227. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/foundation_preproc.py +0 -0
  228. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/labram.py +0 -0
  229. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/lseqsleepnet.py +0 -0
  230. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/neurolm.py +0 -0
  231. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/pretrained.py +0 -0
  232. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/reve.py +0 -0
  233. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/seqsleepnet.py +0 -0
  234. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/sjepa.py +0 -0
  235. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/sleep_tokenizer.py +0 -0
  236. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/sleepfm.py +0 -0
  237. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/sleeptransformer.py +0 -0
  238. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/tfc.py +0 -0
  239. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/tinysleepnet.py +0 -0
  240. {physioex-2.0.1 → physioex-2.0.3}/physioex/models/tsinalis.py +0 -0
  241. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/__init__.py +0 -0
  242. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/bin/__init__.py +0 -0
  243. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/bin/_common.py +0 -0
  244. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/bin/finetune.py +0 -0
  245. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/bin/test.py +0 -0
  246. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/bin/train.py +0 -0
  247. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/logger.py +0 -0
  248. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/metrics.py +0 -0
  249. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/models/__init__.py +0 -0
  250. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/models/check_table.csv +0 -0
  251. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/models/load.py +0 -0
  252. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/multidevicetrainer.py +0 -0
  253. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/progress.py +0 -0
  254. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/stats.py +0 -0
  255. {physioex-2.0.1 → physioex-2.0.3}/physioex/train/trainer.py +0 -0
  256. {physioex-2.0.1 → physioex-2.0.3}/pyproject.toml +0 -0
  257. {physioex-2.0.1 → physioex-2.0.3}/tests/__init__.py +0 -0
  258. {physioex-2.0.1 → physioex-2.0.3}/tests/conftest.py +0 -0
  259. {physioex-2.0.1 → physioex-2.0.3}/tests/data/test_base_smoke.py +0 -0
  260. {physioex-2.0.1 → physioex-2.0.3}/tests/data/test_modality.py +0 -0
  261. {physioex-2.0.1 → physioex-2.0.3}/tests/data/test_multi.py +0 -0
  262. {physioex-2.0.1 → physioex-2.0.3}/tests/data/test_readers_annotations.py +0 -0
  263. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/foundational/test_sleep_bands.py +0 -0
  264. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/foundational/test_specificity.py +0 -0
  265. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/lrp/__init__.py +0 -0
  266. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/lrp/test_functional.py +0 -0
  267. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/lrp/test_lrp.py +0 -0
  268. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/lrp/test_model.py +0 -0
  269. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/lrp/test_real_models.py +0 -0
  270. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/lrp/test_recurrent.py +0 -0
  271. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/lrp/test_transformer.py +0 -0
  272. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/posthoc/__init__.py +0 -0
  273. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/posthoc/test_gradients.py +0 -0
  274. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/posthoc/test_metrics.py +0 -0
  275. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/posthoc/test_spectralgradients.py +0 -0
  276. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/posthoc/test_vidft.py +0 -0
  277. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/posthoc/test_vistdft.py +0 -0
  278. {physioex-2.0.1 → physioex-2.0.3}/tests/explain/prototypes/posthoc/test_nmf_vq.py +0 -0
  279. {physioex-2.0.1 → physioex-2.0.3}/tests/factories/__init__.py +0 -0
  280. {physioex-2.0.1 → physioex-2.0.3}/tests/factories/edf.py +0 -0
  281. {physioex-2.0.1 → physioex-2.0.3}/tests/models/test_architectures.py +0 -0
  282. {physioex-2.0.1 → physioex-2.0.3}/tests/models/test_foundation_base.py +0 -0
  283. {physioex-2.0.1 → physioex-2.0.3}/tests/models/test_foundation_encoders.py +0 -0
  284. {physioex-2.0.1 → physioex-2.0.3}/tests/models/test_foundation_preproc.py +0 -0
  285. {physioex-2.0.1 → physioex-2.0.3}/tests/models/test_pretrained_embed_helpers.py +0 -0
  286. {physioex-2.0.1 → physioex-2.0.3}/tests/models/test_sleep_tokenizer.py +0 -0
  287. {physioex-2.0.1 → physioex-2.0.3}/tests/test_alzheimers_parkinsons.py +0 -0
  288. {physioex-2.0.1 → physioex-2.0.3}/tests/test_api_surface.py +0 -0
  289. {physioex-2.0.1 → physioex-2.0.3}/tests/test_cache.py +0 -0
  290. {physioex-2.0.1 → physioex-2.0.3}/tests/test_cache_enabled.py +0 -0
  291. {physioex-2.0.1 → physioex-2.0.3}/tests/test_channel_resolution.py +0 -0
  292. {physioex-2.0.1 → physioex-2.0.3}/tests/test_cli_workflows.py +0 -0
  293. {physioex-2.0.1 → physioex-2.0.3}/tests/test_dataset_config.py +0 -0
  294. {physioex-2.0.1 → physioex-2.0.3}/tests/test_e2e_smoke.py +0 -0
  295. {physioex-2.0.1 → physioex-2.0.3}/tests/test_events.py +0 -0
  296. {physioex-2.0.1 → physioex-2.0.3}/tests/test_explain_fixes.py +0 -0
  297. {physioex-2.0.1 → physioex-2.0.3}/tests/test_label_hardening.py +0 -0
  298. {physioex-2.0.1 → physioex-2.0.3}/tests/test_load_model.py +0 -0
  299. {physioex-2.0.1 → physioex-2.0.3}/tests/test_mass_dataset.py +0 -0
  300. {physioex-2.0.1 → physioex-2.0.3}/tests/test_metrics_and_packaging.py +0 -0
  301. {physioex-2.0.1 → physioex-2.0.3}/tests/test_phase_b_datasets.py +0 -0
  302. {physioex-2.0.1 → physioex-2.0.3}/tests/test_phase_c_datasets.py +0 -0
  303. {physioex-2.0.1 → physioex-2.0.3}/tests/test_pipeline.py +0 -0
  304. {physioex-2.0.1 → physioex-2.0.3}/tests/test_presets.py +0 -0
  305. {physioex-2.0.1 → physioex-2.0.3}/tests/test_raw_dataset_integration.py +0 -0
  306. {physioex-2.0.1 → physioex-2.0.3}/tests/test_reading_modes.py +0 -0
  307. {physioex-2.0.1 → physioex-2.0.3}/tests/test_stages_dataset.py +0 -0
  308. {physioex-2.0.1 → physioex-2.0.3}/tests/test_trainer_enhancements.py +0 -0
  309. {physioex-2.0.1 → physioex-2.0.3}/tests/test_trainer_fixes.py +0 -0
  310. {physioex-2.0.1 → physioex-2.0.3}/tests/test_trainer_integration.py +0 -0
  311. {physioex-2.0.1 → physioex-2.0.3}/tests/test_wake_trimming.py +0 -0
  312. {physioex-2.0.1 → physioex-2.0.3}/tests/test_wsc_dataset.py +0 -0
  313. {physioex-2.0.1 → physioex-2.0.3}/tests/train/test_logger.py +0 -0
  314. {physioex-2.0.1 → physioex-2.0.3}/tests/train/test_progress.py +0 -0
  315. {physioex-2.0.1 → physioex-2.0.3}/tests/train/test_stats.py +0 -0
@@ -0,0 +1,124 @@
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.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
+
58
+ ## 2.0.2 — 2026-10-10
59
+
60
+ ### Fixed — accuracy-weighted modality dropout (`physioex.models.protosleepnet`)
61
+
62
+ `ZeroEmbeddingDropout` now samples the channels to occlude with probability
63
+ proportional to their accuracy, `p_c = a_c / sum_j a_j`, so the channels the
64
+ model relies on most are dropped most often. This is the rule the released
65
+ ProtoSleepNet and ProtoSleepTransformer models were trained with; a later
66
+ refactoring had inverted the sampling weights to `1 - a_c`. When no accuracy
67
+ estimate is available yet the sampling is uniform. Unit tests in
68
+ `tests/test_modality_dropout.py`.
69
+
70
+ ## 2.0.1 — 2026-09-13
71
+
72
+ ### Added — Layer-wise Relevance Propagation (`physioex.explain.lrp`)
73
+
74
+ A conservation-based attribution family (Bach et al. 2015) covering the whole
75
+ model zoo. Install with the new optional extra `pip install "physioex[explain]"`
76
+ (Zennit only; no LXT dependency).
77
+
78
+ - **Two entry points**, both seeding the target neuron with its logit so that
79
+ `Σ R ≈ f_c(x)`, both returning a per-sample `ConservationReport` with
80
+ `return_report=True`:
81
+ - `LRP` — Zennit composites for feed-forward / CNN models (Tsinalis,
82
+ Chambon2018): ε on dense, γ on convolutions, **w²** on the first layer
83
+ (unbounded EEG, not the pixel z-box), BatchNorm canonized; αβ / flat / z-box
84
+ configurable via `physioex_composite`.
85
+ - `ModelLRP` — whole-model LRP for recurrent, attention and transformer
86
+ architectures (TinySleepNet, SeqSleepNet, L-SeqSleepNet, SleepTransformer,
87
+ CoReSleep, ProtoSleepNet): **Arras signal-take** rule for LSTM/GRU at cell
88
+ level (`LRPLSTM`/`LRPGRU`, drop-in subclasses with a bit-identical forward,
89
+ `gate_rule="signal_take" | "uniform"`), **CP-LRP** for attention (value
90
+ path, exact conservation; Ali et al. 2022), identity rule for
91
+ LayerNorm/activations, proportional rule for residual sums, ε for linear
92
+ leaves and PhysioEx's learnable filterbank, CP-LRP adapters for the softmax
93
+ poolings (`AttentionPooling`, `AttentionLayer`, `ChannelMixer`).
94
+ - `prepare_model_for_lrp` rewrites a deep copy of the model; plain `+` residuals
95
+ written in a model's own `forward` are redirected to the proportional rule at
96
+ runtime (`patch_residuals`); `audit_lrp_coverage` / `strict=True` report any
97
+ parametric leaf left on plain autograd; `register_lrp_adapter` adds custom
98
+ blocks. Signed, dtype-safe stabiliser `z + ε·sign z` everywhere (fp16-safe).
99
+ - Docs: entry-point/rule matrix, how to read conservation and choose ε (ε must be
100
+ scaled to the activations: measured on the pretrained SeqSleepNet, 1e-6 inflates
101
+ relevance 2–3×, 1e-2 conserves), unsupported cases, and a literature note on
102
+ LRP vs path-based attribution (Deep Taylor, Integrated Gradients,
103
+ DeepLIFT/DeepSHAP). `physioex.explain.lrp` is in the API reference.
104
+ - Example: `examples/explain/lrp_seqsleepnet_n3.py` explains the N3 logit of the
105
+ pretrained `seqsleepnet-phan` on MASS training sequences with Saliency,
106
+ Input×Gradient, Integrated Gradients and LRP under different rule assignments.
107
+ - Tests: 91 new tests (`tests/explain/lrp/`), including forward-equivalence to
108
+ the fused modules, exact conservation on bias-free blocks and models, and all
109
+ six real architectures end-to-end with full rule coverage.
110
+
111
+ ### Known limitations
112
+
113
+ CP-LRP assigns zero relevance to the attention query/key path by design (full
114
+ AttnLRP is not implemented); bias relevance is absorbed (LRP-ε convention);
115
+ attention masks, `batch_first=False` transformers, explicit RNN initial states
116
+ and `proj_size` raise; ProtoSleepNet's VQ path runs under `no_grad`. Foundation
117
+ encoders and frequency-domain (DFT/STFT) LRP are planned follow-ups.
118
+
119
+ ## 2.0.0 — 2026-07-15
120
+
121
+ Ground-up release: raw-EDF lazy data layer with 12 dataset loaders, 9 pretrained
122
+ foundation encoders behind a uniform `encode()` interface, hand-written `Trainer`,
123
+ pytest suite and Sphinx documentation. Full notes in the
124
+ [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.1
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
@@ -64,7 +64,7 @@ Description-Content-Type: text/markdown
64
64
  # PhysioEx
65
65
 
66
66
  ![Python Version](https://img.shields.io/badge/python-3.11%2B-blue)
67
- [![PyPI Version](https://img.shields.io/pypi/v/physioex)](https://pypi.org/project/physioex/)
67
+ [![PyPI Version](https://img.shields.io/pypi/v/physioex?cacheSeconds=3600)](https://pypi.org/project/physioex/)
68
68
 
69
69
  **PhysioEx ( Physiological Signal Explainer )** is a versatile python library tailored for building, training, and explaining deep learning models for physiological signal analysis.
70
70
 
@@ -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
@@ -172,8 +173,20 @@ Note: the github version of the library is kept updated weekly, the PiPy version
172
173
  $ pip install "physioex[foundation]" # transformers, for the REVE encoder
173
174
  $ pip install "physioex[datasets]" # mne, for the HOMEPAP EDF fallback reader
174
175
  $ pip install "physioex[tracking]" # tensorboard / wandb logging
176
+ $ pip install "physioex[explain]" # zennit, for Layer-wise Relevance Propagation
175
177
  ```
176
178
 
179
+ > **Note on `torchvision` (CPU-only installs).** The `explain` extra depends on
180
+ > `zennit`, which requires `torchvision`. If your `torch` comes from the PyTorch
181
+ > CPU index, install `torchvision` from the **same** index *before* the extra,
182
+ > otherwise pip pulls a CUDA build from PyPI that fails at import with
183
+ > `operator torchvision::nms does not exist`:
184
+ >
185
+ > ```bash
186
+ > $ pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
187
+ > $ pip install "physioex[explain]"
188
+ > ```
189
+
177
190
  ## Quickstart
178
191
 
179
192
  Set `PHYSIOEX_DATA` to the directory holding your datasets, then use the raw-EDF
@@ -5,7 +5,7 @@
5
5
  # PhysioEx
6
6
 
7
7
  ![Python Version](https://img.shields.io/badge/python-3.11%2B-blue)
8
- [![PyPI Version](https://img.shields.io/pypi/v/physioex)](https://pypi.org/project/physioex/)
8
+ [![PyPI Version](https://img.shields.io/pypi/v/physioex?cacheSeconds=3600)](https://pypi.org/project/physioex/)
9
9
 
10
10
  **PhysioEx ( Physiological Signal Explainer )** is a versatile python library tailored for building, training, and explaining deep learning models for physiological signal analysis.
11
11
 
@@ -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
@@ -113,8 +114,20 @@ Note: the github version of the library is kept updated weekly, the PiPy version
113
114
  $ pip install "physioex[foundation]" # transformers, for the REVE encoder
114
115
  $ pip install "physioex[datasets]" # mne, for the HOMEPAP EDF fallback reader
115
116
  $ pip install "physioex[tracking]" # tensorboard / wandb logging
117
+ $ pip install "physioex[explain]" # zennit, for Layer-wise Relevance Propagation
116
118
  ```
117
119
 
120
+ > **Note on `torchvision` (CPU-only installs).** The `explain` extra depends on
121
+ > `zennit`, which requires `torchvision`. If your `torch` comes from the PyTorch
122
+ > CPU index, install `torchvision` from the **same** index *before* the extra,
123
+ > otherwise pip pulls a CUDA build from PyPI that fails at import with
124
+ > `operator torchvision::nms does not exist`:
125
+ >
126
+ > ```bash
127
+ > $ pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
128
+ > $ pip install "physioex[explain]"
129
+ > ```
130
+
118
131
  ## Quickstart
119
132
 
120
133
  Set `PHYSIOEX_DATA` to the directory holding your datasets, then use the raw-EDF
@@ -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`:
@@ -64,6 +64,12 @@ is **optional** — install the extra:
64
64
  pip install "physioex[explain]"
65
65
  ```
66
66
 
67
+ The extra pulls in `zennit`, and therefore `torchvision`. On a CPU-only `torch`
68
+ from the PyTorch index, install `torch torchvision` from that same index *first*
69
+ (`pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu`),
70
+ or pip will fetch a mismatched CUDA `torchvision` from PyPI that fails at import
71
+ (`operator torchvision::nms does not exist`).
72
+
67
73
  Two entry points, both seeding the target neuron with its **logit value** so
68
74
  that `Σ R ≈ f_c(x)` and returning relevance shaped like the input:
69
75
 
@@ -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.