braindecode 1.6.1.dev1065__tar.gz → 1.6.1.dev1079__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 (176) hide show
  1. {braindecode-1.6.1.dev1065/braindecode.egg-info → braindecode-1.6.1.dev1079}/PKG-INFO +1 -1
  2. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/augmentation/base.py +67 -19
  3. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/augmentation/functional.py +44 -30
  4. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/__init__.py +2 -0
  5. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/base.py +109 -0
  6. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bids/hub.py +41 -1
  7. braindecode-1.6.1.dev1079/braindecode/datasets/collate.py +105 -0
  8. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/eegneuralnet.py +8 -1
  9. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/functional/__init__.py +2 -0
  10. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/functional/functions.py +39 -0
  11. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/__init__.py +4 -0
  12. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/biot.py +3 -10
  13. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/brainmodule.py +185 -54
  14. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegitnet.py +33 -12
  15. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegsym.py +9 -0
  16. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/medformer.py +4 -22
  17. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/msvtnet.py +7 -3
  18. braindecode-1.6.1.dev1079/braindecode/models/steegformer.py +654 -0
  19. braindecode-1.6.1.dev1079/braindecode/models/summary.csv +61 -0
  20. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/util.py +43 -0
  21. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/__init__.py +6 -1
  22. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/activation.py +34 -0
  23. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/blocks.py +100 -25
  24. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/filter.py +8 -5
  25. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/layers.py +126 -0
  26. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/preprocessing/mne_preprocess.py +129 -12
  27. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/training/scoring.py +8 -1
  28. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/util.py +36 -1
  29. braindecode-1.6.1.dev1079/braindecode/version.py +1 -0
  30. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079/braindecode.egg-info}/PKG-INFO +1 -1
  31. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode.egg-info/SOURCES.txt +2 -0
  32. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/api.rst +7 -0
  33. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/conf.py +14 -0
  34. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/models_table.rst +8 -3
  35. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/whats_new.rst +90 -0
  36. braindecode-1.6.1.dev1065/braindecode/models/summary.csv +0 -60
  37. braindecode-1.6.1.dev1065/braindecode/version.py +0 -1
  38. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/LICENSE.txt +0 -0
  39. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/MANIFEST.in +0 -0
  40. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/NOTICE.txt +0 -0
  41. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/README.rst +0 -0
  42. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/__init__.py +0 -0
  43. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/augmentation/__init__.py +0 -0
  44. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/augmentation/transforms.py +0 -0
  45. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/classifier.py +0 -0
  46. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bbci.py +0 -0
  47. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bcicomp.py +0 -0
  48. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bids/__init__.py +0 -0
  49. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bids/datasets.py +0 -0
  50. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bids/format.py +0 -0
  51. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bids/hub_format.py +0 -0
  52. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bids/hub_io.py +0 -0
  53. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bids/hub_validation.py +0 -0
  54. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/bids/iterable.py +0 -0
  55. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/chb_mit.py +0 -0
  56. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/mne.py +0 -0
  57. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/moabb.py +0 -0
  58. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/nmt.py +0 -0
  59. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/registry.py +0 -0
  60. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/siena.py +0 -0
  61. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  62. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/sleep_physionet.py +0 -0
  63. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/tuh.py +0 -0
  64. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/utils.py +0 -0
  65. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datasets/xy.py +0 -0
  66. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datautil/__init__.py +0 -0
  67. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datautil/channel_utils.py +0 -0
  68. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datautil/hub_formats.py +0 -0
  69. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datautil/serialization.py +0 -0
  70. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/datautil/util.py +0 -0
  71. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/functional/initialization.py +0 -0
  72. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/atcnet.py +0 -0
  73. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/attentionbasenet.py +0 -0
  74. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/attn_sleep.py +0 -0
  75. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/base.py +0 -0
  76. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/bendr.py +0 -0
  77. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/cbramod.py +0 -0
  78. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/codebrain.py +0 -0
  79. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/config.py +0 -0
  80. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/contrawr.py +0 -0
  81. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/ctnet.py +0 -0
  82. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/deep4.py +0 -0
  83. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/deepsleepnet.py +0 -0
  84. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/dgcnn.py +0 -0
  85. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegconformer.py +0 -0
  86. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegdino.py +0 -0
  87. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eeginception_erp.py +0 -0
  88. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eeginception_mi.py +0 -0
  89. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegminer.py +0 -0
  90. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegnet.py +0 -0
  91. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegnex.py +0 -0
  92. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegpt.py +0 -0
  93. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegsimpleconv.py +0 -0
  94. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/eegtcnet.py +0 -0
  95. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/emg2qwerty.py +0 -0
  96. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/fbcnet.py +0 -0
  97. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/fblightconvnet.py +0 -0
  98. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/fbmsnet.py +0 -0
  99. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/hybrid.py +0 -0
  100. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/ifnet.py +0 -0
  101. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/interpolated.py +0 -0
  102. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/labram.py +0 -0
  103. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/luna.py +0 -0
  104. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/meta_neuromotor.py +0 -0
  105. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/patchedtransformer.py +0 -0
  106. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/reve.py +0 -0
  107. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/sccnet.py +0 -0
  108. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/shallow_fbcsp.py +0 -0
  109. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/signal_jepa.py +0 -0
  110. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/sinc_shallow.py +0 -0
  111. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
  112. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
  113. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/sparcnet.py +0 -0
  114. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/sstdpn.py +0 -0
  115. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/syncnet.py +0 -0
  116. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/tcformer.py +0 -0
  117. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/tcn.py +0 -0
  118. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/tidnet.py +0 -0
  119. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/tsinception.py +0 -0
  120. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/models/usleep.py +0 -0
  121. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/attention.py +0 -0
  122. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/convolution.py +0 -0
  123. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/interpolation.py +0 -0
  124. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/linear.py +0 -0
  125. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/parametrization.py +0 -0
  126. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/stats.py +0 -0
  127. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/util.py +0 -0
  128. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/modules/wrapper.py +0 -0
  129. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/preprocessing/__init__.py +0 -0
  130. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
  131. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/preprocessing/preprocess.py +0 -0
  132. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/preprocessing/util.py +0 -0
  133. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/preprocessing/windowers.py +0 -0
  134. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/regressor.py +0 -0
  135. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/samplers/__init__.py +0 -0
  136. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/samplers/base.py +0 -0
  137. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/samplers/ssl.py +0 -0
  138. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/training/__init__.py +0 -0
  139. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/training/callbacks.py +0 -0
  140. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/training/losses.py +0 -0
  141. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/visualization/__init__.py +0 -0
  142. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/visualization/attribution.py +0 -0
  143. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/visualization/confusion_matrices.py +0 -0
  144. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/visualization/frequency.py +0 -0
  145. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/visualization/metrics.py +0 -0
  146. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/visualization/sanity.py +0 -0
  147. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode/visualization/topology.py +0 -0
  148. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode.egg-info/dependency_links.txt +0 -0
  149. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode.egg-info/requires.txt +0 -0
  150. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/braindecode.egg-info/top_level.txt +0 -0
  151. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/Makefile +0 -0
  152. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/_templates/autosummary/class.rst +0 -0
  153. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  154. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/_templates/autosummary/function.rst +0 -0
  155. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  156. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/cite.rst +0 -0
  157. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/help.rst +0 -0
  158. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/index.rst +0 -0
  159. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/install/install.rst +0 -0
  160. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/install/install_pip.rst +0 -0
  161. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/install/install_source.rst +0 -0
  162. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/categorization/attention.rst +0 -0
  163. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/categorization/channel.rst +0 -0
  164. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/categorization/convolution.rst +0 -0
  165. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/categorization/filterbank.rst +0 -0
  166. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/categorization/gnn.rst +0 -0
  167. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/categorization/interpretable.rst +0 -0
  168. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/categorization/lbm.rst +0 -0
  169. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/categorization/recurrent.rst +0 -0
  170. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/categorization/spd.rst +0 -0
  171. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/models.rst +0 -0
  172. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/models_categorization.rst +0 -0
  173. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/models/models_visualization.rst +0 -0
  174. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/docs/sg_execution_times.rst +0 -0
  175. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/pyproject.toml +0 -0
  176. {braindecode-1.6.1.dev1065 → braindecode-1.6.1.dev1079}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.6.1.dev1065
