braindecode 1.8.1.dev169484576__tar.gz → 1.8.1.dev169575464__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 (217) hide show
  1. {braindecode-1.8.1.dev169484576/braindecode.egg-info → braindecode-1.8.1.dev169575464}/PKG-INFO +4 -3
  2. braindecode-1.8.1.dev169575464/braindecode/__init__.py +53 -0
  3. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/augmentation/base.py +99 -3
  4. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/augmentation/transforms.py +2 -1
  5. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/classifier.py +13 -2
  6. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/base.py +90 -9
  7. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/bids/datasets.py +32 -8
  8. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datautil/serialization.py +15 -2
  9. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datautil/util.py +3 -2
  10. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/eegneuralnet.py +50 -8
  11. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/functional/__init__.py +15 -0
  12. braindecode-1.8.1.dev169575464/braindecode/functional/_lfilter.py +118 -0
  13. braindecode-1.8.1.dev169575464/braindecode/functional/channel_mask.py +214 -0
  14. braindecode-1.8.1.dev169575464/braindecode/functional/patches.py +105 -0
  15. braindecode-1.8.1.dev169575464/braindecode/models/__init__.py +133 -0
  16. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/atcnet.py +5 -2
  17. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/axon.py +5 -3
  18. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/barista.py +80 -35
  19. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/base.py +77 -14
  20. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/bendr.py +4 -6
  21. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/biot.py +12 -58
  22. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/brainbert.py +41 -32
  23. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/brainmodule.py +1 -1
  24. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/brant.py +58 -37
  25. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/codebrain.py +1 -1
  26. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/config.py +10 -6
  27. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/csbrain.py +3 -3
  28. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/ctnet.py +42 -150
  29. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/diver1.py +143 -33
  30. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/duin.py +41 -6
  31. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegconformer.py +86 -76
  32. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegdino.py +17 -74
  33. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eeginception_erp.py +12 -5
  34. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eeginception_mi.py +7 -3
  35. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegpt.py +19 -215
  36. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/emg2qwerty.py +6 -2
  37. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/epint.py +39 -21
  38. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/labram.py +2 -6
  39. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/luna.py +25 -52
  40. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/medformer.py +11 -20
  41. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/meta_neuromotor.py +6 -2
  42. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/mirepnet.py +27 -79
  43. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/mscformer.py +42 -161
  44. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/mvpformer.py +79 -11
  45. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/neurorvq.py +39 -61
  46. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/neurorvq_tokenizer.py +5 -5
  47. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/patchedtransformer.py +2 -3
  48. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/popt.py +13 -11
  49. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/reve.py +10 -10
  50. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/signal_jepa.py +7 -6
  51. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/sleepfm.py +3 -3
  52. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/util.py +129 -34
  53. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/zuna.py +115 -4
  54. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/__init__.py +12 -0
  55. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/attention.py +101 -5
  56. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/blocks.py +2 -61
  57. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/dance_modules.py +5 -3
  58. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/filter.py +6 -8
  59. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/layers.py +187 -2
  60. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/parametrization.py +10 -2
  61. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/preprocessing/eegprep_preprocess.py +61 -0
  62. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/preprocessing/mne_preprocess.py +79 -4
  63. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/preprocessing/preprocess.py +34 -24
  64. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/preprocessing/windowers.py +9 -8
  65. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/regressor.py +7 -1
  66. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/samplers/base.py +27 -17
  67. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/training/__init__.py +6 -0
  68. braindecode-1.8.1.dev169575464/braindecode/training/_trial_aggregation.py +106 -0
  69. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/training/scoring.py +335 -122
  70. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/util.py +15 -10
  71. braindecode-1.8.1.dev169575464/braindecode/version.py +1 -0
  72. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/visualization/frequency.py +11 -7
  73. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464/braindecode.egg-info}/PKG-INFO +4 -3
  74. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode.egg-info/SOURCES.txt +6 -1
  75. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode.egg-info/requires.txt +3 -2
  76. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/api.rst +18 -0
  77. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/index.rst +1 -0
  78. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/install/install.rst +1 -1
  79. braindecode-1.8.1.dev169575464/docs/user_guide/cropped_inference.rst +130 -0
  80. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/whats_new.rst +156 -2
  81. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/pyproject.toml +8 -2
  82. braindecode-1.8.1.dev169484576/braindecode/__init__.py +0 -9
  83. braindecode-1.8.1.dev169484576/braindecode/models/__init__.py +0 -197
  84. braindecode-1.8.1.dev169484576/braindecode/version.py +0 -1
  85. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/LICENSE.txt +0 -0
  86. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/MANIFEST.in +0 -0
  87. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/NOTICE.txt +0 -0
  88. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/README.rst +0 -0
  89. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/augmentation/__init__.py +0 -0
  90. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/augmentation/functional.py +0 -0
  91. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/__init__.py +0 -0
  92. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/_notebook_viewer.py +0 -0
  93. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/bbci.py +0 -0
  94. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/bcicomp.py +0 -0
  95. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/bids/__init__.py +0 -0
  96. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/bids/hub.py +0 -0
  97. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/bids/hub_format.py +0 -0
  98. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/bids/hub_io.py +0 -0
  99. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/bids/hub_validation.py +0 -0
  100. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/bids/iterable.py +0 -0
  101. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/chb_mit.py +0 -0
  102. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/collate.py +0 -0
  103. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/mne.py +0 -0
  104. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/moabb.py +0 -0
  105. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/nmt.py +0 -0
  106. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/registry.py +0 -0
  107. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/siena.py +0 -0
  108. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  109. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/sleep_physionet.py +0 -0
  110. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/tuh.py +0 -0
  111. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/utils.py +0 -0
  112. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datasets/xy.py +0 -0
  113. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datautil/__init__.py +0 -0
  114. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datautil/channel_utils.py +0 -0
  115. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/datautil/hub_formats.py +0 -0
  116. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/functional/_real_dft.py +0 -0
  117. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/functional/attention.py +0 -0
  118. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/functional/functions.py +0 -0
  119. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/functional/initialization.py +0 -0
  120. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/attentionbasenet.py +0 -0
  121. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/attn_sleep.py +0 -0
  122. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/brainomni.py +0 -0
  123. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/cbramod.py +0 -0
  124. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/contrawr.py +0 -0
  125. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/dance.py +0 -0
  126. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/deep4.py +0 -0
  127. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/deepsleepnet.py +0 -0
  128. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/dgcnn.py +0 -0
  129. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eeg_clip.py +0 -0
  130. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegitnet.py +0 -0
  131. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegminer.py +0 -0
  132. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegnet.py +0 -0
  133. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegnex.py +0 -0
  134. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegsimpleconv.py +0 -0
  135. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegsym.py +0 -0
  136. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/eegtcnet.py +0 -0
  137. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/fbcnet.py +0 -0
  138. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/fblightconvnet.py +0 -0
  139. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/fbmsnet.py +0 -0
  140. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/gcnsnet.py +0 -0
  141. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/guetschel2026.py +0 -0
  142. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/hybrid.py +0 -0
  143. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/ifnet.py +0 -0
  144. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/mapa.py +0 -0
  145. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/msvtnet.py +0 -0
  146. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/neuropose.py +0 -0
  147. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/sccnet.py +0 -0
  148. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/seizure_transformer.py +0 -0
  149. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/sensingdynamics.py +0 -0
  150. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/shallow_fbcsp.py +0 -0
  151. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/sinc_shallow.py +0 -0
  152. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
  153. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
  154. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/sparcnet.py +0 -0
  155. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/sstdpn.py +0 -0
  156. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/steegformer.py +0 -0
  157. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/summary.csv +0 -0
  158. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/syncnet.py +0 -0
  159. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/tcformer.py +0 -0
  160. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/tcn.py +0 -0
  161. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/tfm_tokenizer.py +0 -0
  162. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/tidnet.py +0 -0
  163. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/tmsanet.py +0 -0
  164. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/tsinception.py +0 -0
  165. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/usleep.py +0 -0
  166. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/models/vemg2pose.py +0 -0
  167. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/activation.py +0 -0
  168. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/channels.py +0 -0
  169. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/convolution.py +0 -0
  170. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/linear.py +0 -0
  171. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/linear_attention.py +0 -0
  172. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/quantization.py +0 -0
  173. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/stats.py +0 -0
  174. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/util.py +0 -0
  175. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/modules/wrapper.py +0 -0
  176. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/preprocessing/__init__.py +0 -0
  177. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/preprocessing/util.py +0 -0
  178. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/samplers/__init__.py +0 -0
  179. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/samplers/ssl.py +0 -0
  180. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/training/callbacks.py +0 -0
  181. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/training/losses.py +0 -0
  182. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/visualization/__init__.py +0 -0
  183. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/visualization/activations.py +0 -0
  184. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/visualization/attribution.py +0 -0
  185. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/visualization/confusion_matrices.py +0 -0
  186. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/visualization/metrics.py +0 -0
  187. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/visualization/sanity.py +0 -0
  188. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode/visualization/topology.py +0 -0
  189. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode.egg-info/dependency_links.txt +0 -0
  190. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/braindecode.egg-info/top_level.txt +0 -0
  191. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/Makefile +0 -0
  192. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/_templates/autosummary/class.rst +0 -0
  193. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  194. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/_templates/autosummary/function.rst +0 -0
  195. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  196. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/agent_docs.rst +0 -0
  197. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/cite.rst +0 -0
  198. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/conf.py +0 -0
  199. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/help.rst +0 -0
  200. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/install/install_pip.rst +0 -0
  201. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/install/install_source.rst +0 -0
  202. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/categorization/attention.rst +0 -0
  203. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/categorization/channel.rst +0 -0
  204. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/categorization/convolution.rst +0 -0
  205. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/categorization/filterbank.rst +0 -0
  206. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/categorization/gnn.rst +0 -0
  207. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/categorization/interpretable.rst +0 -0
  208. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/categorization/lbm.rst +0 -0
  209. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/categorization/recurrent.rst +0 -0
  210. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/categorization/spd.rst +0 -0
  211. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/models.rst +0 -0
  212. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/models_categorization.rst +0 -0
  213. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/models_table.rst +0 -0
  214. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/models/models_visualization.rst +0 -0
  215. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/sg_execution_times.rst +0 -0
  216. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/docs/user_guide/channel_strategies.rst +0 -0
  217. {braindecode-1.8.1.dev169484576 → braindecode-1.8.1.dev169575464}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.8.1.dev169484576
