braindecode 1.6.1.dev1052__tar.gz → 1.6.1.dev1053__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 (172) hide show
  1. {braindecode-1.6.1.dev1052/braindecode.egg-info → braindecode-1.6.1.dev1053}/PKG-INFO +1 -1
  2. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/eegneuralnet.py +41 -0
  3. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/__init__.py +2 -0
  4. braindecode-1.6.1.dev1053/braindecode/models/eegdino.py +533 -0
  5. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/summary.csv +1 -0
  6. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/util.py +1 -0
  7. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/__init__.py +2 -1
  8. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/blocks.py +82 -0
  9. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/preprocessing/mne_preprocess.py +28 -1
  10. braindecode-1.6.1.dev1053/braindecode/version.py +1 -0
  11. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053/braindecode.egg-info}/PKG-INFO +1 -1
  12. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode.egg-info/SOURCES.txt +1 -0
  13. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/api.rst +2 -0
  14. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/whats_new.rst +25 -0
  15. braindecode-1.6.1.dev1052/braindecode/version.py +0 -1
  16. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/LICENSE.txt +0 -0
  17. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/MANIFEST.in +0 -0
  18. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/NOTICE.txt +0 -0
  19. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/README.rst +0 -0
  20. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/__init__.py +0 -0
  21. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/augmentation/__init__.py +0 -0
  22. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/augmentation/base.py +0 -0
  23. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/augmentation/functional.py +0 -0
  24. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/augmentation/transforms.py +0 -0
  25. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/classifier.py +0 -0
  26. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/__init__.py +0 -0
  27. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/base.py +0 -0
  28. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bbci.py +0 -0
  29. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bcicomp.py +0 -0
  30. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bids/__init__.py +0 -0
  31. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bids/datasets.py +0 -0
  32. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bids/format.py +0 -0
  33. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bids/hub.py +0 -0
  34. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bids/hub_format.py +0 -0
  35. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bids/hub_io.py +0 -0
  36. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bids/hub_validation.py +0 -0
  37. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/bids/iterable.py +0 -0
  38. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/chb_mit.py +0 -0
  39. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/mne.py +0 -0
  40. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/moabb.py +0 -0
  41. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/nmt.py +0 -0
  42. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/registry.py +0 -0
  43. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/siena.py +0 -0
  44. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  45. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/sleep_physionet.py +0 -0
  46. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/tuh.py +0 -0
  47. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/utils.py +0 -0
  48. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datasets/xy.py +0 -0
  49. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datautil/__init__.py +0 -0
  50. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datautil/channel_utils.py +0 -0
  51. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datautil/hub_formats.py +0 -0
  52. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datautil/serialization.py +0 -0
  53. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/datautil/util.py +0 -0
  54. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/functional/__init__.py +0 -0
  55. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/functional/functions.py +0 -0
  56. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/functional/initialization.py +0 -0
  57. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/atcnet.py +0 -0
  58. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/attentionbasenet.py +0 -0
  59. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/attn_sleep.py +0 -0
  60. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/base.py +0 -0
  61. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/bendr.py +0 -0
  62. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/biot.py +0 -0
  63. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/brainmodule.py +0 -0
  64. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/cbramod.py +0 -0
  65. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/codebrain.py +0 -0
  66. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/config.py +0 -0
  67. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/contrawr.py +0 -0
  68. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/ctnet.py +0 -0
  69. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/deep4.py +0 -0
  70. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/deepsleepnet.py +0 -0
  71. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/dgcnn.py +0 -0
  72. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eegconformer.py +0 -0
  73. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eeginception_erp.py +0 -0
  74. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eeginception_mi.py +0 -0
  75. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eegitnet.py +0 -0
  76. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eegminer.py +0 -0
  77. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eegnet.py +0 -0
  78. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eegnex.py +0 -0
  79. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eegpt.py +0 -0
  80. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eegsimpleconv.py +0 -0
  81. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eegsym.py +0 -0
  82. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/eegtcnet.py +0 -0
  83. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/emg2qwerty.py +0 -0
  84. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/fbcnet.py +0 -0
  85. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/fblightconvnet.py +0 -0
  86. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/fbmsnet.py +0 -0
  87. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/hybrid.py +0 -0
  88. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/ifnet.py +0 -0
  89. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/interpolated.py +0 -0
  90. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/labram.py +0 -0
  91. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/luna.py +0 -0
  92. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/medformer.py +0 -0
  93. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/meta_neuromotor.py +0 -0
  94. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/msvtnet.py +0 -0
  95. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/patchedtransformer.py +0 -0
  96. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/reve.py +0 -0
  97. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/sccnet.py +0 -0
  98. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/shallow_fbcsp.py +0 -0
  99. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/signal_jepa.py +0 -0
  100. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/sinc_shallow.py +0 -0
  101. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
  102. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
  103. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/sparcnet.py +0 -0
  104. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/sstdpn.py +0 -0
  105. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/syncnet.py +0 -0
  106. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/tcn.py +0 -0
  107. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/tidnet.py +0 -0
  108. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/tsinception.py +0 -0
  109. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/models/usleep.py +0 -0
  110. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/activation.py +0 -0
  111. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/attention.py +0 -0
  112. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/convolution.py +0 -0
  113. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/filter.py +0 -0
  114. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/interpolation.py +0 -0
  115. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/layers.py +0 -0
  116. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/linear.py +0 -0
  117. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/parametrization.py +0 -0
  118. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/stats.py +0 -0
  119. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/util.py +0 -0
  120. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/modules/wrapper.py +0 -0
  121. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/preprocessing/__init__.py +0 -0
  122. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
  123. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/preprocessing/preprocess.py +0 -0
  124. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/preprocessing/util.py +0 -0
  125. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/preprocessing/windowers.py +0 -0
  126. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/regressor.py +0 -0
  127. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/samplers/__init__.py +0 -0
  128. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/samplers/base.py +0 -0
  129. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/samplers/ssl.py +0 -0
  130. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/training/__init__.py +0 -0
  131. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/training/callbacks.py +0 -0
  132. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/training/losses.py +0 -0
  133. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/training/scoring.py +0 -0
  134. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/util.py +0 -0
  135. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/visualization/__init__.py +0 -0
  136. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/visualization/attribution.py +0 -0
  137. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/visualization/confusion_matrices.py +0 -0
  138. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/visualization/frequency.py +0 -0
  139. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/visualization/metrics.py +0 -0
  140. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/visualization/sanity.py +0 -0
  141. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode/visualization/topology.py +0 -0
  142. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode.egg-info/dependency_links.txt +0 -0
  143. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode.egg-info/requires.txt +0 -0
  144. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/braindecode.egg-info/top_level.txt +0 -0
  145. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/Makefile +0 -0
  146. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/_templates/autosummary/class.rst +0 -0
  147. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  148. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/_templates/autosummary/function.rst +0 -0
  149. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  150. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/cite.rst +0 -0
  151. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/conf.py +0 -0
  152. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/help.rst +0 -0
  153. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/index.rst +0 -0
  154. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/install/install.rst +0 -0
  155. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/install/install_pip.rst +0 -0
  156. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/install/install_source.rst +0 -0
  157. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/categorization/attention.rst +0 -0
  158. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/categorization/channel.rst +0 -0
  159. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/categorization/convolution.rst +0 -0
  160. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/categorization/filterbank.rst +0 -0
  161. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/categorization/gnn.rst +0 -0
  162. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/categorization/interpretable.rst +0 -0
  163. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/categorization/lbm.rst +0 -0
  164. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/categorization/recurrent.rst +0 -0
  165. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/categorization/spd.rst +0 -0
  166. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/models.rst +0 -0
  167. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/models_categorization.rst +0 -0
  168. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/models_table.rst +0 -0
  169. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/models/models_visualization.rst +0 -0
  170. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/docs/sg_execution_times.rst +0 -0
  171. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/pyproject.toml +0 -0
  172. {braindecode-1.6.1.dev1052 → braindecode-1.6.1.dev1053}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.6.1.dev1052
