Braindecode 1.8.1.dev183301946__tar.gz → 1.8.1.dev183301950__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 (209) hide show
  1. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/NOTICE.txt +1 -0
  2. {braindecode-1.8.1.dev183301946/braindecode.egg-info → braindecode-1.8.1.dev183301950}/PKG-INFO +1 -1
  3. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/base.py +57 -5
  4. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/functional/_real_dft.py +55 -0
  5. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/functional/functions.py +32 -13
  6. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/__init__.py +2 -0
  7. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/axon.py +42 -8
  8. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/barista.py +2 -2
  9. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/brainmodule.py +13 -1
  10. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/codebrain.py +42 -18
  11. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/contrawr.py +23 -1
  12. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/csbrain.py +69 -36
  13. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/ctnet.py +9 -1
  14. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/diver1.py +51 -59
  15. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegconformer.py +14 -1
  16. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eeginception_mi.py +7 -3
  17. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegnex.py +7 -3
  18. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegpt.py +15 -9
  19. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegsym.py +68 -16
  20. braindecode-1.8.1.dev183301950/braindecode/models/epint.py +311 -0
  21. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/labram.py +34 -15
  22. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/luna.py +7 -4
  23. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/medformer.py +47 -20
  24. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/meta_neuromotor.py +35 -8
  25. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/mirepnet.py +15 -4
  26. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/msvtnet.py +115 -0
  27. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/neurorvq.py +3 -2
  28. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/neurorvq_tokenizer.py +10 -1
  29. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/patchedtransformer.py +3 -7
  30. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/reve.py +16 -4
  31. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/signal_jepa.py +81 -3
  32. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/sinc_shallow.py +2 -1
  33. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/summary.csv +1 -0
  34. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/tfm_tokenizer.py +44 -3
  35. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/util.py +2 -0
  36. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/zuna.py +11 -9
  37. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/attention.py +87 -6
  38. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/dance_modules.py +16 -3
  39. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/filter.py +55 -21
  40. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/layers.py +41 -2
  41. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/samplers/base.py +60 -61
  42. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/training/losses.py +5 -4
  43. braindecode-1.8.1.dev183301950/braindecode/version.py +1 -0
  44. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950/braindecode.egg-info}/PKG-INFO +1 -1
  45. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode.egg-info/SOURCES.txt +1 -0
  46. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/api.rst +1 -0
  47. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/whats_new.rst +68 -0
  48. braindecode-1.8.1.dev183301946/braindecode/version.py +0 -1
  49. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/LICENSE.txt +0 -0
  50. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/MANIFEST.in +0 -0
  51. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/README.rst +0 -0
  52. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/__init__.py +0 -0
  53. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/augmentation/__init__.py +0 -0
  54. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/augmentation/base.py +0 -0
  55. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/augmentation/functional.py +0 -0
  56. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/augmentation/transforms.py +0 -0
  57. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/classifier.py +0 -0
  58. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/__init__.py +0 -0
  59. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/_notebook_viewer.py +0 -0
  60. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bbci.py +0 -0
  61. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bcicomp.py +0 -0
  62. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bids/__init__.py +0 -0
  63. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bids/datasets.py +0 -0
  64. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bids/format.py +0 -0
  65. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bids/hub.py +0 -0
  66. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bids/hub_format.py +0 -0
  67. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bids/hub_io.py +0 -0
  68. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bids/hub_validation.py +0 -0
  69. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/bids/iterable.py +0 -0
  70. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/chb_mit.py +0 -0
  71. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/collate.py +0 -0
  72. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/mne.py +0 -0
  73. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/moabb.py +0 -0
  74. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/nmt.py +0 -0
  75. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/registry.py +0 -0
  76. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/siena.py +0 -0
  77. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  78. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/sleep_physionet.py +0 -0
  79. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/tuh.py +0 -0
  80. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/utils.py +0 -0
  81. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datasets/xy.py +0 -0
  82. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datautil/__init__.py +0 -0
  83. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datautil/channel_utils.py +0 -0
  84. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datautil/hub_formats.py +0 -0
  85. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datautil/serialization.py +0 -0
  86. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/datautil/util.py +0 -0
  87. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/eegneuralnet.py +0 -0
  88. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/functional/__init__.py +0 -0
  89. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/functional/initialization.py +0 -0
  90. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/atcnet.py +0 -0
  91. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/attentionbasenet.py +0 -0
  92. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/attn_sleep.py +0 -0
  93. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/base.py +0 -0
  94. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/bendr.py +0 -0
  95. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/biot.py +0 -0
  96. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/brainbert.py +0 -0
  97. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/brainomni.py +0 -0
  98. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/brant.py +0 -0
  99. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/cbramod.py +0 -0
  100. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/config.py +0 -0
  101. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/dance.py +0 -0
  102. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/deep4.py +0 -0
  103. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/deepsleepnet.py +0 -0
  104. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/dgcnn.py +0 -0
  105. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eeg_clip.py +0 -0
  106. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegdino.py +0 -0
  107. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eeginception_erp.py +0 -0
  108. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegitnet.py +0 -0
  109. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegminer.py +0 -0
  110. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegnet.py +0 -0
  111. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegsimpleconv.py +0 -0
  112. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/eegtcnet.py +0 -0
  113. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/emg2qwerty.py +0 -0
  114. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/fbcnet.py +0 -0
  115. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/fblightconvnet.py +0 -0
  116. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/fbmsnet.py +0 -0
  117. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/guetschel2026.py +0 -0
  118. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/hybrid.py +0 -0
  119. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/ifnet.py +0 -0
  120. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/mapa.py +0 -0
  121. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/mscformer.py +0 -0
  122. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/mvpformer.py +0 -0
  123. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/neuropose.py +0 -0
  124. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/popt.py +0 -0
  125. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/sccnet.py +0 -0
  126. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/seizure_transformer.py +0 -0
  127. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/sensingdynamics.py +0 -0
  128. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/shallow_fbcsp.py +0 -0
  129. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
  130. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
  131. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/sleepfm.py +0 -0
  132. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/sparcnet.py +0 -0
  133. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/sstdpn.py +0 -0
  134. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/steegformer.py +0 -0
  135. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/syncnet.py +0 -0
  136. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/tcformer.py +0 -0
  137. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/tcn.py +0 -0
  138. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/tidnet.py +0 -0
  139. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/tmsanet.py +0 -0
  140. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/tsinception.py +0 -0
  141. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/usleep.py +0 -0
  142. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/models/vemg2pose.py +0 -0
  143. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/__init__.py +0 -0
  144. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/activation.py +0 -0
  145. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/blocks.py +0 -0
  146. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/channels.py +0 -0
  147. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/convolution.py +0 -0
  148. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/linear.py +0 -0
  149. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/linear_attention.py +0 -0
  150. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/parametrization.py +0 -0
  151. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/popt_modules.py +0 -0
  152. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/quantization.py +0 -0
  153. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/stats.py +0 -0
  154. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/util.py +0 -0
  155. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/modules/wrapper.py +0 -0
  156. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/preprocessing/__init__.py +0 -0
  157. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
  158. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/preprocessing/mne_preprocess.py +0 -0
  159. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/preprocessing/preprocess.py +0 -0
  160. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/preprocessing/util.py +0 -0
  161. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/preprocessing/windowers.py +0 -0
  162. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/regressor.py +0 -0
  163. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/samplers/__init__.py +0 -0
  164. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/samplers/ssl.py +0 -0
  165. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/training/__init__.py +0 -0
  166. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/training/callbacks.py +0 -0
  167. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/training/scoring.py +0 -0
  168. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/util.py +0 -0
  169. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/visualization/__init__.py +0 -0
  170. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/visualization/activations.py +0 -0
  171. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/visualization/attribution.py +0 -0
  172. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/visualization/confusion_matrices.py +0 -0
  173. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/visualization/frequency.py +0 -0
  174. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/visualization/metrics.py +0 -0
  175. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/visualization/sanity.py +0 -0
  176. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode/visualization/topology.py +0 -0
  177. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode.egg-info/dependency_links.txt +0 -0
  178. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode.egg-info/requires.txt +0 -0
  179. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/braindecode.egg-info/top_level.txt +0 -0
  180. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/Makefile +0 -0
  181. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/_templates/autosummary/class.rst +0 -0
  182. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  183. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/_templates/autosummary/function.rst +0 -0
  184. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  185. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/agent_docs.rst +0 -0
  186. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/cite.rst +0 -0
  187. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/conf.py +0 -0
  188. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/help.rst +0 -0
  189. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/index.rst +0 -0
  190. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/install/install.rst +0 -0
  191. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/install/install_pip.rst +0 -0
  192. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/install/install_source.rst +0 -0
  193. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/categorization/attention.rst +0 -0
  194. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/categorization/channel.rst +0 -0
  195. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/categorization/convolution.rst +0 -0
  196. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/categorization/filterbank.rst +0 -0
  197. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/categorization/gnn.rst +0 -0
  198. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/categorization/interpretable.rst +0 -0
  199. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/categorization/lbm.rst +0 -0
  200. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/categorization/recurrent.rst +0 -0
  201. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/categorization/spd.rst +0 -0
  202. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/models.rst +0 -0
  203. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/models_categorization.rst +0 -0
  204. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/models_table.rst +0 -0
  205. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/models/models_visualization.rst +0 -0
  206. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/sg_execution_times.rst +0 -0
  207. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/docs/user_guide/channel_strategies.rst +0 -0
  208. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/pyproject.toml +0 -0
  209. {braindecode-1.8.1.dev183301946 → braindecode-1.8.1.dev183301950}/setup.cfg +0 -0