3
+ Version: 1.8.1.dev169575464
4
4
  Summary: Deep learning software to decode EEG, ECG or MEG signals
5
5
  Author-email: Robin Tibor Schirrmeister <robintibor@gmail.com>, Bruno Aristimunha Pinto <b.aristimunha@gmail.com>, Alexandre Gramfort <agramfort@meta.com>
6
6
  Maintainer-email: Alexandre Gramfort <agramfort@meta.com>, Bruno Aristimunha Pinto <b.aristimunha@gmail.com>, Robin Tibor Schirrmeister <robintibor@gmail.com>
@@ -22,8 +22,8 @@ Requires-Python: >=3.11
22
22
  Description-Content-Type: text/x-rst
23
23
  License-File: LICENSE.txt
24
24
  License-File: NOTICE.txt
25
- Requires-Dist: torch>=2.4
26
- Requires-Dist: torchaudio>=2.4
25
+ Requires-Dist: torch>=2.6
26
+ Requires-Dist: torchaudio>=2.6
27
27
  Requires-Dist: mne>=1.11.0
28
28
  Requires-Dist: mne_bids>=0.18
29
29
  Requires-Dist: h5py
@@ -50,6 +50,7 @@ Provides-Extra: tests
50
50
  Requires-Dist: pytest<9.1; extra == "tests"