3
+ Version: 1.6.1.dev1053
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>
@@ -7,6 +7,7 @@
7
7
  import abc
8
8
  import inspect
9
9
  import logging
10
+ import warnings
10
11
  from typing import Literal
11
12
 
12
13
  import mne
@@ -41,6 +42,7 @@ def _get_model(model: str):
41
42
 
42
43
  class _EEGNeuralNet(NeuralNet, abc.ABC):
43
44
  signal_args_set_ = False
45
+ _warned_drop_last_ = False
44
46
 
45
47
  @property
46
48
  def log(self):
@@ -267,6 +269,44 @@ class _EEGNeuralNet(NeuralNet, abc.ABC):
267
269
  X = X.get_data(units="uV")
268
270
  return super().get_dataset(X, y)
269
271
 
272
+ def get_iterator(self, dataset, training=False):
273
+ iterator = super().get_iterator(dataset, training=training)
274
+ # Warn (once per fit) if drop_last would discard every training batch,
275
+ # e.g. a training set smaller than batch_size. This silently skips
276
+ # training without raising any error, which is hard to diagnose.
277
+ if training and not self._warned_drop_last_:
278
+ self._warn_if_training_batches_dropped(iterator)
279
+ return iterator
280
+
281
+ def _warn_if_training_batches_dropped(self, loader):
282
+ """Warn if ``drop_last`` discards all training batches.
283
+
284
+ With ``iterator_train__drop_last=True`` (the braindecode default), a
285
+ training set smaller than ``batch_size`` yields zero batches, so no
286
+ forward/backward pass runs and the model is left untrained without any
287
+ error. We inspect the resolved training ``DataLoader`` (after
288
+ ``train_split`` has been applied), so the count reflects the actual
289
+ number of training examples.
290
+ """
291
+ self._warned_drop_last_ = True
292
+ if not getattr(loader, "drop_last", False):
293
+ return
294
+ batch_size = getattr(loader, "batch_size", None)
295
+ try:
296
+ n_train = len(loader.dataset)
297
+ except TypeError:
298
+ return # e.g. an IterableDataset whose length is unknown
299
+ if not batch_size or n_train == 0 or n_train >= batch_size:
300
+ return
301
+ warnings.warn(
302
+ f"The training set has {n_train} example(s), which is smaller than "
303
+ f"batch_size={batch_size}. With iterator_train__drop_last=True "
304
+ f"(the braindecode default), all training batches are dropped and "
305
+ f"the model is not trained. Reduce batch_size to at most {n_train}, "
306
+ f"or pass iterator_train__drop_last=False.",
307
+ UserWarning,
308
+ )
309
+
270
310
  def partial_fit(self, X, y=None, classes=None, **fit_params):
