Braindecode 1.3.2.dev176161306__tar.gz → 1.3.2.dev176759409__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.dev176161306/braindecode.egg-info → braindecode-1.3.2.dev176759409}/PKG-INFO +2 -2
  2. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/hub.py +7 -2
  3. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/__init__.py +2 -0
  4. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/base.py +28 -17
  5. braindecode-1.3.2.dev176759409/braindecode/models/dgcnn.py +500 -0
  6. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegsym.py +1 -1
  7. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/luna.py +8 -7
  8. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/reve.py +23 -3
  9. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sstdpn.py +1 -1
  10. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/summary.csv +1 -0
  11. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/util.py +13 -11
  12. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/mne_preprocess.py +2 -1
  13. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/util.py +34 -4
  14. braindecode-1.3.2.dev176759409/braindecode/version.py +1 -0
  15. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409/braindecode.egg-info}/PKG-INFO +2 -2
  16. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode.egg-info/SOURCES.txt +1 -0
  17. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode.egg-info/requires.txt +1 -1
  18. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/api.rst +11 -9
  19. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/conf.py +18 -6
  20. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/gnn.rst +9 -1
  21. braindecode-1.3.2.dev176759409/docs/models/categorization/spd.rst +103 -0
  22. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/models_categorization.rst +60 -60
  23. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/whats_new.rst +28 -5
  24. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/pyproject.toml +10 -1
  25. braindecode-1.3.2.dev176161306/braindecode/version.py +0 -1
  26. braindecode-1.3.2.dev176161306/docs/models/categorization/spd.rst +0 -35
  27. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/LICENSE.txt +0 -0
  28. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/MANIFEST.in +0 -0
  29. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/NOTICE.txt +0 -0
  30. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/README.rst +0 -0
  31. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/__init__.py +0 -0
  32. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/augmentation/__init__.py +0 -0
  33. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/augmentation/base.py +0 -0
  34. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/augmentation/functional.py +0 -0
  35. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/augmentation/transforms.py +0 -0
  36. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/classifier.py +0 -0
  37. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/__init__.py +0 -0
  38. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/base.py +0 -0
  39. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bbci.py +0 -0
  40. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bcicomp.py +0 -0
  41. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/__init__.py +0 -0
  42. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/datasets.py +0 -0
  43. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/format.py +0 -0
  44. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/hub_format.py +0 -0
  45. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/hub_io.py +0 -0
  46. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/hub_validation.py +0 -0
  47. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/iterable.py +0 -0
  48. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/chb_mit.py +0 -0
  49. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/mne.py +0 -0
  50. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/moabb.py +0 -0
  51. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/nmt.py +0 -0
  52. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/registry.py +0 -0
  53. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/siena.py +0 -0
  54. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  55. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/sleep_physionet.py +0 -0
  56. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/tuh.py +0 -0
  57. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/utils.py +0 -0
  58. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/xy.py +0 -0
  59. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/__init__.py +0 -0
  60. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/channel_utils.py +0 -0
  61. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/hub_formats.py +0 -0
  62. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/serialization.py +0 -0
  63. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/util.py +0 -0
  64. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/eegneuralnet.py +0 -0
  65. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/functional/__init__.py +0 -0
  66. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/functional/functions.py +0 -0
  67. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/functional/initialization.py +0 -0
  68. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/atcnet.py +0 -0
  69. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/attentionbasenet.py +0 -0
  70. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/attn_sleep.py +0 -0
  71. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/bendr.py +0 -0
  72. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/biot.py +0 -0
  73. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/brainmodule.py +0 -0
  74. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/cbramod.py +0 -0
  75. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/config.py +0 -0
  76. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/contrawr.py +0 -0
  77. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/ctnet.py +0 -0
  78. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/deep4.py +0 -0
  79. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/deepsleepnet.py +0 -0
  80. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegconformer.py +0 -0
  81. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eeginception_erp.py +0 -0
  82. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eeginception_mi.py +0 -0
  83. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegitnet.py +0 -0
  84. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegminer.py +0 -0
  85. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegnet.py +0 -0
  86. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegnex.py +0 -0
  87. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegpt.py +0 -0
  88. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegsimpleconv.py +0 -0
  89. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegtcnet.py +0 -0
  90. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/fbcnet.py +0 -0
  91. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/fblightconvnet.py +0 -0
  92. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/fbmsnet.py +0 -0
  93. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/hybrid.py +0 -0
  94. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/ifnet.py +0 -0
  95. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/labram.py +0 -0
  96. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/medformer.py +0 -0
  97. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/msvtnet.py +0 -0
  98. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/patchedtransformer.py +0 -0
  99. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sccnet.py +0 -0
  100. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/shallow_fbcsp.py +0 -0
  101. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/signal_jepa.py +0 -0
  102. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sinc_shallow.py +0 -0
  103. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
  104. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
  105. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sparcnet.py +0 -0
  106. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/syncnet.py +0 -0
  107. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/tcn.py +0 -0
  108. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/tidnet.py +0 -0
  109. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/tsinception.py +0 -0
  110. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/usleep.py +0 -0
  111. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/__init__.py +0 -0
  112. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/activation.py +0 -0
  113. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/attention.py +0 -0
  114. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/blocks.py +0 -0
  115. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/convolution.py +0 -0
  116. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/filter.py +0 -0
  117. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/layers.py +0 -0
  118. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/linear.py +0 -0
  119. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/parametrization.py +0 -0
  120. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/stats.py +0 -0
  121. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/util.py +0 -0
  122. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/wrapper.py +0 -0
  123. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/__init__.py +0 -0
  124. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
  125. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/preprocess.py +0 -0
  126. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/util.py +0 -0
  127. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/windowers.py +0 -0
  128. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/regressor.py +0 -0
  129. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/samplers/__init__.py +0 -0
  130. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/samplers/base.py +0 -0
  131. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/samplers/ssl.py +0 -0
  132. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/training/__init__.py +0 -0
  133. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/training/callbacks.py +0 -0
  134. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/training/losses.py +0 -0
  135. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/training/scoring.py +0 -0
  136. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/visualization/__init__.py +0 -0
  137. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/visualization/confusion_matrices.py +0 -0
  138. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/visualization/gradients.py +0 -0
  139. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode.egg-info/dependency_links.txt +0 -0
  140. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode.egg-info/top_level.txt +0 -0
  141. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/Makefile +0 -0
  142. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/_templates/autosummary/class.rst +0 -0
  143. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  144. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/_templates/autosummary/function.rst +0 -0
  145. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  146. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/cite.rst +0 -0
  147. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/help.rst +0 -0
  148. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/index.rst +0 -0
  149. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/install/install.rst +0 -0
  150. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/install/install_pip.rst +0 -0
  151. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/install/install_source.rst +0 -0
  152. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/attention.rst +0 -0
  153. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/channel.rst +0 -0
  154. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/convolution.rst +0 -0
  155. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/filterbank.rst +0 -0
  156. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/interpretable.rst +0 -0
  157. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/lbm.rst +0 -0
  158. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/recurrent.rst +0 -0
  159. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/models.rst +0 -0
  160. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/models_table.rst +0 -0
  161. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/models_visualization.rst +0 -0
  162. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/sg_execution_times.rst +0 -0
  163. {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.3.2.dev176161306
3
+ Version: 1.3.2.dev176759409
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
@@ -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)
@@ -12,6 +12,7 @@ from .contrawr import ContraWR
12
12
  from .ctnet import CTNet
