braindecode 1.3.2.dev179983996__tar.gz → 1.3.2.dev182544824__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 (163) hide show
  1. {braindecode-1.3.2.dev179983996/braindecode.egg-info → braindecode-1.3.2.dev182544824}/PKG-INFO +3 -3
  2. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bids/hub.py +7 -2
  3. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/__init__.py +2 -0
  4. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/base.py +147 -91
  5. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/cbramod.py +19 -5
  6. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/config.py +29 -29
  7. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eegsym.py +1 -1
  8. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/luna.py +8 -7
  9. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/patchedtransformer.py +5 -3
  10. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/reve.py +23 -3
  11. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/shallow_fbcsp.py +16 -8
  12. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/sstdpn.py +1 -1
  13. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/util.py +133 -2
  14. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/__init__.py +2 -1
  15. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/activation.py +20 -0
  16. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/preprocessing/mne_preprocess.py +2 -1
  17. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/util.py +34 -4
  18. braindecode-1.3.2.dev182544824/braindecode/version.py +1 -0
  19. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824/braindecode.egg-info}/PKG-INFO +3 -3
  20. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode.egg-info/requires.txt +2 -2
  21. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/api.rst +10 -9
  22. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/conf.py +18 -6
  23. braindecode-1.3.2.dev182544824/docs/models/categorization/spd.rst +103 -0
  24. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/models_categorization.rst +58 -57
  25. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/whats_new.rst +40 -6
  26. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/pyproject.toml +11 -2
  27. braindecode-1.3.2.dev179983996/braindecode/version.py +0 -1
  28. braindecode-1.3.2.dev179983996/docs/models/categorization/spd.rst +0 -35
  29. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/LICENSE.txt +0 -0
  30. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/MANIFEST.in +0 -0
  31. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/NOTICE.txt +0 -0
  32. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/README.rst +0 -0
  33. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/__init__.py +0 -0
  34. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/augmentation/__init__.py +0 -0
  35. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/augmentation/base.py +0 -0
  36. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/augmentation/functional.py +0 -0
  37. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/augmentation/transforms.py +0 -0
  38. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/classifier.py +0 -0
  39. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/__init__.py +0 -0
  40. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/base.py +0 -0
  41. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bbci.py +0 -0
  42. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bcicomp.py +0 -0
  43. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bids/__init__.py +0 -0
  44. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bids/datasets.py +0 -0
  45. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bids/format.py +0 -0
  46. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bids/hub_format.py +0 -0
  47. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bids/hub_io.py +0 -0
  48. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bids/hub_validation.py +0 -0
  49. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/bids/iterable.py +0 -0
  50. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/chb_mit.py +0 -0
  51. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/mne.py +0 -0
  52. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/moabb.py +0 -0
  53. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/nmt.py +0 -0
  54. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/registry.py +0 -0
  55. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/siena.py +0 -0
  56. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  57. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/sleep_physionet.py +0 -0
  58. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/tuh.py +0 -0
  59. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/utils.py +0 -0
  60. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datasets/xy.py +0 -0
  61. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datautil/__init__.py +0 -0
  62. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datautil/channel_utils.py +0 -0
  63. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datautil/hub_formats.py +0 -0
  64. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datautil/serialization.py +0 -0
  65. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/datautil/util.py +0 -0
  66. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/eegneuralnet.py +0 -0
  67. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/functional/__init__.py +0 -0
  68. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/functional/functions.py +0 -0
  69. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/functional/initialization.py +0 -0
  70. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/atcnet.py +0 -0
  71. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/attentionbasenet.py +0 -0
  72. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/attn_sleep.py +0 -0
  73. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/bendr.py +0 -0
  74. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/biot.py +0 -0
  75. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/brainmodule.py +0 -0
  76. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/contrawr.py +0 -0
  77. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/ctnet.py +0 -0
  78. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/deep4.py +0 -0
  79. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/deepsleepnet.py +0 -0
  80. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/dgcnn.py +0 -0
  81. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eegconformer.py +0 -0
  82. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eeginception_erp.py +0 -0
  83. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eeginception_mi.py +0 -0
  84. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eegitnet.py +0 -0
  85. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eegminer.py +0 -0
  86. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eegnet.py +0 -0
  87. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eegnex.py +0 -0
  88. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eegpt.py +0 -0
  89. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eegsimpleconv.py +0 -0
  90. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/eegtcnet.py +0 -0
  91. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/fbcnet.py +0 -0
  92. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/fblightconvnet.py +0 -0
  93. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/fbmsnet.py +0 -0
  94. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/hybrid.py +0 -0
  95. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/ifnet.py +0 -0
  96. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/labram.py +0 -0
  97. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/medformer.py +0 -0
  98. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/msvtnet.py +0 -0
  99. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/sccnet.py +0 -0
  100. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/signal_jepa.py +0 -0
  101. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/sinc_shallow.py +0 -0
  102. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
  103. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
  104. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/sparcnet.py +0 -0
  105. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/summary.csv +0 -0
  106. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/syncnet.py +0 -0
  107. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/tcn.py +0 -0
  108. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/tidnet.py +0 -0
  109. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/tsinception.py +0 -0
  110. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/models/usleep.py +0 -0
  111. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/attention.py +0 -0
  112. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/blocks.py +0 -0
  113. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/convolution.py +0 -0
  114. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/filter.py +0 -0
  115. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/layers.py +0 -0
  116. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/linear.py +0 -0
  117. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/parametrization.py +0 -0
  118. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/stats.py +0 -0
  119. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/util.py +0 -0
  120. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/modules/wrapper.py +0 -0
  121. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/preprocessing/__init__.py +0 -0
  122. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
  123. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/preprocessing/preprocess.py +0 -0
  124. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/preprocessing/util.py +0 -0
  125. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/preprocessing/windowers.py +0 -0
  126. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/regressor.py +0 -0
  127. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/samplers/__init__.py +0 -0
  128. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/samplers/base.py +0 -0
  129. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/samplers/ssl.py +0 -0
  130. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/training/__init__.py +0 -0
  131. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/training/callbacks.py +0 -0
  132. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/training/losses.py +0 -0
  133. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/training/scoring.py +0 -0
  134. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/visualization/__init__.py +0 -0
  135. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/visualization/confusion_matrices.py +0 -0
  136. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode/visualization/gradients.py +0 -0
  137. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode.egg-info/SOURCES.txt +0 -0
  138. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode.egg-info/dependency_links.txt +0 -0
  139. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/braindecode.egg-info/top_level.txt +0 -0
  140. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/Makefile +0 -0
  141. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/_templates/autosummary/class.rst +0 -0
  142. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  143. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/_templates/autosummary/function.rst +0 -0
  144. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  145. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/cite.rst +0 -0
  146. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/help.rst +0 -0
  147. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/index.rst +0 -0
  148. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/install/install.rst +0 -0
  149. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/install/install_pip.rst +0 -0
  150. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/install/install_source.rst +0 -0
  151. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/categorization/attention.rst +0 -0
  152. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/categorization/channel.rst +0 -0
  153. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/categorization/convolution.rst +0 -0
  154. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/categorization/filterbank.rst +0 -0
  155. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/categorization/gnn.rst +0 -0
  156. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/categorization/interpretable.rst +0 -0
  157. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/categorization/lbm.rst +0 -0
  158. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/categorization/recurrent.rst +0 -0
  159. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/models.rst +0 -0
  160. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/models_table.rst +0 -0
  161. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/models/models_visualization.rst +0 -0
  162. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/docs/sg_execution_times.rst +0 -0
  163. {braindecode-1.3.2.dev179983996 → braindecode-1.3.2.dev182544824}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.3.2.dev179983996