51
51
  Requires-Dist: pytest-cov; extra == "tests"
52
52
  Requires-Dist: pytest-xdist>=3.8.0; extra == "tests"
53
+ Requires-Dist: pytest-timeout; extra == "tests"
53
54
  Requires-Dist: codecov; extra == "tests"
54
55
  Requires-Dist: pytest_cases; extra == "tests"
55
56
  Requires-Dist: mypy; extra == "tests"
@@ -0,0 +1,53 @@
1
+ import importlib
2
+ from typing import TYPE_CHECKING
3
+
4
+ from .version import __version__
5
+
6
+ if TYPE_CHECKING: # type checkers and IDEs see the classes; runtime uses __getattr__
7
+ from .classifier import EEGClassifier
8
+ from .regressor import EEGRegressor
9
+
10
+ __all__ = [
11
+ "__version__",
12
+ "EEGClassifier",
13
+ "EEGRegressor",
14
+ ]
15
+
16
+ # Subpackages reachable as ``braindecode.<name>`` after a bare ``import braindecode``.
17
+ _SUBMODULES = frozenset(
18
+ {
19
+ "augmentation",
20
+ "classifier",
21
+ "datasets",
22
+ "datautil",
23
+ "eegneuralnet",
24
+ "functional",
25
+ "models",
26
+ "modules",
27
+ "preprocessing",
28
+ "regressor",
29
+ "samplers",
30
+ "training",
31
+ "util",
32
+ "visualization",
33
+ }
34
+ )
35
+
36
+ if not TYPE_CHECKING: # hidden from checkers so misspelled names stay errors
37
+
38
+ def __getattr__(name):
39
+ # PEP 562: skorch, the datasets and the models load on first use.
40
+ if name == "EEGClassifier":
41
+ from .classifier import EEGClassifier
42
+
43
+ return EEGClassifier
44
+ if name == "EEGRegressor":
45
+ from .regressor import EEGRegressor
46
+
47
+ return EEGRegressor
48
+ if name in _SUBMODULES:
49
+ return importlib.import_module(f".{name}", __name__)
50
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
51
+
52
+ def __dir__():
53
+ return sorted(set(globals()) | set(__all__) | _SUBMODULES)
@@ -9,10 +9,11 @@
9
9
  from numbers import Real
10
10
  from typing import Any, Callable, Optional, Union
11
11
 
12
+ import numpy as np
12
13
  import torch
13
14
  from sklearn.utils import check_random_state
14
15
  from torch import Tensor, nn
15
- from torch.utils.data import DataLoader
16
+ from torch.utils.data import DataLoader, get_worker_info
16
17
  from torch.utils.data._utils.collate import default_collate
17
18
 
18
19
  from .functional import identity
@@ -191,6 +192,59 @@ def _as_mixed_target(y, lam_dtype):
191
192
  return y, y, lam
192
193
 
193
194
 