13
13
  from .deep4 import Deep4Net
14
14
  from .deepsleepnet import DeepSleepNet
15
+ from .dgcnn import DGCNN
15
16
  from .eegconformer import EEGConformer
16
17
  from .eeginception_erp import EEGInceptionERP
17
18
  from .eeginception_mi import EEGInceptionMI
@@ -87,6 +88,7 @@ __all__ = [
87
88
  "EEGSym",
88
89
  "EEGSimpleConv",
89
90
  "EEGTCNet",
91
+ "DGCNN",
90
92
  "FBCNet",
91
93
  "FBLightConvNet",
92
94
  "FBMSNet",
@@ -86,38 +86,44 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
86
86
  -----
87
87
  If some input signal-related parameters are not specified,
88
88
  there will be an attempt to infer them from the other parameters.
89
+ """
89
90
 
91
+ #: Template for model-specific Hub integration notes appended by
92
+ #: ``__init_subclass__``. ``{name}`` is replaced with the concrete
93
+ #: model class name.
94
+ _HUB_NOTES_TEMPLATE = """
90
95
  .. rubric:: Hugging Face Hub integration
91
96
 
92
97
  When the optional ``huggingface_hub`` package is installed, all models
93
98
  automatically gain the ability to be pushed to and loaded from the
94
99
  Hugging Face Hub. Install with::
95
100
 
96
- pip install braindecode[hug]
101
+ pip install braindecode[hub]
97
102
 
98
103
  **Pushing a model to the Hub:**
99
104
 
100
- .. code-block:: python
105
+ .. code-block::
101
106
 
102
- from braindecode.models import EEGNetv4
107
+ from braindecode.models import {name}
103
108
 
104
109
  # Train your model
105
- model = EEGNetv4(n_chans=22, n_outputs=4, n_times=1000)
110
+ model = {name}(n_chans=22, n_outputs=4, n_times=1000)
106
111
  # ... training code ...
107
112
 
108
113
  # Push to the Hub
109
114
  model.push_to_hub(
110
- repo_id="username/my-eegnet-model", commit_message="Initial model upload"
115
+ repo_id="username/my-{name_lower}-model",
116
+ commit_message="Initial model upload",
111
117
  )
112
118
 
113
119
  **Loading a model from the Hub:**
114
120
 
115
- .. code-block:: python
121
+ .. code-block::
116
122
 
117
- from braindecode.models import EEGNetv4
123
+ from braindecode.models import {name}
118
124
 
119
125
  # Load pretrained model
120
- model = EEGNetv4.from_pretrained("username/my-eegnet-model")
126
+ model = {name}.from_pretrained("username/my-{name_lower}-model")
121
127
 
122
128
  The integration automatically handles EEG-specific parameters (n_chans,
123
129
  n_times, sfreq, chs_info, etc.) by saving them in a config file alongside
@@ -126,19 +132,24 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
126
132
 
127
133
  .. important::
128
134
  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
-
135
+ input_window_seconds, sfreq, chs_info) are saved to the Hub.
136
+ Model-specific parameters (e.g., dropout rates, activation functions,
137
+ number of filters) are not preserved and will use their default values
138
+ when loading from the Hub.
138
139
  Full parameter serialization will be addressed in a future update.