3
+ Version: 1.3.2.dev182544824
4
4
  Summary: Deep learning software to decode EEG, ECG or MEG signals
5
5
  Author-email: Robin Tibor Schirrmeister <robintibor@gmail.com>, Bruno Aristimunha Pinto <b.aristimunha@gmail.com>, Alexandre Gramfort <agramfort@meta.com>
6
6
  Maintainer-email: Alexandre Gramfort <agramfort@meta.com>, Bruno Aristimunha Pinto <b.aristimunha@gmail.com>, Robin Tibor Schirrmeister <robintibor@gmail.com>
@@ -22,7 +22,7 @@ Requires-Python: >=3.11
22
22
  Description-Content-Type: text/x-rst
23
23
  License-File: LICENSE.txt
24
24
  License-File: NOTICE.txt
25
- Requires-Dist: torch>=2.2
25
+ Requires-Dist: torch>=2.0
26
26
  Requires-Dist: torchaudio>=2.0
27
27
  Requires-Dist: mne>=1.11.0
28
28
  Requires-Dist: pandas<3.0.0
@@ -35,7 +35,7 @@ Requires-Dist: wfdb
35
35
  Requires-Dist: linear_attention_transformer
36
36
  Requires-Dist: docstring_inheritance
37
37
  Requires-Dist: rotary_embedding_torch
