Braindecode 1.8.1.dev174738705__tar.gz → 1.8.1.dev174812945__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 (208) hide show
  1. {braindecode-1.8.1.dev174738705/braindecode.egg-info → braindecode-1.8.1.dev174812945}/PKG-INFO +1 -1
  2. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/base.py +46 -3
  3. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/functional/_real_dft.py +55 -0
  4. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/functional/functions.py +13 -5
  5. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/barista.py +2 -2
  6. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/codebrain.py +27 -14
  7. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/contrawr.py +23 -1
  8. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/csbrain.py +69 -36
  9. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/diver1.py +51 -59
  10. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eeginception_mi.py +7 -3
  11. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegnex.py +1 -1
  12. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegpt.py +15 -9
  13. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/labram.py +19 -9
  14. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/luna.py +3 -1
  15. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/medformer.py +13 -13
  16. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/meta_neuromotor.py +35 -8
  17. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/neurorvq_tokenizer.py +10 -1
  18. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/patchedtransformer.py +3 -7
  19. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/reve.py +16 -4
  20. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/zuna.py +11 -9
  21. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/attention.py +53 -6
  22. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/dance_modules.py +16 -3
  23. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/layers.py +41 -2
  24. braindecode-1.8.1.dev174812945/braindecode/version.py +1 -0
  25. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945/braindecode.egg-info}/PKG-INFO +1 -1
  26. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/whats_new.rst +32 -0
  27. braindecode-1.8.1.dev174738705/braindecode/version.py +0 -1
  28. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/LICENSE.txt +0 -0
  29. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/MANIFEST.in +0 -0
  30. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/NOTICE.txt +0 -0
  31. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/README.rst +0 -0
  32. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/__init__.py +0 -0
  33. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/augmentation/__init__.py +0 -0
  34. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/augmentation/base.py +0 -0
  35. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/augmentation/functional.py +0 -0
  36. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/augmentation/transforms.py +0 -0
  37. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/classifier.py +0 -0
  38. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/__init__.py +0 -0
  39. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/_notebook_viewer.py +0 -0
  40. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bbci.py +0 -0
  41. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bcicomp.py +0 -0
  42. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bids/__init__.py +0 -0
  43. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bids/datasets.py +0 -0
  44. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bids/format.py +0 -0
  45. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bids/hub.py +0 -0
  46. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bids/hub_format.py +0 -0
  47. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bids/hub_io.py +0 -0
  48. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bids/hub_validation.py +0 -0
  49. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/bids/iterable.py +0 -0
  50. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/chb_mit.py +0 -0
  51. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/collate.py +0 -0
  52. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/mne.py +0 -0
  53. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/moabb.py +0 -0
  54. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/nmt.py +0 -0
  55. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/registry.py +0 -0
  56. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/siena.py +0 -0
  57. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  58. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/sleep_physionet.py +0 -0
  59. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/tuh.py +0 -0
  60. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/utils.py +0 -0
  61. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datasets/xy.py +0 -0
  62. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datautil/__init__.py +0 -0
  63. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datautil/channel_utils.py +0 -0
  64. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datautil/hub_formats.py +0 -0
  65. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datautil/serialization.py +0 -0
  66. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/datautil/util.py +0 -0
  67. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/eegneuralnet.py +0 -0
  68. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/functional/__init__.py +0 -0
  69. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/functional/initialization.py +0 -0
  70. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/__init__.py +0 -0
  71. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/atcnet.py +0 -0
  72. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/attentionbasenet.py +0 -0
  73. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/attn_sleep.py +0 -0
  74. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/axon.py +0 -0
  75. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/base.py +0 -0
  76. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/bendr.py +0 -0
  77. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/biot.py +0 -0
  78. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/brainbert.py +0 -0
  79. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/brainmodule.py +0 -0
  80. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/brainomni.py +0 -0
  81. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/brant.py +0 -0
  82. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/cbramod.py +0 -0
  83. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/config.py +0 -0
  84. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/ctnet.py +0 -0
  85. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/dance.py +0 -0
  86. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/deep4.py +0 -0
  87. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/deepsleepnet.py +0 -0
  88. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/dgcnn.py +0 -0
  89. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eeg_clip.py +0 -0
  90. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegconformer.py +0 -0
  91. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegdino.py +0 -0
  92. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eeginception_erp.py +0 -0
  93. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegitnet.py +0 -0
  94. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegminer.py +0 -0
  95. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegnet.py +0 -0
  96. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegsimpleconv.py +0 -0
  97. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegsym.py +0 -0
  98. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/eegtcnet.py +0 -0
  99. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/emg2qwerty.py +0 -0
  100. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/fbcnet.py +0 -0
  101. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/fblightconvnet.py +0 -0
  102. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/fbmsnet.py +0 -0
  103. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/guetschel2026.py +0 -0
  104. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/hybrid.py +0 -0
  105. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/ifnet.py +0 -0
  106. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/mapa.py +0 -0
  107. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/mirepnet.py +0 -0
  108. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/mscformer.py +0 -0
  109. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/msvtnet.py +0 -0
  110. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/mvpformer.py +0 -0
  111. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/neuropose.py +0 -0
  112. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/neurorvq.py +0 -0
  113. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/popt.py +0 -0
  114. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/sccnet.py +0 -0
  115. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/seizure_transformer.py +0 -0
  116. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/sensingdynamics.py +0 -0
  117. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/shallow_fbcsp.py +0 -0
  118. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/signal_jepa.py +0 -0
  119. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/sinc_shallow.py +0 -0
  120. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
  121. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
  122. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/sleepfm.py +0 -0
  123. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/sparcnet.py +0 -0
  124. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/sstdpn.py +0 -0
  125. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/steegformer.py +0 -0
  126. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/summary.csv +0 -0
  127. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/syncnet.py +0 -0
  128. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/tcformer.py +0 -0
  129. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/tcn.py +0 -0
  130. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/tfm_tokenizer.py +0 -0
  131. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/tidnet.py +0 -0
  132. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/tmsanet.py +0 -0
  133. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/tsinception.py +0 -0
  134. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/usleep.py +0 -0
  135. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/util.py +0 -0
  136. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/models/vemg2pose.py +0 -0
  137. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/__init__.py +0 -0
  138. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/activation.py +0 -0
  139. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/blocks.py +0 -0
  140. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/channels.py +0 -0
  141. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/convolution.py +0 -0
  142. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/filter.py +0 -0
  143. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/linear.py +0 -0
  144. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/linear_attention.py +0 -0
  145. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/parametrization.py +0 -0
  146. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/popt_modules.py +0 -0
  147. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/quantization.py +0 -0
  148. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/stats.py +0 -0
  149. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/util.py +0 -0
  150. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/modules/wrapper.py +0 -0
  151. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/preprocessing/__init__.py +0 -0
  152. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
  153. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/preprocessing/mne_preprocess.py +0 -0
  154. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/preprocessing/preprocess.py +0 -0
  155. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/preprocessing/util.py +0 -0
  156. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/preprocessing/windowers.py +0 -0
  157. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/regressor.py +0 -0
  158. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/samplers/__init__.py +0 -0
  159. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/samplers/base.py +0 -0
  160. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/samplers/ssl.py +0 -0
  161. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/training/__init__.py +0 -0
  162. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/training/callbacks.py +0 -0
  163. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/training/losses.py +0 -0
  164. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/training/scoring.py +0 -0
  165. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/util.py +0 -0
  166. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/visualization/__init__.py +0 -0
  167. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/visualization/activations.py +0 -0
  168. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/visualization/attribution.py +0 -0
  169. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/visualization/confusion_matrices.py +0 -0
  170. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/visualization/frequency.py +0 -0
  171. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/visualization/metrics.py +0 -0
  172. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/visualization/sanity.py +0 -0
  173. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode/visualization/topology.py +0 -0
  174. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode.egg-info/SOURCES.txt +0 -0
  175. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode.egg-info/dependency_links.txt +0 -0
  176. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode.egg-info/requires.txt +0 -0
  177. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/braindecode.egg-info/top_level.txt +0 -0
  178. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/Makefile +0 -0
  179. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/_templates/autosummary/class.rst +0 -0
  180. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  181. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/_templates/autosummary/function.rst +0 -0
  182. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  183. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/agent_docs.rst +0 -0
  184. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/api.rst +0 -0
  185. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/cite.rst +0 -0
  186. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/conf.py +0 -0
  187. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/help.rst +0 -0
  188. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/index.rst +0 -0
  189. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/install/install.rst +0 -0
  190. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/install/install_pip.rst +0 -0
  191. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/install/install_source.rst +0 -0
  192. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/categorization/attention.rst +0 -0
  193. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/categorization/channel.rst +0 -0
  194. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/categorization/convolution.rst +0 -0
  195. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/categorization/filterbank.rst +0 -0
  196. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/categorization/gnn.rst +0 -0
  197. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/categorization/interpretable.rst +0 -0
  198. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/categorization/lbm.rst +0 -0
  199. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/categorization/recurrent.rst +0 -0
  200. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/categorization/spd.rst +0 -0
  201. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/models.rst +0 -0
  202. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/models_categorization.rst +0 -0
  203. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/models_table.rst +0 -0
  204. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/models/models_visualization.rst +0 -0
  205. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/sg_execution_times.rst +0 -0
  206. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/docs/user_guide/channel_strategies.rst +0 -0
  207. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/pyproject.toml +0 -0
  208. {braindecode-1.8.1.dev174738705 → braindecode-1.8.1.dev174812945}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.8.1.dev174738705