271
311
  """Fit the module.
272
312
 
@@ -366,6 +406,7 @@ class _EEGNeuralNet(NeuralNet, abc.ABC):
366
406
  the module and to the ``self.train_split`` call.
367
407
  """
368
408
  # this needs to be executed before the net is initialized:
409
+ self._warned_drop_last_ = False # re-check on every fresh fit
369
410
  if not self.signal_args_set_:
370
411
  self._set_signal_args(X, y, classes=None)
371
412
  self.signal_args_set_ = True
@@ -15,6 +15,7 @@ from .deep4 import Deep4Net
15
15
  from .deepsleepnet import DeepSleepNet
16
16
  from .dgcnn import DGCNN
17
17
  from .eegconformer import EEGConformer
18
+ from .eegdino import EEGDINO
18
19
  from .eeginception_erp import EEGInceptionERP
19
20
  from .eeginception_mi import EEGInceptionMI
20
21
  from .eegitnet import EEGITNet
@@ -84,6 +85,7 @@ __all__ = [
84
85
  "DeepSleepNet",
85
86
  "BrainModule",
86
87
  "EEGConformer",
88
+ "EEGDINO",
87
89
  "EEGPT",
88
90
  "InterpolatedEEGPT",
89
91
  "EEGInceptionERP",
@@ -0,0 +1,533 @@
1
+ # Authors: Bruno Aristimunha <b.aristimunha@gmail.com>
2
+ #
3
+ # Code adapted from https://huggingface.co/eegdino/EEG-DINO
4
+ #
5
+ # License: BSD (3-clause)
6
+ from __future__ import annotations
7
+
8
+ from typing import Sequence
9
+ from warnings import warn
10
+
11
+ import torch
12
+ import torch.nn.functional as F
13
+ from torch import nn
14
+
15
+ from braindecode.models.base import EEGModuleMixin
16
+ from braindecode.modules import DropPath, PatchTokenizer
17
+
18
+
19
+ class EEGDINO(EEGModuleMixin, nn.Module):
20
+ r"""EEG-DINO from Wang et al. (2025) [eegdino]_.
21
+
22
+ :bdg-danger:`Foundation Model` :bdg-info:`Attention/Transformer`
23
+
24
+ .. figure:: https://braindecode.org/dev/_static/model/eegdino.png
25
+ :align: center
26
+ :alt: EEG-DINO Architecture
27
+
28
+ EEG-DINO is a ViT-style EEG foundation model pre-trained with DINO-v2
29
+ hierarchical self-distillation. Only the *encoder* (plus a classification
30
+ head) is integrated here; the self-distillation pre-training is out of scope
31
+ and it was not released by the authors.
32
+
33
+ The forward path is, end to end:
34
+
35
+ ``(batch, n_chans, n_times)`` → patchify → time-frequency embedding →
36
+ decoupled positional embedding → transformer encoder (+ global token) →
37
+ pooling head → ``(batch, n_outputs)``.
38
+
39
+ .. rubric:: Step 1 -- Patchify
40
+
41
+ The signal is split along time into non-overlapping patches of
42
+ ``patch_size`` samples (200 samples = 1 second at 200 Hz), giving one
43
+ *token* per (channel, patch). Inputs whose length is not a multiple of
44
+ ``patch_size`` are zero-padded with a warning.
45
+ This is analogous to the patchification in ViT, BEiT (LaBraM), and CBraMod.
46
+
47
+ .. rubric:: Step 2 -- Time-Frequency Embedding (TFE)
48
+
49
+ Each patch is embedded by summing two branches, exactly as in
50
+ :class:`~braindecode.models.CBraMod` (the EEG-DINO paper reuses CBraMod's
51
+ TFE): a **time-domain** branch of stacked grouped convolutions
52
+ (``proj_in``), and a **frequency-domain** branch projecting the magnitude
53
+ of the patch's real FFT (``spectral_proj``). The embedding dimension
54
+ ``emb_dim`` is therefore *derived* from the convolution configuration, not
55
+ set independently.
56
+
57
+ .. rubric:: Step 3 -- Decoupled Positional Embedding (DPE)
58
+
59
+ Where CBraMod uses a single convolutional positional encoding (ACPE),
60
+ EEG-DINO *decouples* space and time and adds both to every token: a
61
+ learnable **one-hot channel embedding** over a fixed vocabulary of
62
+ ``n_channel_embeddings`` slots (input channel ``i`` maps to slot ``i``, so a
63
+ 19-slot embedding serves any ``n_chans <= 19``) and a **depthwise temporal
64
+ convolution** over the patch axis (``time_encoding``).
65
+
66
+ .. rubric:: Step 4 -- Transformer Encoder & Global Token
67
+
68
+ Tokens are flattened to a single sequence and processed by ``n_layer``
69
+ pre-norm transformer blocks (BEiT-style attention with separate query/value
70
+ biases). A learnable ``global_tokens`` summary token is prepended after the
71
+ ``global_token_layer``-th block and attends jointly with the patch tokens.
72
+
73
+ .. rubric:: Step 5 -- Classification Head (``final_layer``)
74
+
75
+ Following EEG-DINO's finetuning head, the patch tokens (global token
76
+ excluded) pass through a per-token projection, are mean-pooled over channels
77
+ then over patches (with an intermediate projection), and a three-layer MLP
78
+ (``final_layer``) maps them to ``n_outputs``
79
+ (:class:`~braindecode.EEGClassifier` applies the softmax). With
80
+ ``return_encoder_output=True`` the mean-pooled encoder representation is
81
+ returned instead (linear probing).
82
+
83
+ .. warning::
84
+ EEG-DINO was pre-trained on EEG in microvolts scaled by ``1 / 100``. The
85
+ model does **not** rescale its input, so for the released weights to
86
+ behave as intended provide data on the same scale (e.g. microvolts
87
+ divided by 100) or normalize your signals consistently.
88
+
89
+ .. important::
90
+ **Pre-trained Weights Available**
91
+
92
+ Small and Medium encoders converted from the released checkpoints are
93
+ hosted on the Hugging Face Hub, one repository per size. Only the encoder
94
+ is pretrained; the classification head is randomly initialized, so
95
+ fine-tune or linear-probe before use::
96
+
97
+ from braindecode.models import EEGDINO
98
+
99
+ model = EEGDINO.from_pretrained(
100
+ "braindecode/eegdino-small-pretrained", # or -medium-pretrained
101
+ n_outputs=6,
102
+ n_chans=19,
103
+ sfreq=200,
104
+ )
105
+
106
+ The Small/Medium/Large architectures are also available in
107
+ :data:`EEGDINO_CONFIGS`. Requires ``braindecode[hub]``.
108
+
109
+ .. versionadded:: 1.6.1
110
+
111
+ Parameters
112
+ ----------
113
+ patch_size : int, default=200
114
+ Temporal patch size in samples (200 = 1 second at 200 Hz). Fixed at 200
115
+ for the released weights (the FFT branch uses ``patch_size // 2 + 1`` bins).
116
+ n_layer : int, default=12
117
+ Number of transformer encoder layers.
118
+ nhead : int, default=8
119
+ Number of attention heads.
120
+ dim_feedforward : int, default=512
121
+ Hidden size of the transformer feed-forward block.
122
+ channels_kernel_stride_padding_norm : sequence of tuple, optional
123
+ Configuration of the time-domain convolutions in the patch embedding,
124
+ as ``(out_channels, kernel, stride, padding, (groups, group_channels))``
125
+ per layer. The embedding dimension is derived from this (see
126
+ :class:`~braindecode.models.CBraMod`). Default is the EEG-DINO-Small /
127
+ CBraMod configuration.
128
+ n_channel_embeddings : int, default=19
129
+ Size of the one-hot channel-embedding vocabulary. ``n_chans`` must not
130
+ exceed it; the first ``n_chans`` slots are used. Default 19 (the released
131
+ montage), so the pretrained weights load for any ``n_chans <= 19``.
132
+ n_global_tokens : int, default=1
133
+ Number of learnable global summary tokens.
134
+ global_token_layer : int, default=1
135
+ 1-based index of the encoder layer after which the global tokens are
136
+ inserted.
137
+ activation : type[nn.Module], default=nn.GELU
138
+ Activation function used in the transformer feed-forward blocks and the
139
+ classification head.
140
+ patch_activation : type[nn.Module], default=nn.GELU
141
+ Activation function used in the patch-embedding convolutions. Defaults to
142
+ ``nn.GELU`` to match the released pretrained weights.
143
+ drop_prob : float, default=0.1
144
+ Dropout / stochastic-depth probability in the encoder.
145
+ return_features : bool, default=False
146
+ If True, ``forward`` returns ``{"features", "cls_token"}``.
147
+ return_encoder_output : bool, default=False
148
+ If True, ``final_layer`` is :class:`~torch.nn.Identity` and ``forward``
149
+ returns the pooled encoder representation (linear probing).
150
+
151
+ References
152
+ ----------
153
+ .. [eegdino] Wang, X., Liu, X., Liu, X., Si, Q., Xu, Z., Li, Y., & Zhen, X.
154
+ (2025). EEG-DINO: Learning EEG Foundation Models via Hierarchical
155
+ Self-Distillation. In Medical Image Computing and Computer Assisted
156
+ Intervention (MICCAI 2025).
157
+ """
158
+
159
+ def __init__(
160
+ self,
161
+ n_outputs=None,
162
+ n_chans=None,
163
+ chs_info=None,
164
+ n_times=None,
165
+ input_window_seconds=None,
166
+ sfreq=None,
167
+ patch_size: int = 200,
168
+ n_layer: int = 12,
169
+ nhead: int = 8,
170
+ dim_feedforward: int = 512,
171
+ channels_kernel_stride_padding_norm: Sequence[
172
+ tuple[int, int, int, int, tuple[int, int]]
173
+ ] = (
174
+ (25, 49, 25, 24, (5, 25)),
175
+ (25, 3, 1, 1, (5, 25)),
176
+ (25, 3, 1, 1, (5, 25)),
177
+ ),
178
+ n_channel_embeddings: int = 19,
179
+ n_global_tokens: int = 1,
180
+ global_token_layer: int = 1,
181
+ activation: type[nn.Module] = nn.GELU,
182
+ patch_activation: type[nn.Module] = nn.GELU,
183
+ drop_prob: float = 0.1,
184
+ return_features: bool = False,
185
+ return_encoder_output: bool = False,
186
+ ):
187
+ super().__init__(
188
+ n_outputs=n_outputs,
189
+ n_chans=n_chans,
190
+ chs_info=chs_info,
191
+ n_times=n_times,
192
+ input_window_seconds=input_window_seconds,
193
+ sfreq=sfreq,
194
+ )
195
+ del n_outputs, n_chans, chs_info, n_times, input_window_seconds, sfreq
196
+
197
+ if not 1 <= global_token_layer <= n_layer:
198
+ raise ValueError(
199
+ f"global_token_layer must be in [1, n_layer={n_layer}], "
200
+ f"got {global_token_layer}."
201
+ )
202
+ if n_global_tokens < 1:
203
+ raise ValueError(f"n_global_tokens must be >= 1, got {n_global_tokens}.")
204
+ if self.n_chans > n_channel_embeddings:
205
+ raise ValueError(
206
+ f"n_chans ({self.n_chans}) must not exceed n_channel_embeddings "
207
+ f"({n_channel_embeddings}); the released weights use 19."
208
+ )
209
+
210
+ if self._sfreq is not None and self.sfreq != 200:
211
+ warn(
212
+ f"EEG-DINO was trained at 200 Hz but sfreq={self.sfreq}. Inputs are "
213
+ "not resampled internally; results may be unreliable.",
214
+ UserWarning,
215
+ )
216
+
217
+ self.tokenizer = PatchTokenizer(
218
+ patch_size, n_times=self.n_times, learnable=False
219
+ )
220
+ self.n_global_tokens = n_global_tokens
221
+ self.global_token_layer = global_token_layer
222
+ self.activation = activation
223
+ self.return_features = return_features
224
+ self.return_encoder_output = return_encoder_output
225
+
226
+ self.patch_embedding = _PatchEmbedding(
227
+ patch_size=patch_size,
228
+ channels_kernel_stride_padding_norm=channels_kernel_stride_padding_norm,
229
+ n_channel_embeddings=n_channel_embeddings,
230
+ activation=patch_activation,
231
+ drop_prob=drop_prob,
232
+ )
233
+ self.emb_dim = self.patch_embedding.emb_dim
234
+
235
+ self.encoder_layers = nn.ModuleList(
236
+ _TransformerEncoderLayer(
237
+ self.emb_dim, nhead, dim_feedforward, activation, drop_prob
238
+ )
239
+ for _ in range(n_layer)
240
+ )
241
+ self.global_tokens = nn.Parameter(torch.zeros(1, n_global_tokens, self.emb_dim))
242
+
243
+ self.final_layer = self._make_head()
244
+
245
+ self._init_weights()
246
+
247
+ def _init_weights(self):
248
+ nn.init.trunc_normal_(self.global_tokens, std=0.02)
249
+
250
+ def _make_head(self):
251
+ if self.return_encoder_output:
252
+ return nn.Identity() # ``n_outputs`` not needed for linear probing
253
+ return _ClassificationHead(self.emb_dim, self.n_outputs, self.activation)
254
+
255
+ def reset_head(self, n_outputs):
256
+ """Replace ``final_layer`` with a fresh classification head for ``n_outputs``.
257
+
258
+ Asking for a head implies a classification model, so this also clears
259
+ ``return_encoder_output`` (a linear-probe model thereby gains a real head),
260
+ mirroring :meth:`braindecode.models.CBraMod.reset_head`.
261
+ """
262
+ self._n_outputs = n_outputs
263
+ self.return_encoder_output = False
264
+ self.final_layer = self._make_head()
265
+
266
+ def forward(self, x, return_features: bool | None = None):
267
+ """Forward pass.
268
+
269
+ Parameters
270
+ ----------
271
+ x : torch.Tensor
272
+ Input of shape ``(batch, n_chans, n_times)``. The model does not
273
+ rescale ``x`` (see the amplitude-scale warning in the class docstring).
274
+ return_features : bool, optional
275
+ Overrides ``self.return_features`` for this call.
276
+
277
+ Returns
278
+ -------
279
+ torch.Tensor or dict
280
+ Logits ``(batch, n_outputs)``; or, with features,
281
+ ``{"features": (batch, n_chans * n_patches, emb_dim),
282
+ "cls_token": (batch, emb_dim)}``.
283
+ """
284
+ if return_features is None:
285
+ return_features = self.return_features
286
+ x = self.tokenizer(x) # (batch, n_chans, n_patches, patch_size)
287
+
288
+ patch_emb = self.patch_embedding(x) # (batch, n_chans, n_patches, emb_dim)
289
+ n_chans = patch_emb.shape[1]
290
+ tokens = patch_emb.flatten(1, 2) # (batch, chans * patches, emb)
291
+
292
+ global_tokens = self.global_tokens.expand(tokens.shape[0], -1, -1)
293
+ for i, layer in enumerate(self.encoder_layers):
294
+ tokens = layer(tokens)
295
+ if i + 1 == self.global_token_layer:
296
+ tokens = torch.cat([global_tokens, tokens], dim=1)
297
+
298
+ global_out = tokens[:, : self.n_global_tokens]
299
+ patch_out = tokens[:, self.n_global_tokens :]
300
+
301
+ if return_features:
302
+ return {"features": patch_out, "cls_token": global_out.mean(dim=1)}
303
+
304
+ if self.return_encoder_output:
305
+ return patch_out.mean(dim=1) # pooled encoder features (linear probing)
306
+ return self.final_layer(patch_out, n_chans)
307
+
308
+
309
+ #: Architecture presets. Small and Medium have released weights; Large does not.
310
+ EEGDINO_CONFIGS = {
311
+ "small": dict(
312
+ n_layer=12,
313
+ nhead=8,
314
+ dim_feedforward=512,
315
+ channels_kernel_stride_padding_norm=(
316
+ (25, 49, 25, 24, (5, 25)),
317
+ (25, 3, 1, 1, (5, 25)),
318
+ (25, 3, 1, 1, (5, 25)),
319
+ ),
320
+ ),
321
+ "medium": dict(
322
+ n_layer=16,
323
+ nhead=8,
324
+ dim_feedforward=1024,
325
+ channels_kernel_stride_padding_norm=(
326
+ (64, 49, 25, 24, (8, 64)),
327
+ (128, 3, 1, 1, (8, 128)),
328
+ (64, 3, 1, 1, (8, 64)),
329
+ ),
330
+ ),
331
+ "large": dict(
332
+ n_layer=24,
333
+ nhead=8,
334
+ dim_feedforward=2048,
335
+ channels_kernel_stride_padding_norm=(
336
+ (128, 49, 25, 24, (16, 128)),
337
+ (256, 3, 1, 1, (16, 256)),
338
+ (128, 3, 1, 1, (16, 128)),
339
+ ),
340
+ ),
341
+ }
342
+
343
+
344
+ class _PatchEmbedding(nn.Module):
345
+ """Time-frequency patch embedding with a decoupled positional embedding.
346
+
347
+ The time-domain (``proj_in``) and frequency-domain (``spectral_proj``)
348
+ branches are CBraMod's TFE (see :class:`~braindecode.models.CBraMod`); the
349
+ one-hot ``channel_embedding`` and depthwise ``time_encoding`` are EEG-DINO's
350
+ decoupled positional embedding.
351
+ """
352
+
353
+ def __init__(
354
+ self,
355
+ patch_size,
356
+ channels_kernel_stride_padding_norm,
357
+ n_channel_embeddings,
358
+ activation=nn.GELU,
359
+ drop_prob=0.1,
360
+ ):
361
+ super().__init__()
362
+ self.patch_size = patch_size
363
+ self.channels_kernel_stride_padding_norm = channels_kernel_stride_padding_norm
364
+ self.n_channel_embeddings = n_channel_embeddings
365
+
366
+ in_channels, conv_layers = 1, []
367
+ for (
368
+ out_channels,
369
+ kernel,
370
+ stride,
371
+ padding,
372
+ norm,
373
+ ) in channels_kernel_stride_padding_norm:
374
+ conv_layers += [
375
+ nn.Conv2d(
376
+ in_channels, out_channels, (1, kernel), (1, stride), (0, padding)
377
+ ),
378
+ nn.GroupNorm(*norm),
379
+ activation(),
380
+ ]
381
+ in_channels = out_channels
382
+ self.proj_in = nn.Sequential(*conv_layers)
383
+
384
+ self.spectral_proj = nn.Sequential(
385
+ nn.Linear(patch_size // 2 + 1, self.emb_dim), nn.Dropout(drop_prob)
386
+ )
387
+ self.channel_embedding = nn.Linear(n_channel_embeddings, self.emb_dim)
388
+ # Sequential wrapper keeps the released-checkpoint key ``time_encoding.0.*``.
389
+ self.time_encoding = nn.Sequential(
390
+ nn.Conv2d(
391
+ self.emb_dim, self.emb_dim, (1, 5), (1, 1), (0, 2), groups=self.emb_dim
392
+ )
393
+ )
394
+
395
+ @property
396
+ def emb_dim(self):
397
+ """Embedding dimension implied by the convolution configuration."""
398
+ reduced = self.patch_size
399
+ for _, kernel, stride, padding, _ in self.channels_kernel_stride_padding_norm:
400
+ reduced = (reduced + 2 * padding - kernel) // stride + 1
401
+ return self.channels_kernel_stride_padding_norm[-1][0] * reduced
402
+
403
+ def forward(self, x):
404
+ # x: (batch, n_chans, n_patches, patch_size)
405
+ n_chans = x.shape[1]
406
+
407
+ # Time-frequency embedding (CBraMod TFE): conv branch + FFT-magnitude branch.
408
+ time_tokens = self.proj_in(x.flatten(1, 2).unsqueeze(1))
409
+ batch, conv_dim, _, conv_len = time_tokens.shape
410
+ time_tokens = (
411
+ time_tokens.reshape(batch, conv_dim, n_chans, -1, conv_len)
412
+ .permute(0, 2, 3, 1, 4)
413
+ .flatten(3, 4)
414
+ )
415
+ spectrum = torch.fft.rfft(x, dim=-1, norm="forward").abs()
416
+ patch_emb = time_tokens + self.spectral_proj(spectrum)
417
+
418
+ # Decoupled positional embedding: one-hot channel + depthwise temporal conv.
419
+ # The one-hot uses the first ``n_chans`` of the ``n_channel_embeddings``
420
+ # slots, so the released 19-slot embedding serves any n_chans <= 19.
421
+ channel_ids = torch.arange(n_chans, device=x.device)
422
+ one_hot = F.one_hot(channel_ids, self.n_channel_embeddings).to(
423
+ self.channel_embedding.weight.dtype
424
+ )
425
+ channel_emb = self.channel_embedding(one_hot)
426
+ patch_emb = patch_emb + channel_emb.unsqueeze(0).unsqueeze(2)
427
+ temporal_emb = self.time_encoding(patch_emb.permute(0, 3, 1, 2))
428
+ return patch_emb + temporal_emb.permute(0, 2, 3, 1)
429
+
430
+
431
+ class _TransformerEncoderLayer(nn.Module):
432
+ """Pre-norm transformer block (no LayerScale, matching the released config)."""
433
+
434
+ def __init__(
435
+ self, emb_dim, nhead, dim_feedforward, activation=nn.GELU, drop_prob=0.1
436
+ ):
437
+ super().__init__()
438
+ self.norm1 = nn.LayerNorm(emb_dim)
439
+ self.attn = _Attention(emb_dim, nhead, attn_drop=drop_prob, proj_drop=drop_prob)
440
+ self.drop_path = DropPath(drop_prob) if drop_prob > 0.0 else nn.Identity()
441
+ self.norm2 = nn.LayerNorm(emb_dim)
442
+ self.mlp = _MLP(emb_dim, dim_feedforward, activation=activation, drop=drop_prob)
443
+
444
+ def forward(self, x):
445
+ x = x + self.drop_path(self.attn(self.norm1(x)))
446
+ x = x + self.drop_path(self.mlp(self.norm2(x)))
447
+ return x
448
+
449
+
450
+ class _Attention(nn.Module):
451
+ """BEiT-style attention: fused ``qkv`` without bias plus separate q/v biases.
452
+
453
+ ``head_dim`` is decoupled from ``emb_dim`` (``all_head_dim = head_dim *
454
+ nhead``) so a custom ``nhead`` need not divide ``emb_dim``. For the released
455
+ presets ``nhead=8`` divides every ``emb_dim`` (200/512/1024), so this is a
456
+ no-op there and the released ``qkv``/``proj`` shapes are preserved.
457
+ """
458
+
459
+ def __init__(self, emb_dim, nhead=8, attn_drop=0.0, proj_drop=0.0):
460
+ super().__init__()
461
+ if not 1 <= nhead <= emb_dim:
462
+ raise ValueError(
463
+ f"nhead ({nhead}) must be in [1, emb_dim={emb_dim}] so head_dim >= 1."
464
+ )
465
+ self.nhead = nhead
466
+ head_dim = emb_dim // nhead
467
+ all_head_dim = head_dim * nhead
468
+ self.scale = head_dim**-0.5
469
+ self.qkv = nn.Linear(emb_dim, all_head_dim * 3, bias=False)
470
+ self.q_bias = nn.Parameter(torch.zeros(all_head_dim))
471
+ self.v_bias = nn.Parameter(torch.zeros(all_head_dim))
472
+ self.attn_drop = nn.Dropout(attn_drop)
473
+ self.proj = nn.Linear(all_head_dim, emb_dim)
474
+ self.proj_drop = nn.Dropout(proj_drop)
475
+
476
+ def forward(self, x):
477
+ bias = torch.cat((self.q_bias, torch.zeros_like(self.v_bias), self.v_bias))
478
+ qkv = F.linear(x, self.qkv.weight, bias)
479
+ batch, tokens, _ = qkv.shape
480
+ query, key, value = (
481
+ qkv.reshape(batch, tokens, 3, self.nhead, -1)
482
+ .permute(2, 0, 3, 1, 4)
483
+ .unbind(0)
484
+ )
485
+ attn = (query * self.scale @ key.transpose(-2, -1)).softmax(dim=-1)
486
+ attn = self.attn_drop(attn)
487
+ out = (attn @ value).transpose(1, 2).flatten(2)
488
+ return self.proj_drop(self.proj(out))
489
+
490
+
491
+ class _MLP(nn.Module):
492
+ """Feed-forward block; ``fc1``/``fc2`` names match the released checkpoint."""
493
+
494
+ def __init__(self, emb_dim, hidden_dim, activation=nn.GELU, drop=0.0):
495
+ super().__init__()
496
+ self.fc1 = nn.Linear(emb_dim, hidden_dim)
497
+ self.act = activation()
498
+ self.fc2 = nn.Linear(hidden_dim, emb_dim)
499
+ self.drop = nn.Dropout(drop)
500
+
501
+ def forward(self, x):
502
+ return self.drop(self.fc2(self.act(self.fc1(x))))
503
+
504
+
505
+ class _ClassificationHead(nn.Module):
506
+ """EEG-DINO finetuning head (Wang et al., 2025).
507
+
508
+ Applies a per-token projection, mean-pools the patch tokens over channels
509
+ then over patches (with an intermediate projection), and maps the pooled
510
+ ``emb_dim`` vector to ``n_outputs`` with a three-layer MLP.
511
+ """
512
+
513
+ def __init__(
514
+ self, emb_dim, n_outputs, activation=nn.GELU, drop_prob_1=0.5, drop_prob_2=0.3
515
+ ):
516
+ super().__init__()
517
+ self.token_proj = nn.Sequential(nn.Linear(emb_dim, emb_dim), activation())
518
+ self.time_proj = nn.Sequential(nn.Linear(emb_dim, emb_dim), activation())
519
+ self.classifier = nn.Sequential(
520
+ nn.Linear(emb_dim, emb_dim // 2),
521
+ activation(),
522
+ nn.Dropout(drop_prob_1),
523
+ nn.Linear(emb_dim // 2, emb_dim // 4),
524
+ activation(),
525
+ nn.Dropout(drop_prob_2),
526
+ nn.Linear(emb_dim // 4, n_outputs),
527
+ )
528
+
529
+ def forward(self, patch_tokens, n_chans):
530
+ x = self.token_proj(patch_tokens)
531
+ x = x.reshape(x.shape[0], n_chans, -1, x.shape[2]).mean(dim=1)
532
+ x = self.time_proj(x).mean(dim=1)
533
+ return self.classifier(x)
@@ -56,3 +56,4 @@ InterpolatedBIOT,"Sleep Staging, Epilepsy",Classification,200,"chs_info, n_outpu
56
56
  InterpolatedEEGPT,General,Classification,256,"chs_info, n_outputs, n_times",25323137,"InterpolatedEEGPT(chs_info=<user>, n_outputs=4, n_times=1024)","Foundation Model,Attention/Transformer,Channel"
57
57
  InterpolatedLaBraM,General,"Classification, Embedding",200,"chs_info, n_outputs, n_times",5866196,"InterpolatedLaBraM(chs_info=<user>, n_outputs=4, n_times=1000)","Convolution,Foundation Model,Channel"
58
58
  InterpolatedSignalJEPA,"Motor Imagery, ERP, SSVEP",Embedding,128,"chs_info",3456882,"InterpolatedSignalJEPA(chs_info=<user>)","Convolution,Channel,Foundation Model"
59
+ EEGDINO,General,Classification,200,"n_chans, n_outputs, n_times",4539698,"EEGDINO(n_chans=19, n_outputs=4, n_times=1000)","Foundation Model,Attention/Transformer"
@@ -432,6 +432,7 @@ models_mandatory_parameters: list[
432
432
  {"n_chans": 19, "n_times": 6000},
433
433
  ),
434
434
  ("DGCNN", ["n_chans", "n_outputs", "n_times", "chs_info"], None),
435
+ ("EEGDINO", ["n_chans", "n_outputs", "n_times"], None),
435
436
  ]
436
437
 
437
438
  ################################################################
@@ -14,7 +14,7 @@ from .attention import (
14
14
  MultiHeadAttention,
15
15
  SqueezeAndExcitation,
16
16
  )
17
- from .blocks import MLP, FeedForwardBlock, InceptionBlock
17
+ from .blocks import MLP, FeedForwardBlock, InceptionBlock, PatchTokenizer
18
18
  from .convolution import (
19
19
  AvgPool2dWithConv,
20
20
  CausalConv1d,
@@ -68,6 +68,7 @@ __all__ = [
68
68
  "MLP",
69
69
  "FeedForwardBlock",
70
70
  "InceptionBlock",
71
+ "PatchTokenizer",
71
72
  "AvgPool2dWithConv",
72
73
  "CausalConv1d",
73
74
  "CombinedConv",