38
- Requires-Dist: pandas<3.0.0
38
+ Requires-Dist: pydantic>=2.0
39
39
  Provides-Extra: moabb
40
40
  Requires-Dist: moabb>=1.4.3; extra == "moabb"
41
41
  Provides-Extra: eegprep
@@ -845,11 +845,16 @@ class HubDatasetMixin:
845
845
 
846
846
  # Convert to MNE objects and create dataset
847
847
  info = Info.from_json_dict(info_dict)
848
+ targets = metadata["target"].values
849
+ if np.issubdtype(targets.dtype, np.integer):
850
+ event_ids = targets
851
+ else:
852
+ event_ids = np.ones(len(metadata), dtype=int)
848
853
  events = np.column_stack(
849
854
  [
850
- metadata["i_start_in_trial"].values,
855
+ metadata["i_start_in_trial"].values.astype(int),
851
856
  np.zeros(len(metadata), dtype=int),
852
- metadata["target"].values,
857
+ event_ids,
853
858
  ]
854
859
  )
855
860
  epochs = mne.EpochsArray(data, info, events=events, metadata=metadata)
@@ -55,6 +55,7 @@ from .tsinception import TSception
55
55
  from .usleep import USleep
56
56
  from .util import (
57
57
  _init_models_dict,
58
+ build_model_config,
58
59
  extract_channel_locations_from_chs_info,
59
60
  models_mandatory_parameters,
60
61
  )
@@ -118,6 +119,7 @@ __all__ = [
118
119
  "TIDNet",
119
120
  "TSception",
120
121
  "USleep",
122
+ "build_model_config",
121
123
  "_init_models_dict",
122
124
  "models_mandatory_parameters",
123
125
  ]
@@ -17,6 +17,13 @@ from docstring_inheritance import NumpyDocstringInheritanceInitMeta
17
17
  from mne.utils import _soft_import
18
18
  from torchinfo import ModelStatistics, summary
19
19
 
20
+ from braindecode.models.util import (
21
+ _EEG_PARAMS,
22
+ _IMPORT_ADAPTER,
23
+ build_model_config,
24
+ resolve_type_kwargs,
25
+ track_model_init_kwargs,
26
+ )
20
27
  from braindecode.version import __version__
21
28
 