3
+ Version: 1.6.1.dev1079
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>
@@ -105,7 +105,15 @@ class Transform(torch.nn.Module):
105
105
  mask = self._get_mask(out_X.shape[0], out_X.device)
106
106
  num_valid = mask.sum().long()
107
107
 
108
- if num_valid > 0:
108
+ if num_valid == out_X.shape[0]:
109
+ # Fast path: every sample is augmented (e.g. ``probability=1.0``,
110
+ # the common case). Operate on the whole batch and skip the boolean
111
+ # gather/scatter (``out_X[mask]`` then ``out_X[mask] = ...``), which
112
+ # would otherwise copy the batch twice.
113
+ out_X, out_y = self.operation(
114
+ out_X, out_y, **self.get_augmentation_params(out_X, out_y)
115
+ )
116
+ elif num_valid > 0:
109
117
  # Uses the mask to define the output
110
118
  out_X[mask, ...], tr_y = self.operation(
111
119
  out_X[mask, ...],
@@ -168,23 +176,47 @@ class Compose(Transform):
168
176
  return X, y
169
177
 
170
178
 
171
- def _make_collateable(transform, device=None):
172
- """Wraps a transform to make it collateable.
173
-
174
- with device control.
175
- """
179
+ class _AugmentationCollate:
180
+ """Collate that applies a transform to each batch, with optional expansion.
176
181
 
177
- def _collate_fn(batch):
178
- collated_batch = default_collate(batch)
179
- X, y = collated_batch[:2]
182
+ A picklable callable (so it works with ``num_workers > 0``, unlike a
183
+ closure) that collates the batch once and then either augments it in place
184
+ or grows it.
180
185
 
181
- if device is not None:
182
- X = X.to(device)
183
- y = y.to(device)
184
-
185
- return (*transform(X, y), *collated_batch[2:])
186
+ Parameters
187
+ ----------
188
+ transform : Transform
189
+ Transform applied to the collated ``(X, y)``.
190
+ device : str | torch.device | None, optional
191
+ Device the batch is moved to before transforming. Defaults to None.
192
+ n_augmentation : int, optional
193
+ ``0`` (default) applies the transform in place (batch size unchanged).
194
+ ``> 0`` keeps the clean originals and appends ``n_augmentation``
195
+ independently transformed copies, returning ``(X, y)`` of
196
+ ``(1 + n_augmentation)`` times the original size.
197
+ """
186
198
 
187
- return _collate_fn
199
+ def __init__(self, transform, device=None, n_augmentation=0):
200
+ self.transform = transform
201
+ self.device = device
202
+ self.n_augmentation = n_augmentation
203
+
204
+ def __call__(self, batch):
205
+ collated = default_collate(batch)
206
+ X, y = collated[:2]
207
+ if self.device is not None:
208
+ X = X.to(self.device)
209
+ y = y.to(self.device)
210
+ if self.n_augmentation == 0:
211
+ return (*self.transform(X, y), *collated[2:])
212
+ # Fixed expansion: keep the clean originals, then append
213
+ # ``n_augmentation`` independently transformed copies.
214
+ xs, ys = [X], [y]
215
+ for _ in range(self.n_augmentation):
216
+ aug_X, aug_y = self.transform(X, y)
217
+ xs.append(aug_X)
218
+ ys.append(aug_y)
219
+ return torch.cat(xs), torch.cat(ys)
188
220
 
189
221
 
190
222
  class AugmentedDataLoader(DataLoader):
@@ -198,28 +230,44 @@ class AugmentedDataLoader(DataLoader):
198
230
  Transform or sequence of Transform to be applied to each batch.
199
231
  device : str | torch.device | None, optional
200
232
  Device on which to transform the data. Defaults to None.
233
+ n_augmentation : int, optional
234
+ Number of augmented copies to append to each batch (fixed expansion).
235
+ When ``0`` (default) the transforms are applied in place and the batch
236
+ keeps its size (stochastic-per-epoch augmentation, backwards-compatible).
237
+ When ``> 0`` each batch becomes ``(1 + n_augmentation)`` times larger:
238
+ the clean originals are kept and ``n_augmentation`` independently
239
+ transformed copies are appended. This expresses augmentations defined as
240
+ a fixed set-expansion (e.g. the EEG-Inception MI 6x training set,
241
+ ``n_augmentation=5``). In this mode batches are returned as ``(X, y)``.
201
242
  **kwargs : dict, optional
202
243
  keyword arguments to pass to standard DataLoader class.
203
244
  """
204
245
 
205
- def __init__(self, dataset, transforms=None, device=None, **kwargs):
246
+ def __init__(
247
+ self, dataset, transforms=None, device=None, n_augmentation=0, **kwargs
248
+ ):
206
249
  if "collate_fn" in kwargs:
207
250
  raise ValueError(
208
251
  "collate_fn cannot be used in this context because it is used "
209
252
  "to pass transform"
210
253
  )
254
+ if n_augmentation < 0:
255
+ raise ValueError("n_augmentation must be a non-negative integer.")
211
256
  if transforms is None or (
212
257
  isinstance(transforms, list) and len(transforms) == 0
213
258
  ):
214
- self.collated_tr = _make_collateable(IdentityTransform(), device=device)
259
+ transform = IdentityTransform()
215
260
  elif isinstance(transforms, (Transform, nn.Module)):
216
- self.collated_tr = _make_collateable(transforms, device=device)
261
+ transform = transforms
217
262
  elif isinstance(transforms, list):
218
- self.collated_tr = _make_collateable(Compose(transforms), device=device)
263
+ transform = Compose(transforms)
219
264
  else:
220
265
  raise TypeError(
221
266
  "transforms can be either a Transform object "
222
267
  "or a list of Transform objects."
223
268
  )
224
269
 
270
+ self.collated_tr = _AugmentationCollate(
271
+ transform, device=device, n_augmentation=n_augmentation
272
+ )
225
273
  super().__init__(dataset, collate_fn=self.collated_tr, **kwargs)
@@ -18,7 +18,7 @@ from mne.filter import notch_filter
18
18
  from scipy.interpolate import Rbf
19
19
  from sklearn.utils import check_random_state
20
20
  from torch.fft import fft, ifft
21
- from torch.nn.functional import one_hot, pad
21
+ from torch.nn.functional import pad
22
22
 
23
23
 
24
24
  def identity(X: torch.Tensor, y: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
@@ -253,25 +253,34 @@ def channels_dropout(
253
253
  return X * mask.unsqueeze(-1), y
254
254
 
255
255
 
256
- def _make_permutation_matrix(
256
+ def _make_channel_permutation(
257
257
  X: torch.Tensor, mask: torch.Tensor, random_state: int | np.random.Generator | None
258
258
  ) -> torch.Tensor:
259
+ """Per-sample channel permutation that shuffles only the masked channels.
260
+
261
+ Returns a ``(batch, n_channels)`` index permutation: output channel ``i`` is
262
+ taken from input channel ``perm[b, i]``. Masked channels are permuted among
263
+ their own positions; unmasked channels keep their place.
264
+
265
+ The per-sample ``rng.permutation`` is kept (rather than a vectorized random
266
+ draw) so the output is bit-for-bit identical to the previous one-hot/matmul
267
+ implementation — vectorizing the draw would change seeded results. The win
268
+ is downstream: the caller gathers with this index instead of building
269
+ ``(batch, n_channels, n_channels)`` one-hot matrices and a matmul.
270
+ """
259
271
  rng = check_random_state(random_state)
260
272
  batch_size, n_channels, _ = X.shape
261
- hard_mask = mask.round()
262
- batch_permutations = torch.empty(
263
- batch_size, n_channels, n_channels, device=X.device
264
- )
265
- for b, mask in enumerate(hard_mask):
266
- channels_to_shuffle = torch.arange(n_channels, device=X.device)
267
- channels_to_shuffle = channels_to_shuffle[mask.bool()]
268
- reordered_channels = torch.tensor(
269
- rng.permutation(channels_to_shuffle.cpu()), device=X.device
273
+ hard_mask = mask.round().bool()
274
+ channels = torch.arange(n_channels, device=X.device)
275
+ perm = channels.repeat(batch_size, 1)
276
+ for b in range(batch_size):
277
+ channels_to_shuffle = channels[hard_mask[b]]
278
+ perm[b, channels_to_shuffle] = torch.tensor(
279
+ rng.permutation(channels_to_shuffle.cpu()),
280
+ device=X.device,
281
+ dtype=perm.dtype,
270
282
  )
271
- channels_permutation = torch.arange(n_channels, device=X.device)
272
- channels_permutation[channels_to_shuffle] = reordered_channels
273
- batch_permutations[b, ...] = one_hot(channels_permutation)
274
- return batch_permutations
283
+ return perm
275
284
 
276
285
 
277
286
  def channels_shuffle(
@@ -314,8 +323,11 @@ def channels_shuffle(
314
323
  if p_shuffle == 0:
315
324
  return X, y
316
325
  mask = _pick_channels_randomly(X, 1 - p_shuffle, random_state)
317
- batch_permutations = _make_permutation_matrix(X, mask, random_state)
318
- return torch.matmul(batch_permutations, X), y
326
+ perm = _make_channel_permutation(X, mask, random_state)
327
+ # Gather channels (output[b, i] = X[b, perm[b, i]]) instead of building
328
+ # one-hot permutation matrices and a (batch, C, C) @ (batch, C, T) matmul.
329
+ perm = perm.unsqueeze(-1).expand(-1, -1, X.shape[-1])
330
+ return torch.gather(X, 1, perm), y
319
331
 
320
332
 
321
333
  def gaussian_noise(
@@ -1188,19 +1200,21 @@ def mask_encoding(
1188
1200
  32 (2024): 875-886.
1189
1201
  """
1190
1202
 
1191
- batch_indices = torch.arange(X.shape[0]).repeat_interleave(n_segments)
1192
- start_indices = time_start.flatten()
1193
- mask_indices = start_indices[:, None] + torch.arange(segment_length)
1194
-
1195
- # Create a boolean mask with the same shape as X
1196
- mask = torch.zeros_like(X, dtype=torch.bool)
1197
- for batch_index, grouped_mask_indices in zip(batch_indices, mask_indices):
1198
- mask[batch_index, :, grouped_mask_indices] = True
1199
-
1200
- # Apply the mask to set the values to 0
1201
- X[mask] = 0
1202
-
1203
- return X, y # Return the masked tensor and labels
1203
+ # All channels are zeroed at the same time positions, so build a single
1204
+ # (batch, n_times) time-mask and broadcast it over channels, vectorized
1205
+ # (no Python loop over batch * n_segments).
1206
+ device = X.device
1207
+ batch_indices = torch.arange(X.shape[0], device=device).repeat_interleave(
1208
+ n_segments * segment_length
1209
+ )
1210
+ seg_indices = (
1211
+ time_start.flatten().to(device)[:, None]
1212
+ + torch.arange(segment_length, device=device)
1213
+ ).flatten()
1214
+ time_mask = torch.zeros(X.shape[0], X.shape[-1], dtype=torch.bool, device=device)
1215
+ time_mask[batch_indices, seg_indices] = True
1216
+
1217
+ return X.masked_fill(time_mask.unsqueeze(1), 0), y
1204
1218
 
1205
1219
 
1206
1220
  def channels_rereference(
@@ -10,6 +10,7 @@ from .base import (
10
10
  from .bcicomp import BCICompetitionIVDataset4
11
11
  from .bids import BIDSDataset, BIDSEpochsDataset
12
12
  from .chb_mit import CHBMIT
13
+ from .collate import pad_channels_collate
13
14
  from .mne import create_from_mne_epochs, create_from_mne_raw
14
15
  from .moabb import BNCI2014_001, HGD, MOABBDataset
15
16
  from .nmt import NMT
@@ -42,4 +43,5 @@ __all__ = [
42
43
  "SleepPhysionetChallenge2018",
43
44
  "create_from_X_y",
44
45
  "BCICompetitionIVDataset4",
46
+ "pad_channels_collate",
45
47
  ]
@@ -192,6 +192,31 @@ def _channel_info(mne_obj):
192
192
  return n_ch, type_str, sfreq
193
193
 
194
194
 
195
+ def _compute_ch_pos(info, targets_from):
196
+ """Return ``(n_ch, 3)`` float32 electrode positions aligned to ``X``'s rows.
197
+
198
+ Positions are the MNE head-frame coordinates ``info["chs"][i]["loc"][:3]``
199
+ (meters) -- the same representation braindecode models already consume (see
200
+ e.g. ``signal_jepa``/``REVE``). When ``targets_from != "metadata"`` the
201
+ window's misc channels are dropped from ``X`` in ``__getitem__``, so the
202
+ matching misc rows are dropped here too. Emits a one-time ``UserWarning`` if
203
+ no channel carries a real position (no montage set).
204
+ """
205
+ pos = np.asarray([ch["loc"][:3] for ch in info["chs"]], dtype="float32")
206
+ if targets_from != "metadata":
207
+ misc_mask = np.asarray(info.get_channel_types()) == "misc"
208
+ pos = pos[~misc_mask]
209
+ has_pos = np.isfinite(pos).all(axis=1) & np.any(pos != 0, axis=1)
210
+ if pos.size and not has_pos.any():
211
+ warnings.warn(
212
+ "Returning channel positions but no montage is set: all positions "
213
+ "are zero/non-finite. Call ``raw.set_montage(...)`` (or set a "
214
+ "montage during preprocessing) before enabling ``return_ch_pos``.",
215
+ UserWarning,
216
+ )
217
+ return pos
218
+
219
+
195
220
  def _window_info(crop_inds, sfreq):
196
221
  """Extract window size from crop indices.
197
222
 
@@ -448,6 +473,10 @@ class RecordDataset(Dataset[tuple[np.ndarray, int | str, tuple[int, int, int]]])
448
473
  ):
449
474
  self._description = _create_description(description)
450
475
  self.transform = transform
476
+ # Opt-in: when True, ``__getitem__`` appends channel positions (n_ch, 3).
477
+ # Default off keeps the public ``(X, y, crop_inds)`` contract intact.
478
+ self.return_ch_pos = False
479
+ self._ch_pos = None
451
480
 
452
481
  @abstractmethod
453
482
  def __len__(self) -> int:
@@ -750,6 +779,20 @@ class EEGWindowsDataset(_ZarrMixin, RecordDataset):
750
779
  @raw.setter
751
780
  def raw(self, value):
752
781
  self._raw = value
782
+ self._ch_pos = None # invalidate cached positions
783
+
784
+ @property
785
+ def ch_pos(self):
786
+ """Electrode positions ``(n_ch, 3)`` (x, y, z) aligned to ``X``'s channels.
787
+
788
+ Cached MNE head-frame coordinates in meters. Rows match the channels
789
+ returned by :meth:`__getitem__` (misc target channels excluded when
790
+ ``targets_from="channels"``). All-zero/NaN when no montage is set.
791
+ """
792
+ if self._ch_pos is None:
793
+ info = self._raw.info if self._raw is not None else self._make_mne_info()
794
+ self._ch_pos = _compute_ch_pos(info, self.targets_from)
795
+ return self._ch_pos
753
796
 
754
797
  def __getitem__(self, index: int):
755
798
  """Get a window and its target.
@@ -793,6 +836,8 @@ class EEGWindowsDataset(_ZarrMixin, RecordDataset):
793
836
  y = X[misc_mask, :]
794
837
  y = y.copy()
795
838
  X = X[~misc_mask, :]
839
+ if self.return_ch_pos:
840
+ return X, y, crop_inds, self.ch_pos
796
841
  return X, y, crop_inds
797
842
 
798
843
  def __len__(self):
@@ -1016,6 +1061,24 @@ class WindowsDataset(_ZarrMixin, RecordDataset):
1016
1061
  @windows.setter
1017
1062
  def windows(self, value):
1018
1063
  self._windows = value
1064
+ self._ch_pos = None # invalidate cached positions
1065
+
1066
+ @property
1067
+ def ch_pos(self):
1068
+ """Electrode positions ``(n_ch, 3)`` (x, y, z) aligned to ``X``'s channels.
1069
+
1070
+ Cached MNE head-frame coordinates in meters. Rows match the channels
1071
+ returned by :meth:`__getitem__` (misc target channels excluded when
1072
+ ``targets_from="channels"``). All-zero/NaN when no montage is set.
1073
+ """
1074
+ if self._ch_pos is None:
1075
+ info = (
1076
+ self._windows.info
1077
+ if self._windows is not None
1078
+ else self._make_mne_info()
1079
+ )
1080
+ self._ch_pos = _compute_ch_pos(info, self.targets_from)
1081
+ return self._ch_pos
1019
1082
 
1020
1083
  @staticmethod
1021
1084
  def _can_use_fast_get_epoch_from_raw(epochs: mne.BaseEpochs) -> bool:
@@ -1068,6 +1131,8 @@ class WindowsDataset(_ZarrMixin, RecordDataset):
1068
1131
  # necessary to cast as list to get list of three tensors from batch,
1069
1132
  # otherwise get single 2d-tensor...
1070
1133
  crop_inds = self.crop_inds[index].tolist()
1134
+ if self.return_ch_pos:
1135
+ return X, y, crop_inds, self.ch_pos
1071
1136
  return X, y, crop_inds
1072
1137
 
1073
1138
  def __len__(self) -> int:
@@ -1458,6 +1523,50 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1458
1523
  )
1459
1524
  return self
1460
1525
 
1526
+ def set_return_ch_pos(self, value: bool = True) -> "BaseConcatDataset":
1527
+ """Toggle returning electrode positions from every subdataset.
1528
+
1529
+ When enabled, each windowed subdataset's ``__getitem__`` appends a
1530
+ ``(n_ch, 3)`` array of electrode positions (x, y, z), so a batch yields
1531
+ ``(X, y, crop_inds, ch_pos)``. Heterogeneous (variable-channel)
1532
+ collections additionally need
1533
+ :func:`braindecode.datasets.pad_channels_collate` as the DataLoader
1534
+ ``collate_fn``. Default-off: leaves the ``(X, y, crop_inds)`` contract
1535
+ untouched.
1536
+
1537
+ Parameters
1538
+ ----------
1539
+ value : bool
1540
+ Whether to return channel positions (default ``True``).
1541
+
1542
+ Returns
1543
+ -------
1544
+ self : BaseConcatDataset
1545
+ """
1546
+ for ds in self.datasets:
1547
+ ds.return_ch_pos = value
1548
+ return self
1549
+
1550
+ @property
1551
+ def ch_pos(self):
1552
+ """Electrode positions ``(n_ch, 3)`` from the first recording.
1553
+
1554
+ Convenience accessor mirroring how the repr reports channel info "from
1555
+ first recording". For per-sample positions in a heterogeneous
1556
+ collection, enable :meth:`set_return_ch_pos` and read them from the
1557
+ batch instead.
1558
+ """
1559
+ if not self.datasets:
1560
+ raise ValueError("Empty BaseConcatDataset has no channel positions.")
1561
+ first = self.datasets[0]
1562
+ if not hasattr(first, "ch_pos"):
1563
+ raise AttributeError(
1564
+ f"{type(first).__name__} does not expose channel positions; "
1565
+ "ch_pos is available on windowed datasets (EEGWindowsDataset / "
1566
+ "WindowsDataset)."
1567
+ )
1568
+ return first.ch_pos
1569
+
1461
1570
  def _outdated_save(self, path, overwrite=False):
1462
1571
  """This is a copy of the old saving function, that had inconsistent.
1463
1572
 
@@ -327,6 +327,7 @@ class HubDatasetMixin:
327
327
 
328
328
  n_recordings = len(self.datasets)
329
329
  first_ds = self.datasets[0]
330
+ descriptions = [ds.description for ds in self.datasets]
330
331
 
331
332
  # Get dataset-specific info based on type using registry
332
333
  dataset_type = get_dataset_type(first_ds)
@@ -358,6 +359,9 @@ class HubDatasetMixin:
358
359
  raise TypeError(f"Unsupported dataset type: {dataset_type}")
359
360
 
360
361
  # Create README content and save
362
+ license_value, license_needs_update = _infer_license_from_descriptions(
363
+ descriptions
364
+ )
361
365
  readme_content = _generate_readme_content(
362
366
  format_info=format_info,
363
367
  n_recordings=n_recordings,
@@ -366,6 +370,8 @@ class HubDatasetMixin:
366
370
  data_type=data_type,
367
371
  n_windows=n_windows,
368
372
  total_duration=total_duration,
373
+ license=license_value,
374
+ license_needs_update=license_needs_update,
369
375
  )
370
376
 
371
377
  # Save README
@@ -967,6 +973,8 @@ def _generate_readme_content(
967
973
  n_windows: int,
968
974
  total_duration: float | None = None,
969
975
  format: str = "zarr",
976
+ license: str = "please-specify",
977
+ license_needs_update: bool = True,
970
978
  ):
971
979
  """Generate README.md content for a dataset uploaded to the Hub.
972
980
 
@@ -988,6 +996,10 @@ def _generate_readme_content(
988
996
  Total duration in seconds across all recordings.
989
997
  format : str
990
998
  Storage format (default: "zarr").
999
+ license : str
1000
+ Dataset license string to write in YAML front matter.
1001
+ license_needs_update : bool
1002
+ Whether to append a reminder comment next to the license field.
991
1003
 
992
1004
  Returns
993
1005
  -------
@@ -1003,6 +1015,10 @@ def _generate_readme_content(
1003
1015
  str(timedelta(seconds=int(total_duration))) if total_duration else "N/A"
1004
1016
  )
1005
1017
 
1018
+ license_line = f"license: {license}"
1019
+ if license_needs_update:
1020
+ license_line += " # Please update this field to reflect your dataset's license"
1021
+
1006
1022
  return f"""---
1007
1023
  tags:
1008
1024
  - braindecode
@@ -1010,7 +1026,7 @@ tags:
1010
1026
  - neuroscience
1011
1027
  - brain-computer-interface
1012
1028
  - deep-learning
1013
- license: unknown
1029
+ {license_line}
1014
1030
  ---
1015
1031
 
1016
1032
  # EEG Dataset
@@ -1106,3 +1122,27 @@ if hasattr(dataset.datasets[0], "bids_channels"):
1106
1122
 
1107
1123
  *Created with [braindecode](https://braindecode.org)*
1108
1124
  """
1125
+
1126
+
1127
+ def _infer_license_from_descriptions(
1128
+ descriptions: list[pd.Series | None],
1129
+ ) -> tuple[str, bool]:
1130
+ """Infer a single dataset license from descriptions.
1131
+
1132
+ Returns inferred license and whether a manual update reminder is needed.
1133
+ """
1134
+ licenses = set()
1135
+ for description in descriptions:
1136
+ if description is None:
1137
+ continue
1138
+ raw_license = description.get("license", None)
1139
+ if raw_license is None:
1140
+ continue
1141
+ normalized = str(raw_license).strip()
1142
+ if normalized and normalized.lower() != "nan":
1143
+ licenses.add(normalized)
1144
+
1145
+ if len(licenses) == 1:
1146
+ return next(iter(licenses)), False
1147
+
1148
+ return "please-specify", True
@@ -0,0 +1,105 @@
1
+ """Collate functions for braindecode datasets.
2
+
3
+ :func:`pad_channels_collate` makes a :class:`~braindecode.datasets.BaseConcatDataset`
4
+ that mixes recordings with *different channel sets* (heterogeneous montages)
5
+ batchable: it pads every sample's channels to the batch maximum and emits a
6
+ boolean channel mask. Pair it with ``set_return_ch_pos(True)`` so each sample
7
+ carries its electrode positions -- channel identity is then encoded by the
8
+ positions, not by row index, which is exactly what position-aware models
9
+ (e.g. ``REVE``) consume.
10
+ """
11
+
12
+ # Authors: The braindecode developers
13
+ #
14
+ # License: BSD (3-clause)
15
+
16
+ from __future__ import annotations
17
+
18
+ import numpy as np
19
+ import torch
20
+ from torch.utils.data._utils.collate import default_collate
21
+
22
+ __all__ = ["pad_channels_collate"]
23
+
24
+
25
+ def pad_channels_collate(batch, pad_value: float = 0.0, pos_pad_value: float = 0.0):
26
+ """Collate variable-channel windows by padding channels to the batch maximum.
27
+
28
+ Each item of ``batch`` is the tuple returned by a windowed dataset's
29
+ ``__getitem__``: either ``(X, y, crop_inds)`` or, when
30
+ ``return_ch_pos=True`` (see
31
+ :meth:`braindecode.datasets.BaseConcatDataset.set_return_ch_pos`),
32
+ ``(X, y, crop_inds, ch_pos)``. Signals with different numbers of channels
33
+ are zero-padded along the channel axis to ``max_ch`` (the largest channel
34
+ count in the batch) so they can be stacked into a single tensor, and a
35
+ boolean ``ch_mask`` marks the real (non-padded) channels.
36
+
37
+ The time dimension is assumed uniform (fixed-length windows); a
38
+ :class:`ValueError` is raised otherwise.
39
+
40
+ This makes a heterogeneous collection **batchable**. *Consuming* the result
41
+ (so a model ignores padded channels) requires a position/channel-aware
42
+ model; fixed-geometry models (e.g. ``ShallowFBCSPNet``) structurally need a
43
+ constant channel count and cannot use variable batches.
44
+
45
+ Parameters
46
+ ----------
47
+ batch : list of tuple
48
+ Samples from a windowed dataset (see above).
49
+ pad_value : float
50
+ Value used to pad the signal ``X`` channel rows (default ``0.0``).
51
+ pos_pad_value : float
52
+ Value used to pad the position ``ch_pos`` rows (default ``0.0``).
53
+
54
+ Returns
55
+ -------
56
+ X : torch.Tensor
57
+ Padded signals, shape ``(batch, max_ch, n_times)``.
58
+ y : torch.Tensor
59
+ Targets, collated with ``default_collate``.
60
+ crop_inds : list
61
+ Crop indices, collated with ``default_collate``.
62
+ ch_pos : torch.Tensor
63
+ Only when the samples include positions. Padded positions, shape
64
+ ``(batch, max_ch, 3)``.
65
+ ch_mask : torch.Tensor
66
+ Boolean mask, shape ``(batch, max_ch)``; ``True`` for real channels,
67
+ ``False`` for padding.
68
+ """
69
+ if len(batch) == 0:
70
+ raise ValueError("pad_channels_collate received an empty batch.")
71
+
72
+ has_pos = len(batch[0]) >= 4
73
+ Xs = [np.asarray(item[0]) for item in batch]
74
+
75
+ n_times = Xs[0].shape[1]
76
+ if any(x.shape[1] != n_times for x in Xs):
77
+ raise ValueError(
78
+ "pad_channels_collate only pads the channel axis; all windows must "
79
+ f"share the same number of time samples, got {sorted({x.shape[1] for x in Xs})}."
80
+ )
81
+
82
+ bsz = len(batch)
83
+ max_ch = max(x.shape[0] for x in Xs)
84
+
85
+ X_out = torch.full((bsz, max_ch, n_times), pad_value, dtype=torch.float32)
86
+ ch_mask = torch.zeros((bsz, max_ch), dtype=torch.bool)
87
+ pos_out = (
88
+ torch.full((bsz, max_ch, 3), pos_pad_value, dtype=torch.float32)
89
+ if has_pos
90
+ else None
91
+ )
92
+ for i, (item, x) in enumerate(zip(batch, Xs)):
93
+ c = x.shape[0]
94
+ X_out[i, :c] = torch.as_tensor(x, dtype=torch.float32)
95
+ ch_mask[i, :c] = True
96
+ if pos_out is not None:
97
+ pos_out[i, :c] = torch.as_tensor(np.asarray(item[3]), dtype=torch.float32)
98
+
99
+ # y and crop_inds carry no channel axis -> standard collation.
100
+ y = default_collate([item[1] for item in batch])
101
+ crop_inds = default_collate([item[2] for item in batch])
102
+
103
+ if pos_out is not None:
104
+ return X_out, y, crop_inds, pos_out, ch_mask
105
+ return X_out, y, crop_inds, ch_mask
@@ -134,7 +134,14 @@ class _EEGNeuralNet(NeuralNet, abc.ABC):
134
134
  i_window_in_trials = []
135
135
  i_window_stops = []
136
136
  window_ys = []
137
- for X, y, i in self.get_iterator(dataset, drop_index=False):
137
+ for batch in self.get_iterator(dataset, drop_index=False):
138
+ if len(batch) > 3:
139
+ raise ValueError(
140
+ "Cropped prediction does not support channel positions; "
141
+ "disable return_ch_pos (set_return_ch_pos(False)) for cropped "
142
+ "evaluation."
143
+ )
144
+ X, y, i = batch[0], batch[1], batch[2]
138
145
  i_window_in_trials.append(i[0].cpu().numpy())
139
146
  i_window_stops.append(i[2].cpu().numpy())
140
147
  with torch.no_grad():
@@ -5,6 +5,7 @@ from .functions import (
5
5
  identity,
6
6
  plv_time,
7
7
  safe_log,
8
+ sinusoidal_positional_encoding,
8
9
  square,
9
10
  )
10
11
  from .initialization import glorot_weight_zero_bias, rescale_parameter
@@ -16,6 +17,7 @@ __all__ = [
16
17
  "identity",
17
18
  "plv_time",
18
19
  "safe_log",
20
+ "sinusoidal_positional_encoding",
19
21
  "square",
20
22
  "glorot_weight_zero_bias",
21
23
  "rescale_parameter",