3
+ Version: 1.8.1.dev174812945
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>
@@ -279,7 +279,7 @@ def _compact_fif_tags(inst):
279
279
  return inst
280
280
 
281
281
 
282
- def _read_fif_window(raw, start, stop):
282
+ def _read_fif_window(raw, start, stop, willneed=False):
283
283
  """Samples ``start:stop`` of a lazy FIF raw, copied from the file in one go.
284
284
 
285
285
  mne reads, casts and calibrates the FIF data buffers of a range one at a
@@ -290,6 +290,10 @@ def _read_fif_window(raw, start, stop):
290
290
  times ``raw._cals``), so the values equal ``raw._getitem``'s. Returns None
291
291
  (the caller uses mne) unless ``raw`` is a lazy, single-file, uncompressed
292
292
  FIF raw of float32 buffers without projector, compensation or gaps.
293
+
294
+ With ``willneed=True`` nothing is read and None is returned: the OS is
295
+ only asked to start reading the window's bytes into the page cache in the
296
+ background (``posix_fadvise(POSIX_FADV_WILLNEED)``).
293
297
  """
294
298
  if (
295
299
  not isinstance(raw, mne.io.Raw)
@@ -316,6 +320,18 @@ def _read_fif_window(raw, start, stop):
316
320
  if not len(rows) or (rows[:, 1] != FIFF.FIFFT_FLOAT).any():
317
321
  return None
318
322
  size, pos = rows[:, 2].tolist(), rows[:, 4].tolist()
323
+ if willneed: # only a hint: any error is left to the read itself
324
+ try:
325
+ with open(fname, "rb") as f:
326
+ os.posix_fadvise(
327
+ f.fileno(),
328
+ pos[0],
329
+ pos[-1] + 16 + size[-1] - pos[0],
330
+ os.POSIX_FADV_WILLNEED,
331
+ )
332
+ except OSError:
333
+ pass
334
+ return None
319
335
  # mmap: no copy through a 4 MB read buffer, and a cold file is read around
320
336
  # the window by the kernel (as mne's 4 MB reads did), not window by window
321
337
  with open(fname, "rb") as f, mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as m:
@@ -1362,7 +1378,8 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1362
1378
  def __len__(self):
1363
1379
  return self._ensure_cumulative_sizes()[-1]
1364
1380
 
1365
- def _get_single_item(self, idx: int):
1381
+ def _locate(self, idx: int):
1382
+ """The dataset that holds item ``idx`` and the item's index in it."""
1366
1383
  if idx < 0:
1367
1384
  if -idx > len(self):
1368
1385
  raise ValueError(
@@ -1376,7 +1393,33 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1376
1393
  sample_idx = idx
1377
1394
  else:
1378
1395
  sample_idx = idx - cumulative_sizes[dataset_idx - 1]
1379
- return self.datasets[dataset_idx][sample_idx]
1396
+ return self.datasets[dataset_idx], sample_idx
1397
+
1398
+ def _get_single_item(self, idx: int):
1399
+ ds, i = self._locate(idx)
1400
+ return ds[i]
1401
+
1402
+ def __getitems__(self, indices):
1403
+ """Items ``indices``, as ``[self[i] for i in indices]``.
1404
+
1405
+ A :class:`~torch.utils.data.DataLoader` fetches each batch through
1406
+ this. The lazy FIF windows of the batch are first requested from the
1407
+ OS all at once (``posix_fadvise(POSIX_FADV_WILLNEED)``, where it
1408
+ exists), so their reads from disk overlap instead of running one after
1409
+ another: on a network file system a batch read from cold files then
1410
+ costs about one round trip, not one per window.
1411
+ """
1412
+ # ponytail: advises the whole batch at once; chunk it if a batch's
1413
+ # windows ever outgrow the page cache
1414
+ if hasattr(os, "posix_fadvise"):
1415
+ for idx in indices:
1416
+ if not isinstance(idx, (int, np.integer)):
1417
+ continue # e.g. a SequenceSampler tuple: read, not advised
1418
+ ds, i = self._locate(idx)
1419
+ if isinstance(ds, EEGWindowsDataset) and ds._zarr_data is None:
1420
+ _, start, stop = ds.crop_inds[i].tolist()
1421
+ _read_fif_window(ds.raw, start, stop, willneed=True)
1422
+ return [self[idx] for idx in indices]
1380
1423
 
1381
1424
  def __getitem__(self, idx: int | list):
1382
1425
  """
@@ -12,8 +12,10 @@ never downcast, so callers wrap the numerically sensitive parts with
12
12
  #
13
13
  # License: BSD (3-clause)
14
14
  import functools
15
+ from typing import Optional
15
16
 
16
17
  import torch
18
+ from torch import nn
17
19
 
18
20
 
19
21
  def needs_real_dft(x: torch.Tensor) -> bool:
@@ -136,3 +138,56 @@ def hilbert_freq_real(x: torch.Tensor, forward_fourier: bool) -> torch.Tensor:
136
138
  return torch.stack((out_real, out_imag), dim=-1)
137
139
 
138
140
  return compute(x)
141
+
142
+
143
+ @torch.jit.unused
144
+ def fft_conv1d_real(
145
+ x: torch.Tensor, weight: torch.Tensor, bias: Optional[torch.Tensor]
146
+ ) -> torch.Tensor:
147
+ """Real-valued equivalent of :func:`braindecode.functional.fft_conv1d`.
148
+
149
+ Overlap-save: the input is cut into blocks of ``n`` samples (the power of
150
+ two ``>= 2 * kernel_size``) that advance by ``n - kernel_size + 1`` outputs,
151
+ each block's DFT is a matmul against the cosine/sine basis, and per
152
+ frequency the complex product with the kernels, summed over the input
153
+ channels, is one real matmul.
154
+ """
155
+
156
+ @fp32_island
157
+ def compute(x, weight):
158
+ n_times, kernel_size = x.shape[-1], weight.shape[-1]
159
+ n = 1 << (2 * kernel_size - 1).bit_length()
160
+ hop = n - kernel_size + 1
161
+ n_blocks = -(-n_times // hop)
162
+ n_freqs = n // 2 + 1
163
+ # "same" alignment: torch pads the extra zero of an even kernel on the right
164
+ left = (kernel_size - 1) // 2
165
+ x = nn.functional.pad(x, (left, (n_blocks + 1) * hop - n_times - left))
166
+ hops = x.unflatten(-1, (n_blocks + 1, hop))
167
+ # block i = hop i + the first kernel_size - 1 samples of hop i + 1
168
+ blocks = torch.cat([hops[:, :, :-1], hops[:, :, 1:, : kernel_size - 1]], -1)
169
+ cosine, sine = real_dft_basis(n, device=x.device, dtype=x.dtype)
170
+ basis = torch.cat([cosine, -sine]).transpose(0, 1) # -> [Re | Im] of rfft
171
+ x_f = blocks @ basis # (batch, in, blocks, 2 * n_freqs)
172
+ w_f = nn.functional.pad(weight.to(x), (0, n - kernel_size)) @ basis
173
+ w_re, w_im = w_f[..., :n_freqs], w_f[..., n_freqs:]
174
+ # conv1d is a cross-correlation: x_f * conj(w_f), summed over the input
175
+ # channels. Per frequency [Re x, Im x] @ [[Re w, -Im w], [Im w, Re w]].
176
+ w_f = torch.cat([torch.cat([w_re, -w_im]), torch.cat([w_im, w_re])], 1)
177
+ x_f = x_f.unflatten(-1, (2, n_freqs)).permute(4, 0, 2, 3, 1).flatten(1, 2)
178
+ y_f = torch.bmm(x_f.flatten(2), w_f.permute(2, 1, 0)) # (freq, ., 2 * out)
179
+ y_f = y_f.unflatten(1, (x.shape[0], n_blocks)).unflatten(-1, (2, -1))
180
+ y_f = y_f.permute(1, 4, 2, 3, 0).flatten(
181
+ -2
182
+ ) # (batch, out, blocks, 2 * n_freqs)
183
+ # inverse DFT of the first `hop` samples, the ones without wrap-around
184
+ scale = torch.full((n_freqs, 1), 2.0 / n, dtype=x.dtype, device=x.device)
185
+ scale[0] = 1.0 / n
186
+ scale[-1] = 1.0 / n # Nyquist (n is even)
187
+ inverse = torch.cat([scale * cosine[:, :hop], -scale * sine[:, :hop]])
188
+ return (y_f @ inverse).flatten(-2)[..., :n_times]
189
+
190
+ y = compute(x, weight)
191
+ if bias is not None:
192
+ y = y + bias.to(y).unsqueeze(-1)
193
+ return y.to(x)
@@ -62,9 +62,10 @@ def fft_conv1d(
62
62
 
63
63
  Equal to the direct convolution up to float rounding: the FFT is long
64
64
  enough for a linear (not circular) convolution. Its cost barely grows with
65
- the kernel size, so it is faster than the direct one for long kernels on
66
- CPU (see :func:`prefer_fft_conv`). float16/bfloat16 inputs are computed in
67
- float32 (:func:`spectral_input`) and cast back.
65
+ the kernel size, so it is faster than the direct one for long kernels
66
+ (see :func:`prefer_fft_conv`). float16/bfloat16 inputs are computed in
67
+ float32 (:func:`spectral_input`) and cast back. On Intel Gaudi (HPU, no
68
+ complex dtype) the DFTs are real matmuls over overlapping blocks.
68
69
 
69
70
  Parameters
70
71
  ----------
@@ -82,6 +83,8 @@ def fft_conv1d(
82
83
  ``x``. For an even ``kernel_size`` it is aligned like torch's
83
84
  ``padding="same"`` (the extra zero padded on the right).
84
85
  """
86
+ if _real_dft.needs_real_dft(x):
87
+ return _real_dft.fft_conv1d_real(x, weight, bias)
85
88
  n_times, kernel_size = x.shape[-1], weight.shape[-1]
86
89
  # >= n_times + kernel_size - 1 (no circular wrap-around), even and
87
90
  # 5-smooth: the fast cases of a real FFT
@@ -115,8 +118,11 @@ def prefer_fft_conv(x: torch.Tensor, kernel_size: int) -> bool:
115
118
  and from ``batch_size * kernel_size >= 600`` if also
116
119
  ``in_channels * kernel_size >= 120``: with one or two input channels a
117
120
  float32 direct convolution stays cheaper up to k = 85 / 31 (13 long
118
- temporal convolutions of the model zoo, 22 channels x batch 32). GPUs and
119
- HPUs (no complex dtype) keep the direct convolution.
121
+ temporal convolutions of the model zoo, 22 channels x batch 32). On Intel
122
+ Gaudi (HPU) a direct convolution costs about ``batch_size * n_times *
123
+ kernel_size`` and the FFT about a fixed few milliseconds in eager mode: the
124
+ FFT wins from 8 input channels and ``batch_size * n_times * kernel_size >=
125
+ 8e6``. GPUs keep the direct convolution.
120
126
 
121
127
  Parameters
122
128
  ----------
@@ -125,6 +131,8 @@ def prefer_fft_conv(x: torch.Tensor, kernel_size: int) -> bool:
125
131
  kernel_size : int
126
132
  Kernel length in samples.
127
133
  """
134
+ if x.device.type == "hpu":
135
+ return x.shape[1] >= 8 and x.shape[0] * x.shape[-1] * kernel_size >= 8e6
128
136
  return x.device.type == "cpu" and (
129
137
  x.element_size() == 2
130
138
  or (x.shape[0] * kernel_size >= 600 and x.shape[1] * kernel_size >= 120)
@@ -7,13 +7,13 @@ from __future__ import annotations
7
7
  from typing import Optional
8
8
 
9
9
  import torch
10
- import torch.nn.functional as F
11
10
  from einops.layers.torch import Rearrange
12
11
  from torch import nn
13
12
 
14
13
  from braindecode.models.base import EEGModuleMixin
15
14
  from braindecode.models.util import extract_channel_locations_from_chs_info
16
15
  from braindecode.modules import GatedLinearUnit, PatchTokenizer
16
+ from braindecode.modules.attention import _scaled_dot_product_attention
17
17
 
18
18
 
19
19
  class BaRISTA(EEGModuleMixin, nn.Module, license="other"):
@@ -689,7 +689,7 @@ class _RotarySelfAttention(nn.Module):
689
689
 
690
690
  # Dropout falls on the attention output rather than on the weights,
691
691
  # through dropout_p, because the reference disables the latter outright.
692
- attention = F.scaled_dot_product_attention(query, key, value)
692
+ attention = _scaled_dot_product_attention(query, key, value)
693
693
  attention = self.dropout(attention)
694
694
  attention = self.merge_heads(attention)
695
695
  return self.o_proj(attention)
@@ -15,6 +15,7 @@ from torch import nn
15
15
  from torch.nn.utils.parametrizations import weight_norm
16
16
 
17
17
  from braindecode.functional import spectral_input
18
+ from braindecode.functional.functions import _next_fast_len
18
19
  from braindecode.models.base import EEGModuleMixin
19
20
 
20
21
 
@@ -521,16 +522,21 @@ class _GConv(nn.Module):
521
522
  kernel = F.pad(k_fwd, (0, seq_len)) + F.pad(k_bwd.flip(-1), (seq_len, 0))
522
523
 
523
524
  # FFT-based convolution: O(N log N)
525
+ # The bidirectional kernel is circular (lags 0..L-1, then -L..-1).
526
+ # Rolled to lags -L..L-1 it is a linear convolution read from lag 0,
527
+ # so any FFT length >= 2 * seq_len works: take a 5-smooth (fast) one.
528
+ start = seq_len if self.bidirectional else 0
529
+ n_fft = 2 * _next_fast_len(seq_len)
524
530
  # kernel_freq: (channels, d_model, freq_bins)
525
531
  # spectral_input(x) dtype: x's, at least float32 (the reference's .float()).
526
532
  x_in = spectral_input(x)
527
- kernel_freq = torch.fft.rfft(kernel.to(x_in), n=2 * seq_len)
533
+ kernel_freq = torch.fft.rfft(kernel.roll(start, -1).to(x_in), n=n_fft)
528
534
  # x_freq: (batch, d_model, freq_bins)
529
- x_freq = torch.fft.rfft(x_in, n=2 * seq_len)
535
+ x_freq = torch.fft.rfft(x_in, n=n_fft)
530
536
  # out_freq: (batch, channels, d_model, freq_bins)
531
537
  out_freq = torch.einsum("bhl,chl->bchl", x_freq, kernel_freq)
532
538
  # out: (batch, channels, d_model, seq_len)
533
- out = torch.fft.irfft(out_freq, n=2 * seq_len)[..., :seq_len].to(x)
539
+ out = torch.fft.irfft(out_freq, n=n_fft)[..., start : start + seq_len].to(x)
534
540
 
535
541
  # Skip connection via learnable D matrix
536
542
  # (batch, channels, d_model, seq_len)
@@ -697,18 +703,15 @@ class _ResidualBlock(nn.Module):
697
703
  # Sliding-window attention branch
698
704
  # (batch, 2*res_channels, seq_len) -> (batch, seq_len, 2*res_channels)
699
705
  h_attn = h_ssm.transpose(1, 2)
700
- if (
701
- self.swa_window_size == 1
702
- and not self.training
703
- and not torch.is_grad_enabled()
704
- and h_attn.device.type == "cpu"
705
- ):
706
+ if self.swa_window_size == 1 and h_attn.device.type == "cpu":
706
707
  # A one-wide window keeps only the diagonal: the softmax weights are
707
- # exactly 1 and 0, so the attention returns its value projection.
708
- # Same projection calls as nn.MultiheadAttention (on its (seq,
709
- # batch, embed) view), so the output is unchanged bit for bit. (In
710
- # training the weights carry dropout; on Gaudi the full attention
711
- # was faster.)
708
+ # exactly 1 and 0, so the attention returns its value projection,
709
+ # times the weights' dropout in training (the same (batch * heads,
710
+ # L, L) mask nn.MultiheadAttention draws, of which only the
711
+ # diagonal reaches the output). Same projection calls as
712
+ # nn.MultiheadAttention (on its (seq, batch, embed) view), so
713
+ # outputs and gradients are unchanged bit for bit, and so is the
714
+ # RNG stream. (On Gaudi the full attention was faster.)
712
715
  # ponytail: the unused query/key projections are computed too, to
713
716
  # keep the rounding; in_proj_weight[2E:] alone is ~2x less GEMM.
714
717
  embed_dim = self.attention.embed_dim
@@ -716,6 +719,16 @@ class _ResidualBlock(nn.Module):
716
719
  value = F.linear(
717
720
  seq_first, self.attention.in_proj_weight, self.attention.in_proj_bias
718
721
  )[..., 2 * embed_dim :]
722
+ p = self.attention.dropout
723
+ if self.training and p > 0.0:
724
+ n_rows = seq_first.shape[1] * self.attention.num_heads
725
+ keep = (
726
+ value.new_empty(n_rows, seq_len, seq_len)
727
+ .bernoulli_(1 - p)
728
+ .diagonal(dim1=1, dim2=2)
729
+ .div(1 - p)
730
+ )
731
+ value = value.reshape(seq_len, n_rows, -1) * keep.t().unsqueeze(-1)
719
732
  h_attn = (
720
733
  self.attention.out_proj(value.reshape(-1, embed_dim))
721
734
  .view(seq_first.shape)
@@ -4,7 +4,7 @@ import torch
4
4
  import torch.nn as nn
5
5
  from mne.utils import warn
6
6
 
7
- from braindecode.functional import spectral_input
7
+ from braindecode.functional import _real_dft, spectral_input
8
8
  from braindecode.models.base import EEGModuleMixin
9
9
  from braindecode.models.util import _disable_batch_norm_training_if_batch_size_one
10
10
 
@@ -297,6 +297,8 @@ class _STFTModule(nn.Module):
297
297
 
298
298
  def forward(self, x: torch.Tensor) -> torch.Tensor:
299
299
  # x: (B, C, T)
300
+ if _real_dft.needs_real_dft(x) and self.center and self.one_sided:
301
+ return self._forward_real_dft(x).to(x)
300
302
  B, C, T = x.shape
301
303
  # flatten batch & channel into one dim
302
304
  x_flat = spectral_input(x.reshape(B * C, T))
@@ -319,3 +321,23 @@ class _STFTModule(nn.Module):
319
321
  spec = spec_flat.view(B, C, F, L)
320
322
 
321
323
  return torch.abs(spec).to(x)
324
+
325
+ @torch.jit.unused
326
+ @_real_dft.fp32_island
327
+ def _forward_real_dft(self, x):
328
+ # The magnitude above without complex tensors, on the device (Intel
329
+ # Gaudi / HPU), instead of a host STFT and a host-to-device copy of
330
+ # the whole spectrogram: reflect-padded frames (gathered, not a
331
+ # strided unfold) times the one-sided DFT basis. No annotations: with
332
+ # postponed annotations torch.jit.unused cannot resolve them.
333
+ pad = self.n_fft // 2
334
+ x = nn.functional.pad(x, (pad, pad), mode="reflect")
335
+ n_frames = 1 + (x.shape[-1] - self.n_fft) // self.hop_length
336
+ starts = torch.arange(n_frames, device=x.device) * self.hop_length
337
+ index = (starts[:, None] + torch.arange(self.n_fft, device=x.device)).flatten()
338
+ frames = x.index_select(-1, index).unflatten(-1, (n_frames, self.n_fft))
339
+ real, imag = _real_dft.real_rfft(frames)
340
+ magnitude = torch.sqrt(real * real + imag * imag)
341
+ if self.normalized:
342
+ magnitude = magnitude / self.n_fft**0.5
343
+ return magnitude.transpose(-1, -2)
@@ -541,6 +541,7 @@ class _RegionEmbedding(nn.Module):
541
541
  """
542
542
 
543
543
  region_bounds: List[Tuple[int, int]]
544
+ tap_starts: List[int]
544
545
 
545
546
  def __init__(
546
547
  self,
@@ -574,42 +575,61 @@ class _RegionEmbedding(nn.Module):
574
575
  for region_key in area_config
575
576
  }
576
577
  )