22
29
  huggingface_hub = _soft_import(
@@ -86,61 +93,69 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
86
93
  -----
87
94
  If some input signal-related parameters are not specified,
88
95
  there will be an attempt to infer them from the other parameters.
96
+ """
89
97
 
98
+ #: Template for model-specific Hub integration notes appended by
99
+ #: ``__init_subclass__``. ``{name}`` is replaced with the concrete
100
+ #: model class name.
101
+ _HUB_NOTES_TEMPLATE = """
90
102
  .. rubric:: Hugging Face Hub integration
91
103
 
92
104
  When the optional ``huggingface_hub`` package is installed, all models
93
105
  automatically gain the ability to be pushed to and loaded from the
94
106
  Hugging Face Hub. Install with::
95
107
 
96
- pip install braindecode[hug]
108
+ pip install braindecode[hub]
97
109
 
98
110
  **Pushing a model to the Hub:**
99
111
 
100
- .. code-block:: python
112
+ .. code-block::
101
113
 
102
- from braindecode.models import EEGNetv4
114
+ from braindecode.models import {name}
103
115
 
104
116
  # Train your model
105
- model = EEGNetv4(n_chans=22, n_outputs=4, n_times=1000)
117
+ model = {name}(n_chans=22, n_outputs=4, n_times=1000)
106
118
  # ... training code ...
107
119
 
108
120
  # Push to the Hub
109
121
  model.push_to_hub(
110
- repo_id="username/my-eegnet-model", commit_message="Initial model upload"
122
+ repo_id="username/my-{name_lower}-model",
123
+ commit_message="Initial model upload",
111
124
  )
112
125
 
113
126
  **Loading a model from the Hub:**
114
127
 
115
- .. code-block:: python
128
+ .. code-block::
116
129
 
117
- from braindecode.models import EEGNetv4
130
+ from braindecode.models import {name}
118
131
 
119
132
  # Load pretrained model
120
- model = EEGNetv4.from_pretrained("username/my-eegnet-model")
133
+ model = {name}.from_pretrained("username/my-{name_lower}-model")
121
134
 
122
135
  The integration automatically handles EEG-specific parameters (n_chans,
123
136
  n_times, sfreq, chs_info, etc.) by saving them in a config file alongside
124
137
  the model weights. This ensures that loaded models are correctly configured
125
138
  for their original data specifications.
126
139
 
127
- .. important::
128
- Currently, only EEG-specific parameters (n_outputs, n_chans, n_times,
129
- input_window_seconds, sfreq, chs_info) are saved to the Hub. Model-specific
130
- parameters (e.g., dropout rates, activation functions, number of filters)
131
- are not preserved and will use their default values when loading from the Hub.
132
-
133
- To use non-default model parameters, specify them explicitly when calling
134
- :func:`from_pretrained()`::
135
-
136
- model = EEGNet.from_pretrained("user/model", dropout=0.3, activation='relu')
137
-
138
- Full parameter serialization will be addressed in a future update.
140
+ All model parameters (both EEG-specific and model-specific such as
141
+ dropout rates, activation functions, number of filters) are automatically
142
+ saved to the Hub and restored when loading.
139
143
  """
140
144
 
141
145
  def __init_subclass__(cls, **kwargs):
146
+ # Append model-specific Hub integration notes to the docstring.
147
+ # This runs after the metaclass, so we concatenate rather than
148
+ # override any existing Notes section in the subclass.
149
+ if cls.__doc__ is not None:
150
+ hub_notes = cls._HUB_NOTES_TEMPLATE.format(
151
+ name=cls.__name__,
152
+ name_lower=cls.__name__.lower(),
153
+ )
154
+ cls.__doc__ = cls.__doc__.rstrip() + "\n" + hub_notes
155
+
142
156
  if not HAS_HF_HUB:
143
157
  super().__init_subclass__(**kwargs)
158
+ track_model_init_kwargs(cls)
144
159
  return
145
160
 
146
161
  base_tags = ["braindecode", cls.__name__]
@@ -157,6 +172,19 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
157
172
  repo_url = kwargs.pop("repo_url", "https://braindecode.org")
158
173
  library_name = kwargs.pop("library_name", "braindecode")
159
174
  license = kwargs.pop("license", "bsd-3-clause")
175
+
176
+ # Register a coder so that type[nn.Module] parameters
177
+ # (e.g. activation=nn.ELU) are serialized as importable
178
+ # strings in config.json and decoded back on load.
179
+ coders = kwargs.pop("coders", None) or {}
180
+ coders.setdefault(
181
+ type,
182
+ (
183
+ lambda t: f"{t.__module__}.{t.__qualname__}",
184
+ lambda data: _IMPORT_ADAPTER.validate_python(data),
185
+ ),
186
+ )
187
+
160
188
  # TODO: model_card_template can be added in the future for custom model cards
161
189
  super().__init_subclass__(
162
190
  tags=tags,
@@ -164,8 +192,10 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
164
192
  repo_url=repo_url,
165
193
  library_name=library_name,
166
194
  license=license,
195
+ coders=coders,
167
196
  **kwargs,
168
197
  )
198
+ track_model_init_kwargs(cls)
169
199
 
170
200
  def __init__(
171
201
  self,
@@ -205,6 +235,17 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
205
235
  self._n_times = n_times # type: ignore[assignment]
206
236
  self._sfreq = sfreq # type: ignore[assignment]
207
237
 
238
+ # Back-fill instance attributes from _hub_mixin_config for any
239
+ # params the subclass didn't store on self. Skip EEG params
240
+ # (stored as self._*) and descriptors (properties).
241
+ for key, val in getattr(self, "_hub_mixin_config", {}).items():
242
+ if key in _EEG_PARAMS:
243
+ continue
244
+ if hasattr(getattr(type(self), key, None), "__get__"):
245
+ continue
246
+ if not hasattr(self, key):
247
+ setattr(self, key, val)
248
+
208
249
  super().__init__()
209
250
 
210
251
  @property
@@ -315,6 +356,76 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
315
356
  raise ValueError(msg) from exc
316
357
  raise exc
317
358
 
359
+ def get_config(self) -> dict:
360
+ """Return a JSON-serializable dict of all ``__init__`` parameters.
361
+
362
+ The returned dictionary can be saved to a JSON file and later
363
+ used with :meth:`from_config` to reconstruct the model (without
364
+ weights). It is also used internally by :meth:`push_to_hub` to
365
+ persist the full model configuration.
366
+
367
+ Returns
368
+ -------
369
+ dict
370
+ All ``__init__`` parameters, JSON-serializable.
371
+ ``type[nn.Module]`` parameters (e.g. ``activation``) are
372
+ encoded as importable dotted-path strings.
373
+
374
+ Examples
375
+ --------
376
+ >>> import json
377
+ >>> from braindecode.models import EEGNet
378
+ >>> model = EEGNet(n_chans=22, n_times=1000, n_outputs=4, F1=16)
379
+ >>> config = model.get_config()
380
+ >>> config["F1"]
381
+ 16
382
+ >>> # Save to disk
383
+ >>> with open("config.json", "w") as f:
384
+ ... json.dump(config, f)
385
+
386
+ .. versionadded:: 1.4
387
+ """
388
+ return build_model_config(self)
389
+
390
+ @classmethod
391
+ def from_config(cls, config: dict) -> "EEGModuleMixin":
392
+ """Create a model instance from a configuration dict.
393
+
394
+ This is the inverse of :meth:`get_config`. Weights are **not**
395
+ loaded -- use :meth:`from_pretrained` for that.
396
+
397
+ Parameters
398
+ ----------
399
+ config : dict
400
+ Configuration dict as returned by :meth:`get_config`.
401
+
402
+ Returns
403
+ -------
404
+ EEGModuleMixin
405
+ A new model instance.
406
+
407
+ Examples
408
+ --------
409
+ >>> import json
410
+ >>> from braindecode.models import EEGNet
411
+ >>> model = EEGNet(n_chans=22, n_times=1000, n_outputs=4, F1=16)
412
+ >>> config = model.get_config()
413
+ >>> # Reconstruct (without weights)
414
+ >>> model2 = EEGNet.from_config(config)
415
+ >>> model2.F1
416
+ 16
417
+ >>> # Or from a JSON file
418
+ >>> with open("config.json") as f:
419
+ ... config = json.load(f)
420
+ >>> model3 = EEGNet.from_config(config)
421
+
422
+ .. versionadded:: 1.4
423
+ """
424
+ config = dict(config) # shallow copy
425
+ config.pop("braindecode_version", None)
426
+ resolve_type_kwargs(cls, config)
427
+ return cls(**config)
428
+
318
429
  mapping: Optional[Dict[str, str]] = None
319
430
 
320
431
  def load_state_dict(self, state_dict, *args, **kwargs):
@@ -419,54 +530,22 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
419
530
 
420
531
  @staticmethod
421
532
  def _serialize_chs_info(chs_info):
422
- """
423
- Serialize MNE channel info to JSON-compatible format.
424
-
425
- Parameters
426
- ----------
427
- chs_info : list of dict or None
428
- Channel information from MNE Info object.
429
-
430
- Returns
431
- -------
432
- list of dict or None
433
- Serialized channel information that can be saved to JSON.
434
- """
533
+ """Serialize MNE channel info (``info["chs"]``) to JSON-compatible dicts."""
435
534
  if chs_info is None:
436
535
  return None
437
-
536
+ _INT_FIELDS = ("kind", "coil_type", "unit")
537
+ _FLOAT_FIELDS = ("cal", "range")
438
538
  serialized = []
439
539
  for ch in chs_info:
440
- # Extract serializable fields from MNE channel info
441
- ch_dict = {
442
- "ch_name": ch.get("ch_name", ""),
443
- }
444
-
445
- # Handle kind field - can be either string or integer
446
- kind_val = ch.get("kind")
447
- if kind_val is not None:
448
- ch_dict["kind"] = (
449
- kind_val if isinstance(kind_val, str) else int(kind_val)
450
- )
451
-
452
- # Add numeric fields with safe conversion
453
- coil_type = ch.get("coil_type")
454
- if coil_type is not None:
455
- ch_dict["coil_type"] = int(coil_type)
456
-
457
- unit = ch.get("unit")
458
- if unit is not None:
459
- ch_dict["unit"] = int(unit)
460
-
461
- cal = ch.get("cal")
462
- if cal is not None:
463
- ch_dict["cal"] = float(cal)
464
-
465
- range_val = ch.get("range")
466
- if range_val is not None:
467
- ch_dict["range"] = float(range_val)
468
-
469
- # Serialize location array if present
540
+ ch_dict = {"ch_name": ch.get("ch_name", "")}
541
+ for key in _INT_FIELDS:
542
+ val = ch.get(key)
543
+ if val is not None:
544
+ ch_dict[key] = val if isinstance(val, str) else int(val)
545
+ for key in _FLOAT_FIELDS:
546
+ val = ch.get(key)
547
+ if val is not None:
548
+ ch_dict[key] = float(val)
470
549
  if "loc" in ch and ch["loc"] is not None:
471
550
  ch_dict["loc"] = (
472
551
  ch["loc"].tolist()
@@ -474,35 +553,19 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
474
553
  else list(ch["loc"])
475
554
  )
476
555
  serialized.append(ch_dict)
477
-
478
556
  return serialized
479
557
 
480
558
  @staticmethod
481
559
  def _deserialize_chs_info(chs_info_dict):
482
- """
483
- Deserialize channel info from JSON-compatible format to MNE-like structure.
484
-
485
- Parameters
486
- ----------
487
- chs_info_dict : list of dict or None
488
- Serialized channel information.
489
-
490
- Returns
491
- -------
492
- list of dict or None
493
- Deserialized channel information compatible with MNE.
494
- """
560
+ """Deserialize JSON channel dicts back to MNE-compatible format."""
495
561
  if chs_info_dict is None:
496
562
  return None
497
-
498
563
  deserialized = []
499
564
  for ch_dict in chs_info_dict:
500
565
  ch = ch_dict.copy()
501
- # Convert location back to numpy array if present
502
566
  if "loc" in ch and ch["loc"] is not None:
503
567
  ch["loc"] = np.array(ch["loc"])
504
568
  deserialized.append(ch)
505
-
506
569
  return deserialized
507
570
 
508
571
  def _save_pretrained(self, save_directory):
@@ -522,16 +585,8 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
522
585
 
523
586
  save_directory = Path(save_directory)
524
587
 
525
- # Collect EEG-specific configuration
526
- config = {
527
- "n_outputs": self._n_outputs,
528
- "n_chans": self._n_chans,
529
- "n_times": self._n_times,
530
- "input_window_seconds": self._input_window_seconds,
531
- "sfreq": self._sfreq,
532
- "chs_info": self._serialize_chs_info(self._chs_info),
533
- "braindecode_version": __version__,
534
- }
588
+ config = build_model_config(self)
589
+ config["braindecode_version"] = __version__
535
590
 
536
591
  # Save to config.json
537
592
  config_path = save_directory / "config.json"
@@ -570,6 +625,7 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
570
625
  **model_kwargs,
571
626
  ):
572
627
  model_kwargs.pop("braindecode_version", None)
628
+ resolve_type_kwargs(cls, model_kwargs)
573
629
  return super()._from_pretrained( # type: ignore
574
630
  model_id=model_id,
575
631
  revision=revision,
@@ -207,11 +207,25 @@ class CBraMod(EEGModuleMixin, nn.Module):
207
207
 
208
208
  self._weights_init()
209
209
 
210
- self.final_layer = (
211
- nn.Identity()
212
- if return_encoder_output
213
- else nn.Sequential(nn.Flatten(), nn.LazyLinear(self.n_outputs))
214
- )
210
+ try:
211
+ n_times = self.n_times
212
+ n_chans = self.n_chans
213
+ except ValueError:
214
+ n_times = None
215
+ n_chans = None
216
+
217
+ if return_encoder_output:
218
+ self.final_layer = nn.Identity()
219
+ elif n_times is not None and n_chans is not None:
220
+ n_patch = n_times // patch_size
221
+ flat_dim = n_chans * n_patch * emb_dim
222
+ self.final_layer = nn.Sequential(
223
+ nn.Flatten(), nn.Linear(flat_dim, self.n_outputs)
224
+ )
225
+ else:
226
+ self.final_layer = nn.Sequential(
227
+ nn.Flatten(), nn.LazyLinear(self.n_outputs)
228
+ )
215
229
 
216
230
  def _weights_init(self):
217
231
  for m in self.modules():
@@ -4,13 +4,16 @@ from types import UnionType
4
4
  from typing import Annotated, Any, Literal, Union, get_args, get_origin
5
5
 
6
6
  import numpy as np
7
- from mne.utils import _soft_import
7
+ import pydantic
8
8
  from typing_extensions import TypedDict
9
9
 
10
10
  from braindecode.models.base import EEGModuleMixin
11
- from braindecode.models.util import SigArgName, models_dict, models_mandatory_parameters
12
-
13
- pydantic = _soft_import(name="pydantic", purpose="model configuration", strict=False)
11
+ from braindecode.models.util import (
12
+ SigArgName,
13
+ _init_models_dict,
14
+ models_dict,
15
+ models_mandatory_parameters,
16
+ )
14
17
 
15
18
  try:
16
19
  from numpydantic import NDArray, Shape
@@ -57,8 +60,12 @@ SIGNAL_ARGS_TYPES = {
57
60
  }
58
61
 
59
62
 
60
- class BaseBraindecodeModelConfig(pydantic.BaseModel): # type: ignore
63
+ class BaseBraindecodeModelConfig(pydantic.BaseModel):
64
+ """Base class for braindecode model pydantic configs."""
65
+
61
66
  def create_instance(self) -> EEGModuleMixin:
67
+ if self.model_name_ not in models_dict:
68
+ _init_models_dict()
62
69
  model_cls = models_dict[self.model_name_]
63
70
  kwargs = self.model_dump(mode="python", exclude={"model_name_"})
64
71
  if kwargs.get("n_chans") is not None and kwargs.get("chs_info") is not None:
@@ -90,13 +97,6 @@ def make_model_config(
90
97
  type
91
98
  A pydantic BaseModel subclass representing the model config.
92
99
  """
93
- if not pydantic:
94
- raise ImportError(
95
- "pydantic is required to use make_model_config. "
96
- "Please install braindecode[typing]."
97
- )
98
-
99
- # ironically, we need to ignore the type here to have the soft dependency.
100
100
 
101
101
  @pydantic.model_validator(mode="before")
102
102
  def validate_signal_params(cls, data: Any):
@@ -207,23 +207,23 @@ def make_model_config(
207
207
  # and define __all__ based on generated classes
208
208
  __all__ = ["make_model_config"]
209
209
 
210
- if not pydantic:
211
- pass
212
- else:
213
- models_configs: list[type[BaseBraindecodeModelConfig]] = []
214
- for model_name, req, _ in models_mandatory_parameters:
215
- model_cls = models_dict[model_name]
216
- model_cfg = make_model_config(model_cls, req)
217
- globals()[model_cfg.__name__] = model_cfg
218
- __all__.append(model_cfg.__name__)
219
- models_configs.append(model_cfg)
220
-
221
- BraindecodeModelConfig = Annotated[ # type: ignore
222
- Union[tuple(models_configs)],
223
- pydantic.Field(
224
- discriminator="model_name_", description="Braindecode model configuration"
225
- ),
226
- ]
210
+ if not models_dict:
211
+ _init_models_dict()
212
+
213
+ models_configs: list[type[BaseBraindecodeModelConfig]] = []
214
+ for model_name, req, _ in models_mandatory_parameters:
215
+ model_cls = models_dict[model_name]
216
+ model_cfg = make_model_config(model_cls, req)
217
+ globals()[model_cfg.__name__] = model_cfg
218
+ __all__.append(model_cfg.__name__)
219
+ models_configs.append(model_cfg)
220
+
221
+ BraindecodeModelConfig = Annotated[ # type: ignore[valid-type]
222
+ Union[tuple(models_configs)],
223
+ pydantic.Field(
224
+ discriminator="model_name_", description="Braindecode model configuration"
225
+ ),
226
+ ]
227
227
 
228
228
  # # Example usage:
229
229
  #
@@ -112,7 +112,7 @@ class EEGSym(EEGModuleMixin, nn.Module):
112
112
  corresponding to different frequency characteristics.
113
113
 
114
114
  Notes
115
- ----------
115
+ -----
116
116
  * EEGSym achieved competitive accuracies across five large MI datasets [eegsym2022]_.
117
117
  * The model maintained high accuracy using a reduced set of electrodes (8 or 16 channels)
118
118
  [eegsym2022]_.
@@ -7,6 +7,7 @@ Retrieved from https://openreview.net/forum?id=uazfjnFL0G
7
7
 
8
8
  Original Authors: Berkay Döner, Thorir Mar Ingolfsson
9
9
  Braindecode Adaptation: Bruno Aristimunha
10
+ Contributions: Sarthak Tayal <sarthaktayal2@gmail.com>
10
11
 
11
12
  the LICENSE Of this file is APACHE-2.0.
12
13
  """
@@ -82,11 +83,8 @@ class LUNA(EEGModuleMixin, nn.Module):
82
83
 
83
84
  # Load pre-trained encoder weights
84
85
  state_dict = load_file(model_path)
85
- # Apply key mapping for pretrained weights
86
- mapping = model.mapping.copy()
87
- mapping["cross_attn.temparature"] = "cross_attn.temperature"
88
- mapped_state_dict = {mapping.get(k, k): v for k, v in state_dict.items()}
89
- model.load_state_dict(mapped_state_dict, strict=False)
86
+ # load_state_dict applies model.mapping automatically
87
+ model.load_state_dict(state_dict, strict=False)
90
88
 
91
89
  To push your own trained model to the Hub:
92
90
 
@@ -170,6 +168,7 @@ class LUNA(EEGModuleMixin, nn.Module):
170
168
  "channel_location_embedder.0.fc2.bias": "channel_location_embedder.fc2.bias",
171
169
  "channel_location_embedder.0.norm.weight": "channel_location_embedder.norm.weight",
172
170
  "channel_location_embedder.0.norm.bias": "channel_location_embedder.norm.bias",
171
+ "cross_attn.temparature": "cross_attn.temperature", # typo in pretrained weights
173
172
  }
174
173
 
175
174
  # Model parameters
@@ -315,8 +314,10 @@ class LUNA(EEGModuleMixin, nn.Module):
315
314
  channel_locations_emb = self.channel_location_embedder(channel_locations)
316
315
 
317
316
  x_tokenized = rearrange(x_masked, "B (C t) D -> (B t) C D", C=num_channels)
318
- channel_locations_emb = channel_locations_emb.repeat(
319
- num_patches_per_channel, 1, 1
317
+ channel_locations_emb = (
318
+ channel_locations_emb.repeat_interleave( # was repeat(), wrong dim ordering
319
+ num_patches_per_channel, dim=0
320
+ )
320
321
  )
321
322
  x_tokenized = x_tokenized + channel_locations_emb
322
323
 
@@ -4,6 +4,7 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import math
7
+ import warnings
7
8
  from typing import Optional, Tuple
8
9
 
9
10
  import torch
@@ -496,9 +497,10 @@ class _TransformerEncoderLayer(nn.Module):
496
497
 
497
498
  if dim_feedforward is None:
498
499
  dim_feedforward = 4 * embed_dim
499
- # note: preserve the original behaviour (print) from the provided code
500
- print(
501
- "dim_feedforward is set to 4*embed_dim, the default in Vaswani et al. (Attention is all you need)"
500
+ warnings.warn(
501
+ "dim_feedforward is set to 4*embed_dim, the default in "
502
+ "Vaswani et al. (Attention is all you need)",
503
+ stacklevel=2,
502
504
  )
503
505
 
504
506
  self.layer_norm_att = _LayerNorm(embed_dim, bias=bias)
@@ -16,7 +16,16 @@ import torch
16
16
  import torch.nn.functional as F
17
17
  from einops import rearrange
18
18
  from torch import nn
19
- from torch.nn.attention import SDPBackend, sdpa_kernel
19
+
20
+ # Safe import for older PyTorch versions (Support for Intel-based Macs)
21
+ try:
22
+ from torch.nn.attention import SDPBackend, sdpa_kernel
23
+
24
+ HAS_SDPA = True
25
+ except ImportError:
26
+ HAS_SDPA = False
27
+ SDPBackend = None
28
+ sdpa_kernel = None
20
29
 
21
30
  from braindecode.models.base import EEGModuleMixin
22
31
 
@@ -530,11 +539,22 @@ class FeedForward(nn.Module):
530
539
 
531
540
 
532
541
  class ClassicalAttention(nn.Module):
533
- def __init__(self, heads: int, use_sdpa: bool = True):
542
+ def __init__(self, heads: int, use_sdpa: bool | None = None):
534
543
  super().__init__()
535
- self.use_sdpa = use_sdpa
536
544
  self.heads = heads
537
545
 
546
+ if use_sdpa is None:
547
+ self.use_sdpa = HAS_SDPA
548
+ elif use_sdpa is True and not HAS_SDPA:
549
+ logger.warning(
550
+ "SDPA (Scaled Dot Product Attention) was requested, but it is not "
551
+ "available in your current PyTorch version. Falling back to naive "
552
+ "implementation. Please upgrade to PyTorch >= 2.2 for SDPA support."
553
+ )
554
+ self.use_sdpa = False
555
+ else:
556
+ self.use_sdpa = use_sdpa
557
+
538
558
  def forward(self, qkv: torch.Tensor) -> torch.Tensor:
539
559
  # Split concatenated QKV into separate tensors
540
560
  # qkv shape: (batch, seq_len, 3 * heads * head_dim)