139
140
  """
140
141
 
141
142
  def __init_subclass__(cls, **kwargs):
143
+ # Append model-specific Hub integration notes to the docstring.
144
+ # This runs after the metaclass, so we concatenate rather than
145
+ # override any existing Notes section in the subclass.
146
+ if cls.__doc__ is not None:
147
+ hub_notes = cls._HUB_NOTES_TEMPLATE.format(
148
+ name=cls.__name__,
149
+ name_lower=cls.__name__.lower(),
150
+ )
151
+ cls.__doc__ = cls.__doc__.rstrip() + "\n" + hub_notes
152
+
142
153
  if not HAS_HF_HUB:
143
154
  super().__init_subclass__(**kwargs)
144
155
  return
@@ -0,0 +1,500 @@
1
+ # Authors: Vandit Shah <shahvanditt@gmail.com>
2
+ # Bruno Aristimunha <b.aristimunha@gmail.com>
3
+ #
4
+ # Architecture based on Song et al. (2018):
5
+ # Official TensorFlow code archived at:
6
+ # http://web.archive.org/web/20221122064435/http://aip.seu.edu.cn/wp-content/uploads/2021/08/EEG-TAC.zip
7
+ #
8
+ # License: BSD (3-clause)
9
+
10
+ from __future__ import annotations
11
+
12
+ import mne
13
+ import numpy as np
14
+ import torch
15
+ import torch.nn as nn
16
+ import torch.nn.functional as F
17
+ from sklearn.metrics import pairwise_distances
18
+ from sklearn.neighbors import kneighbors_graph
19
+
20
+ from braindecode.models.base import EEGModuleMixin
21
+ from braindecode.models.util import extract_channel_locations_from_chs_info
22
+
23
+
24
+ class _GraphConvolution(nn.Module):
25
+ r"""Chebyshev spectral graph convolution with :math:`1 \times 1` mixing.
26
+
27
+ Implements Equations 11-13 of Song et al. (2018). The spectral
28
+ filter :math:`g(\boldsymbol{\Lambda}^*)` is approximated by a
29
+ :math:`K`-order Chebyshev polynomial expansion:
30
+
31
+ .. math::
32
+
33
+ \mathbf{y}
34
+ = \sum_{k=0}^{K-1} \theta_k\, T_k(\tilde{\mathbf{L}}^*)\,\mathbf{x},
35
+
36
+ where :math:`T_k` are Chebyshev polynomials of the first kind
37
+ computed recursively (Eq. 12):
38
+
39
+ .. math::
40
+
41
+ T_0(x) = 1,\quad T_1(x) = x,\quad
42
+ T_k(x) = 2x\,T_{k-1}(x) - T_{k-2}(x),\quad k \ge 2,
43
+
44
+ and :math:`\tilde{\mathbf{L}}^*` is the rescaled normalized Laplacian.
45
+ The learnable coefficients :math:`\theta_k` are absorbed into a single
46
+ linear projection of the concatenated Chebyshev components, which
47
+ simultaneously acts as a :math:`1 \times 1` convolution that mixes
48
+ features across frequency bands (see Fig. 2 in the paper).
49
+
50
+ Parameters
51
+ ----------
52
+ in_features : int
53
+ Number of input features per node (e.g. number of time samples
54
+ or frequency bands).
55
+ out_features : int
56
+ Number of output features per node (number of spectral filters).
57
+ cheb_order : int
58
+ Order :math:`K` of the Chebyshev polynomial approximation.
59
+ """
60
+
61
+ def __init__(self, in_features, out_features, cheb_order):
62
+ super().__init__()
63
+ self.cheb_order = cheb_order
64
+ self.projection = nn.Linear(in_features * cheb_order, out_features, bias=False)
65
+
66
+ def forward(self, x, laplacian):
67
+ r"""Apply Chebyshev graph convolution.
68
+
69
+ Computes :math:`[T_0(\mathbf{L})\mathbf{x},\;\ldots,\;
70
+ T_{K-1}(\mathbf{L})\mathbf{x}]` and projects the concatenation
71
+ through a learned weight matrix.
72
+
73
+ Parameters
74
+ ----------
75
+ x : Tensor, shape (B, N, F_in)
76
+ Input node features, where *N* is the number of graph nodes
77
+ (EEG channels) and *F_in* the input feature dimension.
78
+ laplacian : Tensor, shape (N, N)
79
+ Normalized graph Laplacian.
80
+
81
+ Returns
82
+ -------
83
+ Tensor, shape (B, N, F_out)
84
+ Filtered node features.
85
+ """
86
+ cheb_components = [x] # T_0(L) x = x
87
+
88
+ if self.cheb_order > 1:
89
+ cheb_components.append(torch.matmul(laplacian, x)) # T_1(L) x = L x
90
+
91
+ for _ in range(2, self.cheb_order):
92
+ cheb_components.append(
93
+ 2 * torch.matmul(laplacian, cheb_components[-1]) - cheb_components[-2]
94
+ )
95
+
96
+ return self.projection(torch.cat(cheb_components, dim=-1))
97
+
98
+
99
+ def _build_initial_adjacency(chs_info, n_chans, n_neighbors=5):
100
+ r"""Build an initial adjacency matrix from electrode spatial positions.
101
+
102
+ Implements the Gaussian kernel graph construction from Eq. 1 of
103
+ Song et al. (2018):
104
+
105
+ .. math::
106
+
107
+ w_{ij} = \begin{cases}
108
+ \exp\!\Big(-\dfrac{\mathrm{dist}(i,j)^{2}}{2\rho^{2}}\Big),
109
+ & \text{if } \mathrm{dist}(i,j) \le \tau \\
110
+ 0, & \text{otherwise}
111
+ \end{cases}
112
+
113
+ where :math:`\rho` is estimated as the median of :math:`k`-nearest
114
+ neighbor distances and the threshold :math:`\tau` is enforced by
115
+ keeping only the ``n_neighbors`` nearest neighbors per node.
116
+ The resulting matrix is symmetrized so that
117
+ :math:`A = \max(A, A^\top)`.
118
+
119
+ Parameters
120
+ ----------
121
+ chs_info : list of dict
122
+ MNE-style channel information with ``'loc'`` entries containing
123
+ 3-D electrode positions.
124
+ n_chans : int
125
+ Number of channels (graph nodes :math:`N`).
126
+ n_neighbors : int
127
+ How many spatial neighbors to connect per node (:math:`\tau`
128
+ is implicitly defined by this value).
129
+
130
+ Returns
131
+ -------
132
+ np.ndarray, shape (n_chans, n_chans)
133
+ Symmetric adjacency matrix :math:`\mathbf{W}`.
134
+
135
+ Notes
136
+ -----
137
+ If valid 3-D positions cannot be extracted from ``chs_info``,
138
+ channel names are looked up in the ``standard_1005`` MNE montage.
139
+ If that also fails (or ``chs_info`` is ``None``)
140
+ """
141
+ electrode_positions = extract_channel_locations_from_chs_info(
142
+ chs_info, num_channels=n_chans
143
+ )
144
+
145
+ if electrode_positions is None and chs_info is not None:
146
+ # Try to infer positions from channel names via a standard montage
147
+ try:
148
+ ch_names = [ch["ch_name"] for ch in chs_info[:n_chans]]
149
+ montage = mne.channels.make_standard_montage("standard_1005")
150
+ info = mne.create_info(ch_names=ch_names, sfreq=256, ch_types="eeg")
151
+ info.set_montage(montage, on_missing="raise")
152
+ electrode_positions = extract_channel_locations_from_chs_info(
153
+ info["chs"], num_channels=n_chans
154
+ )
155
+ except Exception:
156
+ raise ValueError(
157
+ "Could not extract valid 3-D channel locations from chs_info. "
158
+ "Please ensure that each entry in chs_info contains a 'loc' key "
159
+ "with 3-D positions or that channel names match a standard montage."
160
+ )
161
+
162
+ n_neighbors_capped = min(n_neighbors, n_chans - 1)
163
+
164
+ # Pairwise Euclidean distances
165
+ distance_matrix = pairwise_distances(electrode_positions, metric="euclidean")
166
+
167
+ # Gaussian kernel: w_ij = exp(-d_ij^2 / (2 * sigma^2))
168
+ # sigma = median of kNN distances (robust scale estimate)
169
+ knn_distances = np.sort(distance_matrix, axis=1)[:, 1 : n_neighbors_capped + 1]
170
+ sigma = np.median(knn_distances) + 1e-8
171
+ gaussian_weights = np.exp(-(distance_matrix**2) / (2 * sigma**2))
172
+
173
+ # Sparsify: keep only k nearest neighbors (symmetric)
174
+ knn_connectivity = kneighbors_graph(
175
+ electrode_positions, n_neighbors=n_neighbors_capped, mode="connectivity"
176
+ )
177
+ adjacency = np.array(knn_connectivity.toarray()) * gaussian_weights
178
+
179
+ # Symmetrize
180
+ adjacency = np.maximum(adjacency, adjacency.T).astype(np.float32)
181
+ return adjacency
182
+
183
+
184
+ class _LearnableAdjacency(nn.Module):
185
+ r"""Learnable adjacency matrix with ReLU non-negativity.
186
+
187
+ Implements the dynamical adjacency learning described in Section 3
188
+ and Algorithm 1 of Song et al. (2018). The adjacency matrix
189
+ :math:`\mathbf{W}^*` is initialized from electrode spatial
190
+ positions (Eq. 1) and then optimized jointly with all other model
191
+ parameters via back-propagation (Eqs. 15-16):
192
+
193
+ .. math::
194
+
195
+ \mathbf{W}^* \leftarrow (1 - \rho)\,\mathbf{W}^*
196
+ + \rho\,\frac{\partial Loss}{\partial \mathbf{W}^*}.
197
+
198
+ Following Algorithm 1 (step 3), a ReLU operation is applied after
199
+ every update to keep all entries non-negative. The normalized
200
+ graph Laplacian is then derived as (Eq. 2, normalized form):
201
+
202
+ .. math::
203
+
204
+ \mathbf{L} = \mathbf{I}
205
+ - \mathbf{D}^{-1/2}\,\mathbf{W}^*\,\mathbf{D}^{-1/2},
206
+
207
+ where :math:`D_{ii} = \sum_j w^*_{ij}`.
208
+
209
+ Parameters
210
+ ----------
211
+ n_chans : int
212
+ Number of graph nodes :math:`N` (EEG channels).
213
+ chs_info : list of dict
214
+ MNE-style channel information with 3-D positions used to
215
+ build the initial adjacency via :func:`_build_initial_adjacency`.
216
+ n_neighbors : int
217
+ Neighbors per node for the initial spatial kNN graph.
218
+ """
219
+
220
+ def __init__(self, n_chans, chs_info, n_neighbors=5):
221
+ super().__init__()
222
+ self.n_chans = n_chans
223
+
224
+ init_adj = _build_initial_adjacency(chs_info, n_chans, n_neighbors)
225
+ self.adjacency = nn.Parameter(
226
+ torch.from_numpy(init_adj) + 0.1 * torch.randn(n_chans, n_chans)
227
+ )
228
+ self.bias = nn.Parameter(torch.tensor(0.0))
229
+
230
+ def forward(self):
231
+ r"""Compute the normalized Laplacian from the current adjacency.
232
+
233
+ Applies ReLU to enforce non-negativity (Algorithm 1, step 3)
234
+ and returns
235
+ :math:`\mathbf{L} = \mathbf{I}
236
+ - \mathbf{D}^{-1/2}\,\mathbf{A}\,\mathbf{D}^{-1/2}`.
237
+
238
+ Returns
239
+ -------
240
+ Tensor, shape (N, N)
241
+ Normalized graph Laplacian.
242
+ """
243
+ adjacency = F.relu(self.adjacency + self.bias)
244
+
245
+ degree = adjacency.sum(dim=1)
246
+ degree_inv_sqrt = 1.0 / (torch.sqrt(degree) + 1e-5)
247
+ degree_inv_sqrt_diag = torch.diag(degree_inv_sqrt)
248
+
249
+ identity = torch.eye(self.n_chans, device=adjacency.device)
250
+ return identity - degree_inv_sqrt_diag @ adjacency @ degree_inv_sqrt_diag
251
+
252
+
253
+ class DGCNN(EEGModuleMixin, nn.Module):
254
+ r"""DGCNN for EEG classification from Song et al. (2018) [dgcnn]_.
255
+
256
+ :bdg-light:`Graph Neural Network` :bdg-dark-line:`Channel`
257
+
258
+ .. figure:: ../_static/model/DGCNN.gif
259
+ :align: center
260
+ :alt: DGCNN Architecture
261
+ :width: 600px
262
+
263
+ .. rubric:: Architectural Overview
264
+
265
+ DGCNN is a *graph-based* architecture that models EEG channels as nodes
266
+ in a graph and **dynamically learns the adjacency matrix**
267
+ :math:`\mathbf{W}^*` jointly with all other parameters via
268
+ back-propagation (Algorithm 1 in [dgcnn]_). The end-to-end flow is:
269
+
270
+ - (i) learn inter-channel relationships by dynamically updating a
271
+ trainable adjacency matrix,
272
+ - (ii) apply spectral graph convolution via Chebyshev polynomial
273
+ approximation to extract graph-structured features, and
274
+ - (iii) classify with a fully connected head.
275
+
276
+ Different from traditional GCNN methods that predetermine the connections
277
+ of the graph nodes according to their spatial positions, "the proposed
278
+ DGCNN method learns the adjacency matrix in a dynamic way, i.e., the
279
+ entries of the adjacency matrix are adaptively updated with the changes
280
+ of graph model parameters during the model training" [dgcnn]_.
281
+
282
+ .. rubric:: Macro Components
283
+
284
+ - :class:`_LearnableAdjacency` **(Dynamical adjacency → graph Laplacian)**
285
+
286
+ - *Operations.*
287
+ - A trainable :math:`(N \times N)` matrix :math:`\mathbf{W}^*`
288
+ initialized from electrode spatial positions via a Gaussian kernel
289
+ (Eq. 1): :math:`w_{ij} = \exp(-\mathrm{dist}(i,j)^2 / 2\rho^2)`
290
+ for the :math:`k`-nearest neighbors, zero otherwise.
291
+ - **ReLU** applied after every gradient update to keep all entries
292
+ non-negative (Algorithm 1, step 3).
293
+ - The normalized graph Laplacian is derived as (Eq. 2):
294
+ :math:`\mathbf{L} = \mathbf{I}
295
+ - \mathbf{D}^{-1/2}\,\mathbf{W}^*\,\mathbf{D}^{-1/2}`.
296
+
297
+ The adjacency matrix captures intrinsic functional relationships
298
+ between EEG channels that pure spatial proximity may not reflect.
299
+
300
+ - :class:`_GraphConvolution` **(Chebyshev spectral graph convolution +
301
+ 1x1 mixing)**
302
+
303
+ - *Operations.*
304
+ - :math:`K`-order Chebyshev polynomial expansion of spectral graph
305
+ filters on the learned Laplacian (Eqs. 11-13):
306
+
307
+ .. math::
308
+
309
+ \mathbf{y}
310
+ = \sum_{k=0}^{K-1} \theta_k\, T_k(\tilde{\mathbf{L}}^*)\,
311
+ \mathbf{x},
312
+
313
+ where :math:`T_k` are Chebyshev polynomials computed recursively
314
+ (Eq. 12) and :math:`\theta_k` are learnable coefficients.
315
+ - A :math:`1 \times 1` convolution (linear projection) that mixes
316
+ the concatenated Chebyshev components, mapping each node's input
317
+ features to ``n_filters`` output features.
318
+
319
+ "Following the graph filtering operation is a :math:`1 \times 1`
320
+ convolution layer, which aims to learn the discriminative features
321
+ among the various frequency domains" [dgcnn]_.
322
+
323
+ - **Activation layer.** ReLU with a learnable per-feature bias ensures
324
+ non-negative outputs of the graph filtering layer [dgcnn]_.
325
+
326
+ - **Classifier Head.**
327
+ Flatten all node features and classify via a multi-layer fully
328
+ connected network with dropout and softmax.
329
+
330
+ .. rubric:: Graph Convolution Details
331
+
332
+ - **Spatial (graph structure).** The adjacency matrix encodes pairwise
333
+ relationships between EEG channels. It is initialized from 3-D
334
+ electrode positions using a Gaussian kernel with kNN sparsification
335
+ (Eq. 1), then *jointly optimized* with all other parameters. This
336
+ allows the model to discover functional connectivity patterns that
337
+ differ from the initial spatial layout. The spectral graph
338
+ convolution then propagates information across neighboring nodes
339
+ according to this learned graph topology.
340
+
341
+ - **Spectral (graph spectral domain).** The Chebyshev polynomial
342
+ approximation (Eq. 11) operates in the *graph spectral domain*
343
+ defined by the eigenvalues of the graph Laplacian. The :math:`K`-order
344
+ approximation acts as a localized graph filter: each node aggregates
345
+ information from its :math:`K`-hop neighborhood. This is analogous
346
+ to a band-pass filter in the graph frequency domain.
347
+
348
+ - **Temporal / Frequency.** No explicit temporal convolution or
349
+ frequency decomposition is performed within the network. In the
350
+ original paper, the input features per node are pre-extracted
351
+ frequency-band features (e.g., differential entropy from
352
+ :math:`\delta`, :math:`\theta`, :math:`\alpha`, :math:`\beta`,
353
+ :math:`\gamma` bands). When used with raw time series, the time
354
+ samples serve directly as node features.
355
+
356
+ .. rubric:: Additional Comments
357
+
358
+ - **Dynamic vs. static graph.** Traditional GCNN methods fix the
359
+ adjacency matrix before training based on spatial positions.
360
+ DGCNN learns it end-to-end, allowing the graph to capture
361
+ task-relevant functional connectivity rather than mere spatial
362
+ proximity.
363
+ - **Chebyshev order.** The order :math:`K` controls the receptive
364
+ field on the graph: :math:`K=1` uses only direct neighbors,
365
+ :math:`K=2` (default) reaches 2-hop neighborhoods. Higher orders
366
+ increase expressivity but also parameter count.
367
+ - **Regularization.** Dropout in the classification head and the
368
+ ReLU constraint on the adjacency matrix provide implicit
369
+ regularization. The loss function in the original paper also
370
+ includes an explicit :math:`\ell_2` penalty on all parameters
371
+ (Eq. 14).
372
+
373
+ Parameters
374
+ ----------
375
+ chs_info : list of dict, optional
376
+ Information about each channel, typically obtained from
377
+ ``mne.Info['chs']``. Each entry must contain a ``'loc'``
378
+ key with 3-D electrode positions so the initial adjacency
379
+ matrix can be built from spatial proximity (Eq. 1). A montage
380
+ must be set on the ``mne.Info`` object (see
381
+ :meth:`mne.Info.set_montage`). If ``None`` or positions
382
+ cannot be extracted, raised ValueError (see Notes).
383
+ n_filters : int, default=64
384
+ Number of spectral graph-convolutional filters. This is the
385
+ output feature dimension per node produced by the Chebyshev
386
+ graph convolution followed by the :math:`1 \times 1`
387
+ convolution (see Fig. 2 in the paper). The original code
388
+ uses 64.
389
+ cheb_order : int, default=2
390
+ Order :math:`K` of the Chebyshev polynomial approximation
391
+ (Eq. 11).
392
+ n_neighbors : int, default=5
393
+ Number of spatial nearest neighbors per node used to build the
394
+ initial adjacency matrix (Eq. 1).
395
+ mlp_dims : tuple[int, ...], default=(256,)
396
+ Hidden-layer sizes of the fully connected classification head.
397
+ activation : type[nn.Module], default=nn.ReLU
398
+ Activation function class used after the graph convolution and
399
+ in the classification head.
400
+ drop_prob : float, default=0.5
401
+ Dropout probability in the classification head.
402
+
403
+ References
404
+ ----------
405
+ .. [dgcnn] Song, T., Zheng, W., Song, P., & Cui, Z. (2018). EEG emotion
406
+ recognition using dynamical graph convolutional neural networks.
407
+ IEEE Transactions on Affective Computing, 11(3), 532-541.
408
+ https://doi.org/10.1109/TAFFC.2018.2817622
409
+ """
410
+
411
+ def __init__(
412
+ self,
413
+ n_outputs: int | None = None,
414
+ n_chans: int | None = None,
415
+ chs_info: list[dict] | None = None,
416
+ n_times: int | None = None,
417
+ input_window_seconds: float | None = None,
418
+ sfreq: float | None = None,
419
+ n_filters: int = 64,
420
+ cheb_order: int = 2,
421
+ n_neighbors: int = 5,
422
+ mlp_dims: tuple[int, ...] = (256,),
423
+ activation: type[nn.Module] = nn.ReLU,
424
+ drop_prob: float = 0.5,
425
+ ):
426
+ super().__init__(
427
+ n_outputs=n_outputs,
428
+ n_chans=n_chans,
429
+ chs_info=chs_info,
430
+ n_times=n_times,
431
+ sfreq=sfreq,
432
+ input_window_seconds=input_window_seconds,
433
+ )
434
+
435
+ del n_outputs, n_chans, n_times, input_window_seconds, sfreq
436
+
437
+ self.activation = activation()
438
+ self.drop_prob = drop_prob
439
+
440
+ # Learnable adjacency W* (Section 3.1, Algorithm 1)
441
+ # Use self.chs_info (populated by EEGModuleMixin with defaults
442
+ # when chs_info=None) so that the adjacency can always be built.
443
+ self.learnable_adjacency = _LearnableAdjacency(
444
+ n_chans=self.n_chans,
445
+ chs_info=self.chs_info,
446
+ n_neighbors=n_neighbors,
447
+ )
448
+
449
+ # Chebyshev graph convolution + 1x1 conv (Eq. 13 + Fig. 2)
450
+ self.graph_conv = _GraphConvolution(
451
+ in_features=self.n_times,
452
+ out_features=n_filters,
453
+ cheb_order=cheb_order,
454
+ )
455
+
456
+ # Per-feature bias before ReLU (b1relu in official code)
457
+ self.graph_conv_bias = nn.Parameter(torch.zeros(1, 1, n_filters))
458
+
459
+ # Fully connected classification head (Fig. 2: "Full connection" + softmax)
460
+ classifier_input_dim = self.n_chans * n_filters
461
+ layers = []
462
+ for hidden_dim in mlp_dims:
463
+ layers.append(nn.Linear(classifier_input_dim, hidden_dim))
464
+ layers.append(activation())
465
+ layers.append(nn.Dropout(p=drop_prob))
466
+ classifier_input_dim = hidden_dim
467
+
468
+ self.classifier = nn.Sequential(*layers)
469
+ self.final_layer = nn.Linear(classifier_input_dim, self.n_outputs)
470
+
471
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
472
+ r"""Forward pass through the DGCNN pipeline (Fig. 2).
473
+
474
+ .. figure:: ../_static/model/DGCNN.gif
475
+ :align: center
476
+ :alt: DGCNN Architecture
477
+ :width: 600px
478
+
479
+ 1. Compute normalized Laplacian from the learned adjacency.
480
+ 2. Apply Chebyshev graph convolution (Eq. 13).
481
+ 3. Add per-feature bias and apply ReLU.
482
+ 4. Flatten and classify through the fully connected head.
483
+
484
+ Parameters
485
+ ----------
486
+ x : Tensor, shape (batch, n_chans, n_times)
487
+ Input EEG tensor where each channel corresponds to a
488
+ graph node and the time samples are the input features
489
+ per node.
490
+
491
+ Returns
492
+ -------
493
+ Tensor, shape (batch, n_outputs)
494
+ Class logits.
495
+ """
496
+ laplacian = self.learnable_adjacency()
497
+ x = self.activation(self.graph_conv(x, laplacian) + self.graph_conv_bias)
498
+ x = x.reshape(x.size(0), -1)
499
+ x = self.classifier(x)
500
+ return self.final_layer(x)
@@ -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]_.