578
+ # All regions in one pass: tap j of the widest kernel reads, for every
579
+ # electrode, the electrode at offset j - half on its region's ring
580
+ # (circular padding), or nothing (zero padding of a one-electrode
581
+ # region); kernel k uses the middle k taps. As a 0/1 matrix, so the
582
+ # gather and its backward are matmuls.
583
+ half = (max(kernel_sizes) - 1) // 2
584
+ n_taps = 2 * half + 1
585
+ self.tap_starts = [half - (k - 1) // 2 for k in kernel_sizes]
586
+ n_chans = self.region_bounds[-1][1] if self.region_bounds else 0
587
+ chan_region = []
588
+ tap_select = torch.zeros(n_chans * n_taps, n_chans)
589
+ for i, (start, stop) in enumerate(self.region_bounds):
590
+ n = stop - start
591
+ for p in range(n):
592
+ chan_region.append(i)
593
+ for d in range(-half, half + 1) if n > 1 else [0]:
594
+ row = (start + p) * n_taps + d + half
595
+ tap_select[row, start + (p + d) % n] = 1.0
596
+ self.register_buffer(
597
+ "chan_region", torch.tensor(chan_region, dtype=torch.long), False
598
+ )
599
+ self.register_buffer("tap_select", tap_select, False)
577
600
 
578
601
  def forward(self, x):
579
602
  batch, chans, T, dim_in = x.shape
580
- output = torch.zeros(
581
- (batch, chans, T, self.dim_out), device=x.device, dtype=x.dtype
582
- )
583
- for i, blocks in enumerate(self.region_blocks.values()):
584
- start, stop = self.region_bounds[i]
585
- n_electrodes = stop - start
586
- x_region = x[:, start:stop, :, :]
587
- x_trans = (
588
- x_region.permute(0, 2, 1, 3)
589
- .reshape(-1, n_electrodes, dim_in)
590
- .permute(0, 2, 1)
591
- .unsqueeze(-1)
603
+ if len(self.region_bounds) == 0:
604
+ return torch.zeros(
605
+ (batch, chans, T, self.dim_out), device=x.device, dtype=x.dtype
592
606
  )
593
- fmap_outputs = []
594
- for conv in blocks:
595
- k = conv.kernel_size[0]
596
- pad = (k - 1) // 2
597
- if n_electrodes == 1:
598
- x_padded = F.pad(
599
- x_trans, (0, 0, pad, pad), mode="constant", value=0.0
600
- )
601
- else:
602
- x_padded = F.pad(x_trans, (0, 0, pad, pad), mode="circular")
603
- fmap_outputs.append(conv(x_padded))
604
- fmap_cat = torch.cat(fmap_outputs, dim=1)
605
- fmap_out = (
606
- fmap_cat.squeeze(-1)
607
- .permute(0, 2, 1)
608
- .reshape(batch, T, n_electrodes, self.dim_out)
609
- .permute(0, 2, 1, 3)
607
+ # (batch, chans, n_taps, T, dim_in): every electrode's ring neighbours
608
+ taps = torch.matmul(self.tap_select, x.reshape(batch, chans, T * dim_in))
609
+ taps = taps.reshape(batch, chans, -1, T, dim_in)
610
+ # Per kernel, one batched matmul over electrodes, each with its
611
+ # region's weights: (chans, batch * T, k * dim_in) @ (chans, k * dim_in, out)
612
+ weights: List[List[Tensor]] = [[] for _ in self.tap_starts]
613
+ biases: List[List[Tensor]] = [[] for _ in self.tap_starts]
614
+ for blocks in self.region_blocks.values():
615
+ for j, conv in enumerate(blocks):
616
+ bias = conv.bias
617
+ assert bias is not None
618
+ weights[j].append(conv.weight)
619
+ biases[j].append(bias)
620
+ outs = []
621
+ for j, start in enumerate(self.tap_starts):
622
+ w = torch.stack(weights[j]).index_select(0, self.chan_region)
623
+ k = w.shape[3]
624
+ w = w.squeeze(-1).permute(0, 3, 2, 1).reshape(chans, k * dim_in, -1)
625
+ t = taps[:, :, start : start + k].permute(1, 0, 3, 2, 4)
626
+ b = torch.stack(biases[j]).index_select(0, self.chan_region)
627
+ outs.append(
628
+ torch.bmm(t.reshape(chans, batch * T, k * dim_in), w) + b.unsqueeze(1)
610
629
  )
611
- output[:, start:stop, :, :] = fmap_out
612
- return output
630
+ out = torch.cat(outs, dim=-1).view(chans, batch, T, self.dim_out)
631
+ # contiguous: the residual sum keeps this layout and the next layer views it
632
+ return out.permute(1, 0, 2, 3).contiguous()
613
633
 
614
634
 
615
635
  class _CSBrainEncoderLayer(nn.Module):
@@ -675,6 +695,17 @@ class _CSBrainEncoderLayer(nn.Module):
675
695
  )
676
696
  else:
677
697
  self.region_attn_mask = None
698
+ # Region of every electrode and electrodes per region, for the
699
+ # region means of all regions at once (index_add_ sums in channel
700
+ # order, as the per-region mean did).
701
+ sizes = [stop - start for start, stop in self.region_bounds]
702
+ chan_region = [i for i, n in enumerate(sizes) for _ in range(n)]
703
+ self.register_buffer(
704
+ "chan_region", torch.tensor(chan_region, dtype=torch.long), False
705
+ )
706
+ self.register_buffer(
707
+ "region_sizes", torch.tensor(sizes, dtype=torch.long)[:, None], False
708
+ )
678
709
 
679
710
  def forward(self, src: Tensor) -> Tensor:
680
711
  x = src
@@ -711,11 +742,13 @@ class _CSBrainEncoderLayer(nn.Module):
711
742
  def _inter_region_attention(self, x: Tensor) -> Tensor:
712
743
  batch, chans, T, Fea = x.shape
713
744
  x_flat = x.permute(0, 2, 1, 3).reshape(batch * T, chans, Fea)
714
- global_features = torch.zeros_like(x_flat)
715
- for start, stop in self.region_bounds:
716
- region_global = x[:, start:stop, :, :].mean(dim=1, keepdim=True)
717
- region_global = region_global.permute(0, 2, 1, 3).reshape(batch * T, 1, Fea)
718
- global_features[:, start:stop, :] = region_global
745
+ if len(self.region_bounds) == 0:
746
+ global_features = torch.zeros_like(x_flat)
747
+ else:
748
+ region_sum = x_flat.new_zeros(batch * T, len(self.region_bounds), Fea)
749
+ region_sum.index_add_(1, self.chan_region, x_flat)
750
+ region_mean = region_sum / self.region_sizes
751
+ global_features = region_mean.index_select(1, self.chan_region)
719
752
  x_enhanced = x_flat + self.global_fc(global_features)
720
753
 
721
754
  attn_output = self.inter_region_attn(
@@ -18,7 +18,7 @@ import torch.nn.functional as F
18
18
  from einops.layers.torch import Rearrange
19
19
  from torch import nn
20
20
 
21
- from braindecode.functional import rotate_pairs, spectral_input
21
+ from braindecode.functional import _real_dft, rotate_pairs, spectral_input
22
22
  from braindecode.models.base import EEGModuleMixin
23
23
  from braindecode.models.util import (
24
24
  INTRACRANIAL_CH_TYPES,
@@ -397,10 +397,13 @@ class DIVER1(EEGModuleMixin, nn.Module, license="apache-2.0"):
397
397
  tokens = self.patch_tokenizer(x)
398
398
  tokens = self.patch_cnn(tokens)
399
399
  if self.spectral_emb is not None:
400
- # At least float32, as the reference's float32 FFT.
401
- fft_input = spectral_input(tokens)
402
- spectrum = torch.fft.rfft(fft_input, dim=-1, norm="forward")
403
- amplitude = spectrum.abs().to(tokens)
400
+ if _real_dft.needs_real_dft(tokens):
401
+ amplitude = self._amplitude_real_dft(tokens).to(tokens)
402
+ else:
403
+ # At least float32, as the reference's float32 FFT.
404
+ fft_input = spectral_input(tokens)
405
+ spectrum = torch.fft.rfft(fft_input, dim=-1, norm="forward")
406
+ amplitude = spectrum.abs().to(tokens)
404
407
  spectral_emb = self.spectral_emb(amplitude)
405
408
  tokens = tokens + spectral_emb
406
409
  if self.chan_emb is not None:
@@ -439,6 +442,16 @@ class DIVER1(EEGModuleMixin, nn.Module, license="apache-2.0"):
439
442
  pooled = tokens.mean(dim=(1, 2))
440
443
  return self.final_layer(pooled)
441
444
 
445
+ @torch.jit.unused
446
+ @_real_dft.fp32_island
447
+ def _amplitude_real_dft(self, tokens):
448
+ # |rfft(tokens, norm="forward")| on the device for accelerators without
449
+ # complex tensors (Intel Gaudi / HPU), instead of a host FFT. The norm
450
+ # keeps abs()'s zero gradient at an all-zero bin.
451
+ real, imag = _real_dft.real_rfft(tokens)
452
+ magnitude = torch.linalg.vector_norm(torch.stack((real, imag)), dim=0)
453
+ return magnitude / tokens.shape[-1]
454
+
442
455
 
443
456
  class _PatchCNN(nn.Sequential):
444
457
  """Power-of-two padded patches encoded by Conv2d/GroupNorm/GELU.
@@ -618,28 +631,13 @@ class _STCPE(nn.Module):
618
631
  inner_heads -= 1
619
632
  self.window = window
620
633
  self.n_patches = n_patches
621
- # Full-height windows, padded in time to include partial overlaps.
622
- self.stride = (1, 1)
623
- self.padding = (0, window - 1)
634
+ # Full-height windows, padded in time to include partial overlaps:
635
+ # window w holds patches w - (window - 1) .. w. Entry j * n_windows + w
636
+ # is the padded patch index of offset j in window w (offset outermost,
637
+ # the order in which F.unfold / F.fold accumulate).
624
638
  self.n_windows = n_patches + window - 1
625
- # Fold features into batch; each temporal window spans every channel.
626
- self.fold_features = Rearrange(
627
- "batch chan patch dim -> (batch dim) 1 chan patch"
628
- )
629
- self.unfold_features = Rearrange(
630
- "(batch dim) 1 chan patch -> batch chan patch dim", dim=inner_dim
631
- )
632
- # One sequence per window for the inner encoder, and back again.
633
- self.windows_to_batch = Rearrange(
634
- "(batch dim) (chan window) n_win -> (batch n_win) (chan window) dim",
635
- dim=inner_dim,
636
- window=window,
637
- )
638
- self.batch_to_windows = Rearrange(
639
- "(batch n_win) (chan window) dim -> (batch dim) (chan window) n_win",
640
- n_win=self.n_windows,
641
- window=window,
642
- )
639
+ offsets = torch.arange(window)[:, None] + torch.arange(self.n_windows)
640
+ self.register_buffer("window_index", offsets.flatten(), persistent=False)
643
641
  self.down = nn.Linear(d_model, inner_dim)
644
642
  self.encoder = _AnyVariateEncoder(
645
643
  d_model=inner_dim,
@@ -655,49 +653,43 @@ class _STCPE(nn.Module):
655
653
 
656
654
  def forward(self, x: torch.Tensor) -> torch.Tensor:
657
655
  """Map a token grid to a positional bias of the same shape."""
658
- n_chans = x.shape[1]
659
- kernel_size = (n_chans, self.window)
660
- grid_size = (n_chans, self.n_patches)
656
+ batch, n_chans, n_patches, _ = x.shape
657
+ window, n_windows = self.window, self.n_windows
661
658
 
662
659
  x = self.down(x)
663
- flat = self.fold_features(x)
664
- unfolded = F.unfold(
665
- flat,
666
- kernel_size=kernel_size,
667
- stride=self.stride,
668
- padding=self.padding,
660
+ dim = x.shape[-1]
661
+ # Every window gathered at once (a strided unfold and its fold-shaped
662
+ # backward are slow on some accelerators, e.g. Intel Gaudi).
663
+ windows = (
664
+ F.pad(x, (0, 0, window - 1, window - 1))
665
+ .index_select(2, self.window_index)
666
+ .view(batch, n_chans, window, n_windows, dim)
667
+ .permute(0, 3, 1, 2, 4)
668
+ .reshape(batch * n_windows, n_chans * window, dim)
669
669
  )
670
- windows = self.windows_to_batch(unfolded)
671
670
 
672
671
  var_id = torch.arange(n_chans, device=x.device).repeat_interleave(self.window)
673
672
  time_id = torch.arange(self.window, device=x.device).repeat(n_chans)
674
673
  encoded = self.encoder(windows, var_id=var_id, time_id=time_id)
675
- encoded = self.batch_to_windows(encoded)
676
-
677
- folded = F.fold(
678
- encoded,
679
- output_size=grid_size,
680
- kernel_size=kernel_size,
681
- stride=self.stride,
682
- padding=self.padding,
674
+ encoded = (
675
+ encoded.view(batch, n_windows, n_chans, window, dim)
676
+ .permute(0, 2, 3, 1, 4)
677
+ .reshape(batch, n_chans, window * n_windows, dim)
678
+ )
679
+
680
+ # Overlap-add back onto the padded patches, then drop the padding.
681
+ n_padded = n_patches + 2 * (window - 1)
682
+ folded = encoded.new_zeros(batch, n_chans, n_padded, dim).index_add(
683
+ 2, self.window_index, encoded
683
684
  )
684
- # Fold in the working dtype: a scalar window divisor changes BF16
685
+ # Count in the working dtype: a scalar window divisor changes BF16
685
686
  # accumulation for large windows (e.g. 259 overlaps accumulate to 256).
686
- overlap = F.fold(
687
- torch.ones(
688
- 1,
689
- n_chans * self.window,
690
- self.n_windows,
691
- dtype=x.dtype,
692
- device=x.device,
693
- ),
694
- output_size=grid_size,
695
- kernel_size=kernel_size,
696
- stride=self.stride,
697
- padding=self.padding,
687
+ overlap = x.new_zeros(n_padded).index_add(
688
+ 0, self.window_index, x.new_ones(window * n_windows)
698
689
  )
699
- averaged = folded / overlap
700
- return self.up(self.unfold_features(averaged))
690
+ start, stop = window - 1, window - 1 + n_patches
691
+ averaged = folded[:, :, start:stop] / overlap[start:stop, None]
692
+ return self.up(averaged)
701
693
 
702
694
 
703
695
  class _AnyVariateEncoder(nn.Module):