195
+ def _transform_rngs(transform):
196
+ """Return the ``RandomState`` objects of a transform, in a fixed order.
197
+
198
+ Walks the transform, its submodules and the ``transforms`` of a
199
+ :class:`Compose`, and lists each ``rng`` once (pooled transforms of
200
+ :class:`braindecode.augmentation.TrivialAugment` share theirs). The global
201
+ numpy generator, used with ``random_state=None``, is included: a spawned
202
+ worker receives a pickled copy of it, which torch does not reseed.
203
+ """
204
+ rngs, seen, stack = [], set(), [transform]
205
+ while stack:
206
+ tr = stack.pop(0)
207
+ if id(tr) in seen:
208
+ continue
209
+ seen.add(id(tr))
210
+ # Compose forwards to its children without using its inherited RNG.
211
+ rng = None if type(tr) is Compose else getattr(tr, "rng", None)
212
+ if isinstance(rng, np.random.RandomState) and id(rng) not in seen:
213
+ seen.add(id(rng))
214
+ rngs.append(rng)
215
+ children = list(getattr(tr, "transforms", None) or [])
216
+ if isinstance(tr, nn.Module):
217
+ children += list(tr.children())
218
+ stack.extend(children)
219
+ return rngs
220
+
221
+
222
+ def _assert_stackable(items, name):
223
+ # Fixed expansion concatenates the clean originals with the transformed
224
+ # copies, so every copy must keep the original's shape except along the
225
+ # batch dimension. A transform that reshapes X or y (e.g. one returning
226
+ # class distributions instead of class indices) would otherwise fail deep
227
+ # inside torch.cat with a bare dimension-mismatch RuntimeError.
228
+ reference = items[0]
229
+ for item in items[1:]:
230
+ pairs = (
231
+ zip(reference, item)
232
+ if isinstance(reference, (tuple, list))
233
+ else [(reference, item)]
234
+ )
235
+ for ref_part, part in pairs:
236
+ if part.dim() != ref_part.dim() or part.shape[1:] != ref_part.shape[1:]:
237
+ raise ValueError(
238
+ f"The transform returned {name} with shape "
239
+ f"{tuple(part.shape)} instead of {tuple(ref_part.shape)}: "
240
+ f"the fixed-expansion mode (n_augmentation > 0) keeps the "
241
+ f"clean originals and concatenates the transformed "
242
+ f"copies, so it cannot accommodate a transform that "
243
+ f"changes the shape of {name}. Use n_augmentation = 0 "
244
+ f"(in-place augmentation) instead."
245
+ )
246
+
247
+
194
248
  class _AugmentationCollate:
195
249
  """Collate that applies a transform to each batch, with optional expansion.
196
250
 
@@ -211,15 +265,44 @@ class _AugmentationCollate:
211
265
  ``(1 + n_augmentation)`` times the original size. When the transform
212
266
  mixes targets, as :class:`braindecode.augmentation.Mixup` does, ``y``
213
267
  stays the ``(y_a, y_b, lam)`` triple and the clean originals get a
214
- mixing coefficient of one.
268
+ mixing coefficient of one. In this mode a transform that changes the
269
+ shape of ``X`` or ``y`` is rejected with a clear
270
+ :class:`ValueError`, as its copies cannot be concatenated with the
271
+ clean originals.
215
272
  """
216
273
 
217
274
  def __init__(self, transform, device=None, n_augmentation=0):
218
275
  self.transform = transform
219
276
  self.device = device
220
277
  self.n_augmentation = n_augmentation
278
+ self.worker_seeds = None
279
+ self._worker_rngs_seeded = False
280
+
281
+ def draw_worker_seeds(self):
282
+ """Draw, from each generator of the transform, a seed for the workers.
283
+
284
+ Called in the main process when a multi-process iteration starts. A
285
+ worker receives a copy of the transform whose generators are in the
286
+ state of the main process; without a new seed, every worker, and every
287
+ epoch, would draw the same augmentations.
288
+ """
289
+ self.worker_seeds = [
290
+ int(rng.randint(2**31)) for rng in _transform_rngs(self.transform)
291
+ ]
292
+
293
+ def _seed_worker_rngs(self):
294
+ if self._worker_rngs_seeded or self.worker_seeds is None:
295
+ return
296
+ worker_info = get_worker_info()
297
+ if worker_info is None:
298
+ return
299
+ self._worker_rngs_seeded = True
300
+ rngs = _transform_rngs(self.transform)
301
+ for rng, seed in zip(rngs, self.worker_seeds):
302
+ rng.seed(np.random.SeedSequence([seed, worker_info.id]).generate_state(1))
221
303
 
222
304
  def __call__(self, batch):
305
+ self._seed_worker_rngs()
223
306
  collated = default_collate(batch)
224
307
  X, y = collated[:2]
225
308
  if self.device is not None:
@@ -239,13 +322,16 @@ class _AugmentationCollate:
239
322
  for aug_y in ys
240
323
  if isinstance(aug_y, (tuple, list)) and len(aug_y) == 3
241
324
  ]
325
+ _assert_stackable(xs, "X")
242
326
  if mixed_ys:
243
327
  # a target-mixing transform such as Mixup returns (y_a, y_b, lam),
244
328
  # so the parts are concatenated one by one and the untouched copies
245
329
  # are given a mixing coefficient of one
246
330
  lam_dtype = mixed_ys[0][2].dtype
247
331
  ys = [_as_mixed_target(aug_y, lam_dtype) for aug_y in ys]
332
+ _assert_stackable(ys, "y")
248
333
  return torch.cat(xs), tuple(torch.cat(part) for part in zip(*ys))
334
+ _assert_stackable(ys, "y")
249
335
  return torch.cat(xs), torch.cat(ys)
250
336
 
251
337
 
@@ -268,7 +354,10 @@ class AugmentedDataLoader(DataLoader):
268
354
  the clean originals are kept and ``n_augmentation`` independently