@@ -54,6 +54,7 @@ The following components are licensed under the MIT License:
54
54
  - `docs/_static/model/seizure_transformer_arch.png`
55
55
  - `braindecode/models/guetschel2026.py`
56
56
  - `braindecode/models/tmsanet.py`
57
+ - `braindecode/models/epint.py`
57
58
 
58
59
  The `brainomni.py` file is a derivative of `OpenTSLab/BrainOmni` (MIT), whose
59
60
  SEANet codec derives from Meta's `facebookresearch/encodec` (MIT). The
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.8.1.dev183301946
3
+ Version: 1.8.1.dev183301950
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
  """
@@ -1475,7 +1518,9 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1475
1518
  metadata : pd.DataFrame
1476
1519
  DataFrame containing as many rows as there are windows in the
1477
1520
  BaseConcatDataset, with the metadata and description information
1478
- for each window.
1521
+ for each window, and an ``i_dataset`` column giving the position
1522
+ in ``datasets`` of the dataset (recording) each window comes from.
1523
+ Samplers use it to keep recordings apart.
1479
1524
  """
1480
1525
  if not all(
1481
1526
  [
@@ -1501,9 +1546,15 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1501
1546
  "Dataset descriptions cannot contain the reserved window "
1502
1547
  "metadata key 'i_trial_in_dataset'."
1503
1548
  )
1549
+ if "i_dataset" in df.columns or "i_dataset" in ds.description:
1550
+ raise ValueError(
1551
+ "Window metadata and dataset descriptions cannot contain the "
1552
+ "key 'i_dataset', which get_metadata() reserves for the "
1553
+ "position of the dataset each window comes from. Rename it."
1554
+ )
1504
1555
 
1505
1556
  all_dfs = list()
1506
- for ds in self.datasets:
1557
+ for i_ds, ds in enumerate(self.datasets):
1507
1558
  if hasattr(ds, "_windows") and ds._windows is not None:
1508
1559
  df = ds._windows.metadata
1509
1560
  else:
@@ -1513,6 +1564,7 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
1513
1564
  df = df.copy()
1514
1565
  for k, v in ds.description.items():
1515
1566
  df[k] = v
1567
+ df["i_dataset"] = i_ds
1516
1568
  all_dfs.append(df)
1517
1569
 
1518
1570
  return pd.concat(all_dfs)
@@ -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,22 +83,35 @@ 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
88
91
  n_fft = 2 * _next_fast_len((n_times + kernel_size) // 2)
89
92
  xs = spectral_input(x)
90
93
  # conv1d is a cross-correlation: a convolution with the flipped kernel
91
- x_f = torch.fft.rfft(xs, n=n_fft).permute(2, 0, 1) # (freq, batch, in)
94
+ x_f = torch.fft.rfft(xs, n=n_fft) # (batch, in, freq)
92
95
  w_f = torch.fft.rfft(weight.to(xs).flip(-1), n=n_fft).permute(2, 1, 0)
93
96
  # Per frequency (batch, in) @ (in, out) in complex numbers, as one real
94
- # matmul (a complex one is slower on CPU): [Re x; Im x] @ [Re w, Im w]
95
- # (columns interleaved) holds the four real products.
96
- p = torch.cat([x_f.real, x_f.imag], 1) @ torch.view_as_real(w_f).flatten(2)
97
- p = p.unflatten(1, [2, x.shape[0]]).unflatten(3, [-1, 2]) # x re/im, w re/im
98
- y_f = torch.complex(
99
- p[:, 0, :, :, 0] - p[:, 1, :, :, 1], p[:, 0, :, :, 1] + p[:, 1, :, :, 0]
100
- )
97
+ # matmul (a complex one is slower on CPU).
98
+ needs_grad = torch.is_grad_enabled() and (x.requires_grad or weight.requires_grad)
99
+ if needs_grad or x.shape[0] >= 2 * x.shape[1]:
100
+ # [Re x, Im x] @ [[Re w, Im w], [-Im w, Re w]] (rows of i * w): the
101
+ # output columns are the (Re, Im) pairs of the result, read as complex
102
+ # without a copy (and without a costly backward).
103
+ w_mat = torch.view_as_real(torch.stack([w_f, w_f * 1j], 1)).flatten(1, 2)
104
+ y_f = torch.view_as_real(x_f).permute(2, 0, 3, 1).flatten(2) @ w_mat.flatten(2)
105
+ y_f = torch.view_as_complex(y_f.unflatten(-1, [-1, 2]))
106
+ else:
107
+ # Forward only with fewer rows than twice the input channels: the
108
+ # doubled weight costs more than combining [Re x; Im x] @ [Re w, Im w].
109
+ x_f = x_f.permute(2, 0, 1)
110
+ p = torch.cat([x_f.real, x_f.imag], 1) @ torch.view_as_real(w_f).flatten(2)
111
+ p = p.unflatten(1, [2, x.shape[0]]).unflatten(3, [-1, 2])
112
+ y_f = torch.complex(
113
+ p[:, 0, :, :, 0] - p[:, 1, :, :, 1], p[:, 0, :, :, 1] + p[:, 1, :, :, 0]
114
+ )
101
115
  y = torch.fft.irfft(y_f.permute(1, 2, 0), n=n_fft)
102
116
  # "same" output t is sample t + kernel_size // 2 of the full convolution
103
117
  y = y[..., kernel_size // 2 : kernel_size // 2 + n_times]
@@ -115,8 +129,11 @@ def prefer_fft_conv(x: torch.Tensor, kernel_size: int) -> bool:
115
129
  and from ``batch_size * kernel_size >= 600`` if also
116
130
  ``in_channels * kernel_size >= 120``: with one or two input channels a
117
131
  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.
132
+ temporal convolutions of the model zoo, 22 channels x batch 32). On Intel
133
+ Gaudi (HPU) a direct convolution costs about ``batch_size * n_times *
134
+ kernel_size`` and the FFT about a fixed few milliseconds in eager mode: the
135
+ FFT wins from 8 input channels and ``batch_size * n_times * kernel_size >=
136
+ 8e6``. GPUs keep the direct convolution.
120
137
 
121
138
  Parameters
122
139
  ----------
@@ -125,6 +142,8 @@ def prefer_fft_conv(x: torch.Tensor, kernel_size: int) -> bool:
125
142
  kernel_size : int
126
143
  Kernel length in samples.
127
144
  """
145
+ if x.device.type == "hpu":
146
+ return x.shape[1] >= 8 and x.shape[0] * x.shape[-1] * kernel_size >= 8e6
128
147
  return x.device.type == "cpu" and (
129
148
  x.element_size() == 2
130
149
  or (x.shape[0] * kernel_size >= 600 and x.shape[1] * kernel_size >= 120)
@@ -36,6 +36,7 @@ from .eegsimpleconv import EEGSimpleConv
36
36
  from .eegsym import EEGSym
37
37
  from .eegtcnet import EEGTCNet
38
38
  from .emg2qwerty import EMG2QwertyNet
39
+ from .epint import EpiNT
39
40
  from .fbcnet import FBCNet
40
41
  from .fblightconvnet import FBLightConvNet
41
42
  from .fbmsnet import FBMSNet
@@ -134,6 +135,7 @@ __all__ = [
134
135
  "EEGTCNet",
135
136
  "DGCNN",
136
137
  "EMG2QwertyNet",
138
+ "EpiNT",
137
139
  "NeuroPose",
138
140
  "SensingDynamics",
139
141
  "VEMG2Pose",
@@ -14,6 +14,7 @@ from torch import nn
14
14
  from braindecode.functional import sinusoidal_positional_encoding
15
15
  from braindecode.models.base import EEGModuleMixin
16
16
  from braindecode.models.util import warn_if_sfreq_differs
17
+ from braindecode.modules.attention import _scaled_dot_product_attention
17
18
  from braindecode.modules.channels import _resolve
18
19
 
19
20
 
@@ -462,11 +463,33 @@ class _AxisAttention(nn.Module):
462
463
  .unbind(0)
463
464
  )
464
465
  drop = self.att_drop_prob if self.training else 0.0
465
- attended = F.scaled_dot_product_attention(
466
+ attended = _scaled_dot_product_attention(
466
467
  query, key, value, attn_mask=mask, dropout_p=drop
467
468
  )
468
469
  return attended.transpose(1, 2).reshape(n_seq, length, -1)
469
470
 
471
+ def _attend_two_scales(
472
+ self, projected: torch.Tensor, band: torch.Tensor, weights: torch.Tensor
473
+ ) -> torch.Tensor:
474
+ """Banded and full attention from one score matrix, mixed per query.
475
+
476
+ ``weights`` (N, L, 2) are the scale-gate weights. Both softmaxes share
477
+ the scores and one product with the values serves the mixture.
478
+ """
479
+ n_seq, length, _ = projected.shape
480
+ query, key, value = (
481
+ projected.reshape(n_seq, length, 3, self.num_heads, self.head_dim)
482
+ .permute(2, 0, 3, 1, 4)
483
+ .unbind(0)
484
+ )
485
+ scores = torch.matmul(query, key.transpose(-2, -1)) * self.head_dim**-0.5
486
+ weights = weights.reshape(n_seq, 1, length, 2)
487
+ probs = weights[..., 0:1] * scores.masked_fill(~band, float("-inf")).softmax(
488
+ -1
489
+ ) + weights[..., 1:2] * scores.softmax(-1)
490
+ attended = torch.matmul(probs, value)
491
+ return attended.transpose(1, 2).reshape(n_seq, length, -1)
492
+
470
493
  def forward(self, x: torch.Tensor, band: Optional[torch.Tensor]) -> torch.Tensor:
471
494
  batch, n_chans, n_patches, dim = x.shape
472
495
  axis_weights = self.axis_gate(x) # (B, C, P, 2)
@@ -476,15 +499,26 @@ class _AxisAttention(nn.Module):
476
499
  # the restricted branch is the full one; attend once unless the two
477
500
  # branches draw their own attention dropout.
478
501
  per_channel = self.temporal_qkv(x.reshape(batch * n_chans, n_patches, dim))
479
- restricted = self.temporal_out(self._attend(per_channel, band)).reshape(
480
- batch, n_chans, n_patches, dim
481
- )
482
- full = restricted
483
- if band is not None or (self.training and self.att_drop_prob > 0):
484
- full = self.temporal_out(self._attend(per_channel, None)).reshape(
502
+ dropout = self.training and self.att_drop_prob > 0
503
+ if band is not None and not dropout:
504
+ # The scale-gate weights sum to one, so the two branches share the
505
+ # scores, the product with the values and temporal_out.
506
+ attended = self._attend_two_scales(per_channel, band, scale_weights)
507
+ temporal = self.temporal_out(attended).reshape(
485
508
  batch, n_chans, n_patches, dim
486
509
  )
487
- temporal = scale_weights[..., 0:1] * restricted + scale_weights[..., 1:2] * full
510
+ else:
511
+ restricted = self.temporal_out(self._attend(per_channel, band)).reshape(
512
+ batch, n_chans, n_patches, dim
513
+ )
514
+ full = restricted
515
+ if dropout:
516
+ full = self.temporal_out(self._attend(per_channel, None)).reshape(
517
+ batch, n_chans, n_patches, dim
518
+ )
519
+ temporal = (
520
+ scale_weights[..., 0:1] * restricted + scale_weights[..., 1:2] * full
521
+ )
488
522
 
489
523
  # Spatial path: one sequence of C tokens per time step.
490
524
  per_step = self.spatial_qkv(
@@ -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)
@@ -20,6 +20,7 @@ from torch.nn import functional as F
20
20
  from braindecode.models.base import EEGModuleMixin
21
21
  from braindecode.models.util import has_valid_locations, positions_from_chs_info
22
22
  from braindecode.modules import ChannelMerger
23
+ from braindecode.modules.attention import _hpu_lazy
23
24
  from braindecode.modules.layers import SubjectLayers
24
25
 
25
26
  __all__ = ["BrainModule"]
@@ -712,7 +713,7 @@ class _ConvSequence(nn.Module):
712
713
  nn.Conv1d(
713
714
  chout, 2 * chout, 1 + 2 * glu_context, padding=glu_context
714
715
  ),
715
- nn.GLU(dim=1),
716
+ _GLU(dim=1),
716
717
  )
717
718
  )
718
719
  else:
@@ -727,6 +728,17 @@ class _ConvSequence(nn.Module):
727
728
  return x
728
729
 
729
730
 
731
+ class _GLU(nn.GLU):
732
+ """``nn.GLU`` that Intel Gaudi's lazy mode can compile."""
733
+
734
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
735
+ if not torch.jit.is_scripting() and x.device.type == "hpu" and _hpu_lazy():
736
+ # Its graph compiler fails on ``aten.glu`` after a convolution.
737
+ a, b = x.chunk(2, dim=self.dim)
738
+ return a * torch.sigmoid(b)
739
+ return F.glu(x, self.dim)
740
+
741
+
730
742
  def _pad_multiple(x: torch.Tensor, base: int) -> torch.Tensor:
731
743
  """Pad tensor to be a multiple of base."""
732
744
  length = x.shape[-1]
@@ -15,7 +15,9 @@ 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
20
+ from braindecode.modules.attention import _hpu_lazy
19
21
 
20
22
 
21
23
  class CodeBrain(EEGModuleMixin, nn.Module):
@@ -521,16 +523,21 @@ class _GConv(nn.Module):
521
523
  kernel = F.pad(k_fwd, (0, seq_len)) + F.pad(k_bwd.flip(-1), (seq_len, 0))
522
524
 
523
525
  # FFT-based convolution: O(N log N)
526
+ # The bidirectional kernel is circular (lags 0..L-1, then -L..-1).
527
+ # Rolled to lags -L..L-1 it is a linear convolution read from lag 0,
528
+ # so any FFT length >= 2 * seq_len works: take a 5-smooth (fast) one.
529
+ start = seq_len if self.bidirectional else 0
530
+ n_fft = 2 * _next_fast_len(seq_len)
524
531
  # kernel_freq: (channels, d_model, freq_bins)
525
532
  # spectral_input(x) dtype: x's, at least float32 (the reference's .float()).
526
533
  x_in = spectral_input(x)
527
- kernel_freq = torch.fft.rfft(kernel.to(x_in), n=2 * seq_len)
534
+ kernel_freq = torch.fft.rfft(kernel.roll(start, -1).to(x_in), n=n_fft)
528
535
  # x_freq: (batch, d_model, freq_bins)
529
- x_freq = torch.fft.rfft(x_in, n=2 * seq_len)
536
+ x_freq = torch.fft.rfft(x_in, n=n_fft)
530
537
  # out_freq: (batch, channels, d_model, freq_bins)
531
538
  out_freq = torch.einsum("bhl,chl->bchl", x_freq, kernel_freq)
532
539
  # out: (batch, channels, d_model, seq_len)
533
- out = torch.fft.irfft(out_freq, n=2 * seq_len)[..., :seq_len].to(x)
540
+ out = torch.fft.irfft(out_freq, n=n_fft)[..., start : start + seq_len].to(x)
534
541
 
535
542
  # Skip connection via learnable D matrix
536
543
  # (batch, channels, d_model, seq_len)
@@ -697,18 +704,15 @@ class _ResidualBlock(nn.Module):
697
704
  # Sliding-window attention branch
698
705
  # (batch, 2*res_channels, seq_len) -> (batch, seq_len, 2*res_channels)
699
706
  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
- ):
707
+ if self.swa_window_size == 1 and h_attn.device.type == "cpu":
706
708
  # 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.)
709
+ # exactly 1 and 0, so the attention returns its value projection,
710
+ # times the weights' dropout in training (the same (batch * heads,
711
+ # L, L) mask nn.MultiheadAttention draws, of which only the
712
+ # diagonal reaches the output). Same projection calls as
713
+ # nn.MultiheadAttention (on its (seq, batch, embed) view), so
714
+ # outputs and gradients are unchanged bit for bit, and so is the
715
+ # RNG stream. (On Gaudi the full attention was faster.)
712
716
  # ponytail: the unused query/key projections are computed too, to
713
717
  # keep the rounding; in_proj_weight[2E:] alone is ~2x less GEMM.
714
718
  embed_dim = self.attention.embed_dim
@@ -716,6 +720,16 @@ class _ResidualBlock(nn.Module):
716
720
  value = F.linear(
717
721
  seq_first, self.attention.in_proj_weight, self.attention.in_proj_bias
718
722
  )[..., 2 * embed_dim :]
723
+ p = self.attention.dropout
724
+ if self.training and p > 0.0:
725
+ n_rows = seq_first.shape[1] * self.attention.num_heads
726
+ keep = (
727
+ value.new_empty(n_rows, seq_len, seq_len)
728
+ .bernoulli_(1 - p)
729
+ .diagonal(dim1=1, dim2=2)
730
+ .div(1 - p)
731
+ )
732
+ value = value.reshape(seq_len, n_rows, -1) * keep.t().unsqueeze(-1)
719
733
  h_attn = (
720
734
  self.attention.out_proj(value.reshape(-1, embed_dim))
721
735
  .view(seq_first.shape)
@@ -818,10 +832,20 @@ class _ResidualGroup(nn.Module):
818
832
  def forward(self, input_data: torch.Tensor) -> torch.Tensor:
819
833
  noise = input_data
820
834
  h = noise
821
- skip = torch.zeros((), dtype=h.dtype, device=h.device)
822
- for block in self.residual_blocks:
823
- h, skip_n = block((h, noise))
824
- skip = skip_n + skip
835
+ if not torch.jit.is_scripting() and h.device.type == "hpu" and _hpu_lazy():
836
+ # Gaudi's lazy-mode graph compiler fails on the backward of the
837
+ # sequential skip sum (one skip_conv dedw node lands in two
838
+ # pipeline bundles); one n-ary stack-sum reduction compiles.
839
+ skip_list = []
840
+ for block in self.residual_blocks:
841
+ h, skip_n = block((h, noise))
842
+ skip_list.append(skip_n)
843
+ skip = torch.stack(skip_list, dim=0).sum(dim=0)
844
+ else:
845
+ skip = torch.zeros((), dtype=h.dtype, device=h.device)
846
+ for block in self.residual_blocks:
847
+ h, skip_n = block((h, noise))
848
+ skip = skip_n + skip
825
849
 
826
850
  return skip * math.sqrt(1.0 / self.num_res_layers)
827
851
 
@@ -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)