269
355
  transformed copies are appended. This expresses augmentations defined as
270
356
  a fixed set-expansion (e.g. the EEG-Inception MI 6x training set,
271
- ``n_augmentation=5``). In this mode batches are returned as ``(X, y)``.
357
+ ``n_augmentation=5``). In this mode batches are returned as ``(X, y)``
358
+ and transforms that change the shape of ``X`` or ``y`` are rejected
359
+ with a :class:`ValueError`, as their copies cannot be concatenated
360
+ with the clean originals.
272
361
  **kwargs : dict, optional
273
362
  keyword arguments to pass to standard DataLoader class.
274
363
  """
@@ -301,3 +390,10 @@ class AugmentedDataLoader(DataLoader):
301
390
  transform, device=device, n_augmentation=n_augmentation
302
391
  )
303
392
  super().__init__(dataset, collate_fn=self.collated_tr, **kwargs)
393
+
394
+ def __iter__(self):
395
+ # the workers get a copy of the transform; give them new seeds so that
396
+ # seeded transforms differ across workers and epochs
397
+ if self.num_workers > 0:
398
+ self.collated_tr.draw_worker_seeds()
399
+ return super().__iter__()
@@ -1173,7 +1173,8 @@ class LabelSmoothing(Transform):
1173
1173
  fixed-expansion mode (``n_augmentation > 0``) of
1174
1174
  :class:`braindecode.augmentation.AugmentedDataLoader` is not supported:
1175
1175
  it cannot concatenate the unsmoothed original targets with the reshaped
1176
- smoothed copies.
1176
+ smoothed copies and rejects the combination with a :class:`ValueError`
1177
+ pointing to ``n_augmentation = 0``.
1177
1178
 
1178
1179
  Parameters
1179
1180
  ----------
@@ -33,7 +33,12 @@ class EEGClassifier(_EEGNeuralNet, NeuralNetClassifier):
33
33
 
34
34
  cropped: bool (default=False)
35
35
  Defines whether torch model passed to this class is cropped or not.
36
- Currently used for callbacks definition.
36
+ Currently used for callbacks definition. For input without window
37
+ indices (such as numpy arrays), scoring callbacks treat each example
38
+ as a separate trial and average its temporal predictions. To aggregate
39
+ multiple windows of the same trial, pass a window dataset retaining
40
+ its window indices. Cross-validation splits must keep windows from
41
+ the same trial together.
37
42
 
38
43
  callbacks: None or list of strings or list of Callback instances (default=None)
39
44
  More callbacks, in addition to those returned by
@@ -195,7 +200,7 @@ class EEGClassifier(_EEGNeuralNet, NeuralNetClassifier):
195
200
  """
196
201
  return self.predict_proba(X).argmax(1)
197
202
 
198
- def predict_trials(self, X, return_targets=True):
203
+ def predict_trials(self, X, return_targets=True, *, loader_kwargs=None):
199
204
  """Create trialwise predictions from a cropped dataset.
200
205
 
201
206
  Parameters
@@ -204,6 +209,9 @@ class EEGClassifier(_EEGNeuralNet, NeuralNetClassifier):
204
209
  A braindecode dataset to be predicted.
205
210
  return_targets : bool
206
211
  If True, additionally returns the trial targets.
212
+ loader_kwargs : dict | None
213
+ Cropped mode only: DataLoader overrides, see
214
+ :func:`braindecode.training.iter_trial_predictions`.
207
215
 
208
216
  Returns
209
217
  -------
@@ -220,6 +228,8 @@ class EEGClassifier(_EEGNeuralNet, NeuralNetClassifier):
220
228
  trial lengths differ, a list with one target array per trial.
221
229
  """
222
230
  if not self.cropped:
231
+ if loader_kwargs is not None:
232
+ raise ValueError("loader_kwargs requires cropped=True")
223
233
  warnings.warn(
224
234
  "This method was designed to predict trials in cropped mode. "
225
235
  "Calling it when cropped is False will give the same result as "
@@ -237,6 +247,7 @@ class EEGClassifier(_EEGNeuralNet, NeuralNetClassifier):
237
247
  return_targets=return_targets,
238
248
  batch_size=self.batch_size,
239
249
  num_workers=self.get_iterator(X, training=False).loader.num_workers,
250
+ loader_kwargs=loader_kwargs,
240
251
  )
241
252
 
242
253
  @property
@@ -26,6 +26,7 @@ from itertools import chain
26
26
  from operator import attrgetter
27
27
  from pathlib import Path
28
28
  from typing import Any, Generic, Iterable, no_type_check
29
+ from uuid import uuid4
29
30
 
30
31
  import mne
31
32
  import mne.io
@@ -343,6 +344,9 @@ def _read_fif_window(raw, start, stop, willneed=False):
343
344
  return np.multiply(picked, raw._cals[:, np.newaxis], order="C")
344
345
 
345
346
 
347
+ _COALESCED_MAX_BYTES = 2 * 1024 * 1024
348
+
349
+
346
350
  def _window_info(crop_inds, sfreq):
347
351
  """Extract window size from crop indices.
348
352
 
@@ -479,14 +483,14 @@ def _zarr_to_memmap(zarr_path, group_name):
479
483
  ``SIGBUS`` when workers page-faulted on the unlinked inode.
480
484
 
481
485
  Under ``N`` concurrent writers, every racer materialises the data
482
- into its own per-pid temp file and then tries to publish it; at
486
+ into its own per-call temp file and then tries to publish it; at
483
487
  most one ``os.link`` wins, and the losers silently ``unlink``
484
488
  their temp file in a ``finally`` block. The wasted I/O scales
485
489
  with the number of racers; for the workloads braindecode targets
486
490
  (a handful of concurrent workers per group) it is negligible.
487
491
 
488
492
  Crash recovery: if a writer dies mid-run the only debris is a
489
- ``<group>.<pid>.tmp.npy`` file. Such files are namespaced by pid
493
+ ``<group>.<uuid>.tmp.npy`` file. Such files are unique per call
490
494
  so they can never collide with a live writer, they are never
491
495
  read by the fast path, and they do not prevent subsequent
492
496
  callers from producing a correct result. Caches written by
@@ -504,7 +508,10 @@ def _zarr_to_memmap(zarr_path, group_name):
504
508
 
505
509
  cache_dir.mkdir(parents=True, exist_ok=True)
506
510
  arr = zarr.open(zarr_path, mode="r")[group_name]["data"]
507
- tmp_path = cache_dir / f"{group_name}.{os.getpid()}.tmp.npy"
511
+ # Unique per call, not per pid: threads of one process must not share it.
512
+ # Exclusive creation, not tempfile.mkstemp, which would make it 0600.
513
+ tmp_path = cache_dir / f"{group_name}.{uuid4().hex}.tmp.npy"
514
+ tmp_path.open("xb").close()
508
515
  try:
509
516
  # Write chunk-by-chunk to avoid materialising the whole zarr
510
517
  # array in RAM.
@@ -531,9 +538,9 @@ def _zarr_to_memmap(zarr_path, group_name):
531
538
  # canonical one; ours is discarded in the finally block.
532
539
  pass
533
540
  finally:
534
- # Always remove our private per-pid temp file. It is
535
- # namespaced by pid so this cannot clobber a concurrent
536
- # writer's in-progress tmp file.
541
+ # Always remove our private per-call temp file. Its name is
542
+ # unique per call so this cannot clobber a concurrent writer's
543
+ # in-progress tmp file.
537
544
  try:
538
545
  os.unlink(tmp_path)
539
546
  except FileNotFoundError:
@@ -953,6 +960,10 @@ class EEGWindowsDataset(_ZarrMixin, RecordDataset):
953
960
  X = self.raw._getitem(
954
961
  (slice(None), slice(i_start, i_stop)), return_times=False
955
962
  )
963
+ return self._finish_window(index, crop_inds, X)
964
+
965
+ def _finish_window(self, index, crop_inds, X):
966
+ """Item ``index`` from its raw samples ``X``, copied before the transform."""
956
967
  X = np.array(X, dtype="float32")
957
968
  if self.transform is not None:
958
969
  X = self.transform(X)
@@ -1344,11 +1355,24 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1344
1355
  Optional function to call on targets before returning them.
1345
1356
  lazy : bool, default False
1346
1357
  If True, defer computing cumulative sizes until length or item access.
1358
+ coalesce_reads : bool, default False
1359
+ Opt in to reading a :class:`~torch.utils.data.DataLoader` batch of
1360
+ consecutive, overlapping windows of one lazy (``preload=False``) FIF
1361
+ recording as one union of samples instead of one read per window.
1362
+ Only a batch of consecutive ascending indices into one
1363
+ :class:`~braindecode.datasets.EEGWindowsDataset` (not a subclass)
1364
+ whose union is at most a third of the summed window samples and at
1365
+ most 2 MiB as float64 is read this way; every other batch takes the
1366
+ default path. Outputs, order and random number use equal the
1367
+ default's. It was faster only for heavily overlapping windows and
1368
+ slower for some other layouts (numbers in :ref:`whats_new`), so it is off
1369
+ by default. Containers returned by ``split`` default to ``False``.
1347
1370
  """
1348
1371
 
1349
1372
  plot = _viewer_plot # eegdash-viewer embed; a direct member so the API docs list it
1350
1373
 
1351
1374
  datasets: list[T]
1375
+ coalesce_reads: bool = False # default for pickles made before this option
1352
1376
 
1353
1377
  def __init__(
1354
1378
  self,
@@ -1358,6 +1382,7 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1358
1382
  target_transform: Callable | None = None,
1359
1383
  *,
1360
1384
  lazy: bool = False,
1385
+ coalesce_reads: bool = False,
1361
1386
  ):
1362
1387
  # Adapted from torch.utils.data.ConcatDataset:
1363
1388
  # added lazy init to defer cumulative size computation until access,
@@ -1387,6 +1412,9 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1387
1412
 
1388
1413
  self.target_transform = target_transform
1389
1414
  self._lazy = lazy
1415
+ if type(coalesce_reads) is not bool:
1416
+ raise TypeError("coalesce_reads must be a bool.")
1417
+ self.coalesce_reads = coalesce_reads
1390
1418
 
1391
1419
  @property
1392
1420
  def cumulative_sizes(self) -> list[int]:
@@ -1454,6 +1482,10 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1454
1482
  another: on a network file system a batch read from cold files then
1455
1483
  costs about one round trip, not one per window.
1456
1484
  """
1485
+ if self.coalesce_reads:
1486
+ coalesced = self._get_coalesced_items(indices)
1487
+ if coalesced is not None:
1488
+ return coalesced
1457
1489
  # ponytail: advises the whole batch at once; chunk it if a batch's
1458
1490
  # windows ever outgrow the page cache
1459
1491
  if hasattr(os, "posix_fadvise"):
@@ -1466,6 +1498,52 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1466
1498
  _read_fif_window(ds.raw, start, stop, willneed=True)
1467
1499
  return [self[idx] for idx in indices]
1468
1500
 
1501
+ def _get_coalesced_items(self, indices):
1502
+ """The items cut from one read of their union, or None if not eligible."""
1503
+ n = len(indices)
1504
+ if (
1505
+ type(self) is not BaseConcatDataset # subclasses may override items
1506
+ or n < 2
1507
+ or type(indices[0]) is not int
1508
+ or indices[0] < 0
1509
+ or indices != list(range(indices[0], indices[0] + n))
1510
+ ):
1511
+ return None
1512
+ sizes = self._ensure_cumulative_sizes()
1513
+ k = bisect.bisect_right(sizes, indices[0])
1514
+ if k == len(sizes) or indices[-1] >= sizes[k]:
1515
+ return None # past the end, or across recordings
1516
+ ds = self.datasets[k]
1517
+ if (
1518
+ type(ds) is not EEGWindowsDataset
1519
+ or ds._zarr_data is not None
1520
+ or not isinstance(ds.crop_inds, np.ndarray) # lazy metadata
1521
+ or ds.raw.preload
1522
+ ):
1523
+ return None
1524
+ first = indices[0] - (sizes[k - 1] if k else 0)
1525
+ crops = ds.crop_inds[first : first + n]
1526
+ start, stop = int(crops[:, 1].min()), int(crops[:, 2].max())
1527
+ if (
1528
+ start < 0
1529
+ or stop > ds.raw.n_times
1530
+ or (crops[:, 2] <= crops[:, 1]).any()
1531
+ or 3 * (stop - start) > (crops[:, 2] - crops[:, 1]).sum()
1532
+ or (stop - start) * ds.raw.info["nchan"] * 8 > _COALESCED_MAX_BYTES
1533
+ ):
1534
+ return None
1535
+ union = _read_fif_window(ds.raw, start, stop)
1536
+ if union is None:
1537
+ return None
1538
+ items = []
1539
+ for i, crop in enumerate(crops.tolist(), first):
1540
+ X = union[:, crop[1] - start : crop[2] - start]
1541
+ item = ds._finish_window(i, crop, X)
1542
+ if self.target_transform is not None:
1543
+ item = item[:1] + (self.target_transform(item[1]),) + item[2:]
1544
+ items.append(item)
1545
+ return items
1546
+
1469
1547
  def __getitem__(self, idx: int | list):
1470
1548
  """
1471
1549
  ---
@@ -1583,15 +1661,17 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1583
1661
  df = ds._windows.metadata
1584
1662
  else:
1585
1663
  df = ds.metadata
1664
+ # a dataset built without description has description None
1665
+ description = ds.description if ds.description is not None else {}
1586
1666
  if (
1587
1667
  "i_trial_in_dataset" in df.columns
1588
- and "i_trial_in_dataset" in ds.description
1668
+ and "i_trial_in_dataset" in description
1589
1669
  ):
1590
1670
  raise ValueError(
1591
1671
  "Dataset descriptions cannot contain the reserved window "
1592
1672
  "metadata key 'i_trial_in_dataset'."
1593
1673
  )
1594
- if "i_dataset" in df.columns or "i_dataset" in ds.description:
1674
+ if "i_dataset" in df.columns or "i_dataset" in description:
1595
1675
  raise ValueError(
1596
1676
  "Window metadata and dataset descriptions cannot contain the "
1597
1677
  "key 'i_dataset', which get_metadata() reserves for the "
@@ -1607,7 +1687,8 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1607
1687
  if isinstance(df, pd.DataFrame):
1608
1688
  # copy so the description columns stay out of the dataset
1609
1689
  df = df.copy()
1610
- for k, v in ds.description.items():
1690
+ description = ds.description if ds.description is not None else {}
1691
+ for k, v in description.items():
1611
1692
  df[k] = v
1612
1693
  df["i_dataset"] = i_ds
1613
1694
  all_dfs.append(df)
@@ -224,15 +224,39 @@ class BIDSEpochsDataset(BIDSDataset):
224
224
  )
225
225
 
226
226
  def _set_metadata(self, epochs: mne.BaseEpochs) -> None:
227
- # events = mne.events_from_annotations(epochs
228
227
  n_times = epochs.times.shape[0]
229
- # id_event = {v: k for k, v in epochs.event_id.items()}
230
- annotations = epochs.annotations
231
- if annotations is not None:
232
- target = annotations.description
233
- else:
234
- id_events = {v: k for k, v in epochs.event_id.items()}
235
- target = [id_events[event_id] for event_id in epochs.events[:, -1]]
228
+ id_events = {v: k for k, v in epochs.event_id.items()}
229
+ target = [id_events[event_id] for event_id in epochs.events[:, -1]]
230
+ if epochs.annotations is not None and len(epochs.annotations) > 0:
231
+ # Match triggers on the original recording's sample clock, even if
232
+ # epochs have been cropped past time zero, decimated or resampled.
233
+ onset_samples = np.round(
234
+ epochs.annotations.onset * epochs._raw_sfreq
235
+ ).astype(int)
236
+ at_zero = []
237
+ for sample in epochs.events[:, 0]:
238
+ matches = np.flatnonzero(onset_samples == sample)
239
+ if len(matches) == 0:
240
+ # FIF stores onsets as float32. Only the quantization bins
241
+ # containing this sample's half-sample boundaries can move
242
+ # a valid onset to an adjacent rounded sample on readback.
243
+ boundaries = np.asarray(
244
+ [
245
+ (sample - 0.5) / epochs._raw_sfreq,
246
+ (sample + 0.5) / epochs._raw_sfreq,
247
+ ],
248
+ dtype=np.float32,
249
+ ).astype(float)
250
+ matches = np.flatnonzero(
251
+ np.isin(epochs.annotations.onset, boundaries)
252
+ & (np.abs(onset_samples - sample) == 1)
253
+ )
254
+ at_zero.append(matches)
255
+ if all(len(matches) == 1 for matches in at_zero):
256
+ indices = [matches[0] for matches in at_zero]
257
+ # A boundary annotation cannot label two neighboring events.
258
+ if len(set(indices)) == len(set(epochs.events[:, 0])):
259
+ target = epochs.annotations.description[indices].tolist()
236
260
  metadata_dict = {
237
261
  "i_window_in_trial": np.zeros(len(epochs)),
238
262
  "i_start_in_trial": np.zeros(len(epochs)),
@@ -13,6 +13,7 @@ from glob import glob
13
13
  from pathlib import Path
14
14
 
15
15
  import mne
16
+ import numpy as np
16
17
  import pandas as pd
17
18
  from joblib import Parallel, delayed
18
19
 
@@ -196,7 +197,7 @@ def load_concat_dataset(path, preload, ids_to_load=None, target_name=None, n_job
196
197
  Ids of specific files to load.
197
198
  target_name : str | list | None
198
199
  Load specific description column as target. If not given, take saved
199
- target name.
200
+ target name. Only for datasets of raws.
200
201
  n_jobs : int
201
202
  Number of jobs to be used to read files in parallel.
202
203
 
@@ -245,10 +246,18 @@ def load_concat_dataset(path, preload, ids_to_load=None, target_name=None, n_job
245
246
  UserWarning,
246
247
  )
247
248
  n_jobs = 1
249
+ if target_name is not None and (has_stored_windows or not is_raw):
250
+ raise ValueError(
251
+ "target_name can only be given for datasets of raws; for windowed "
252
+ "datasets, use BaseConcatDataset.set_target after loading."
253
+ )
248
254
  datasets = Parallel(n_jobs)(
249
255
  delayed(_load_parallel)(path, i, preload, is_raw, has_stored_windows)
250
256
  for i in ids_to_load
251
257
  )
258
+ if target_name is not None:
259
+ for ds in datasets:
260
+ ds.target_name = ds._target_name(target_name)
252
261
  return BaseConcatDataset(datasets)
253
262
 
254
263
 
@@ -265,7 +274,11 @@ def _load_parallel(path, i, preload, is_raw, has_stored_windows):
265
274
  signals = _load_signals(fif_file_path, preload, is_raw)
266
275
 
267
276
  description_file_path = sub_dir / "description.json"
268
- description = pd.read_json(description_file_path, typ="series", convert_dates=False)
277
+ # dtype=False: keep strings such as subject "01" as strings
278
+ description = pd.read_json(
279
+ description_file_path, typ="series", convert_dates=False, dtype=False
280
+ )
281
+ description[description.isna()] = np.nan # JSON null was read as NaN
269
282
 
270
283
  # if 'timestamp' in description.index:
271
284
  # timestamp_numeric = pd.to_numeric(description['timestamp'])
@@ -142,10 +142,11 @@ def infer_signal_properties(
142
142
  if isinstance(X, BaseConcatDataset) and all(
143
143
  ds.targets_from == "metadata" for ds in X.datasets
144
144
  ):
145
- y_target = X.get_metadata().target
145
+ # an array: several targets per window give one column each
146
+ y_target = np.asarray(X.get_metadata().target.to_list())
146
147
  signal_kwargs["n_outputs"] = _get_n_outputs(y_target, classes, mode)
147
148
  elif isinstance(X, WindowsDataset) and X.targets_from == "metadata":
148
- y_target = X.windows.metadata.target
149
+ y_target = np.asarray(X.windows.metadata.target.to_list())
149
150
  signal_kwargs["n_outputs"] = _get_n_outputs(y_target, classes, mode)
150
151
  else:
151
152
  log.warning(