braindecode 1.6.1.dev1064__tar.gz → 1.6.1.dev1065__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 (173) hide show
  1. {braindecode-1.6.1.dev1064/braindecode.egg-info → braindecode-1.6.1.dev1065}/PKG-INFO +1 -1
  2. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/__init__.py +2 -0
  3. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/summary.csv +1 -0
  4. braindecode-1.6.1.dev1065/braindecode/models/tcformer.py +643 -0
  5. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/util.py +1 -0
  6. braindecode-1.6.1.dev1065/braindecode/version.py +1 -0
  7. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065/braindecode.egg-info}/PKG-INFO +1 -1
  8. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode.egg-info/SOURCES.txt +1 -0
  9. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/api.rst +1 -0
  10. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/whats_new.rst +5 -0
  11. braindecode-1.6.1.dev1064/braindecode/version.py +0 -1
  12. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/LICENSE.txt +0 -0
  13. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/MANIFEST.in +0 -0
  14. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/NOTICE.txt +0 -0
  15. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/README.rst +0 -0
  16. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/__init__.py +0 -0
  17. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/augmentation/__init__.py +0 -0
  18. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/augmentation/base.py +0 -0
  19. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/augmentation/functional.py +0 -0
  20. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/augmentation/transforms.py +0 -0
  21. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/classifier.py +0 -0
  22. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/__init__.py +0 -0
  23. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/base.py +0 -0
  24. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bbci.py +0 -0
  25. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bcicomp.py +0 -0
  26. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/__init__.py +0 -0
  27. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/datasets.py +0 -0
  28. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/format.py +0 -0
  29. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/hub.py +0 -0
  30. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/hub_format.py +0 -0
  31. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/hub_io.py +0 -0
  32. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/hub_validation.py +0 -0
  33. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/iterable.py +0 -0
  34. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/chb_mit.py +0 -0
  35. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/mne.py +0 -0
  36. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/moabb.py +0 -0
  37. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/nmt.py +0 -0
  38. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/registry.py +0 -0
  39. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/siena.py +0 -0
  40. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  41. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/sleep_physionet.py +0 -0
  42. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/tuh.py +0 -0
  43. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/utils.py +0 -0
  44. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/xy.py +0 -0
  45. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/__init__.py +0 -0
  46. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/channel_utils.py +0 -0
  47. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/hub_formats.py +0 -0
  48. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/serialization.py +0 -0
  49. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/util.py +0 -0
  50. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/eegneuralnet.py +0 -0
  51. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/functional/__init__.py +0 -0
  52. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/functional/functions.py +0 -0
  53. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/functional/initialization.py +0 -0
  54. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/atcnet.py +0 -0
  55. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/attentionbasenet.py +0 -0
  56. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/attn_sleep.py +0 -0
  57. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/base.py +0 -0
  58. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/bendr.py +0 -0
  59. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/biot.py +0 -0
  60. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/brainmodule.py +0 -0
  61. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/cbramod.py +0 -0
  62. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/codebrain.py +0 -0
  63. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/config.py +0 -0
  64. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/contrawr.py +0 -0
  65. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/ctnet.py +0 -0
  66. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/deep4.py +0 -0
  67. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/deepsleepnet.py +0 -0
  68. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/dgcnn.py +0 -0
  69. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegconformer.py +0 -0
  70. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegdino.py +0 -0
  71. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eeginception_erp.py +0 -0
  72. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eeginception_mi.py +0 -0
  73. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegitnet.py +0 -0
  74. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegminer.py +0 -0
  75. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegnet.py +0 -0
  76. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegnex.py +0 -0
  77. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegpt.py +0 -0
  78. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegsimpleconv.py +0 -0
  79. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegsym.py +0 -0
  80. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegtcnet.py +0 -0
  81. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/emg2qwerty.py +0 -0
  82. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/fbcnet.py +0 -0
  83. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/fblightconvnet.py +0 -0
  84. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/fbmsnet.py +0 -0
  85. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/hybrid.py +0 -0
  86. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/ifnet.py +0 -0
  87. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/interpolated.py +0 -0
  88. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/labram.py +0 -0
  89. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/luna.py +0 -0
  90. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/medformer.py +0 -0
  91. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/meta_neuromotor.py +0 -0
  92. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/msvtnet.py +0 -0
  93. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/patchedtransformer.py +0 -0
  94. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/reve.py +0 -0
  95. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sccnet.py +0 -0
  96. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/shallow_fbcsp.py +0 -0
  97. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/signal_jepa.py +0 -0
  98. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sinc_shallow.py +0 -0
  99. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
  100. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
  101. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sparcnet.py +0 -0
  102. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sstdpn.py +0 -0
  103. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/syncnet.py +0 -0
  104. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/tcn.py +0 -0
  105. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/tidnet.py +0 -0
  106. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/tsinception.py +0 -0
  107. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/usleep.py +0 -0
  108. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/__init__.py +0 -0
  109. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/activation.py +0 -0
  110. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/attention.py +0 -0
  111. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/blocks.py +0 -0
  112. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/convolution.py +0 -0
  113. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/filter.py +0 -0
  114. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/interpolation.py +0 -0
  115. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/layers.py +0 -0
  116. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/linear.py +0 -0
  117. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/parametrization.py +0 -0
  118. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/stats.py +0 -0
  119. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/util.py +0 -0
  120. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/wrapper.py +0 -0
  121. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/__init__.py +0 -0
  122. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
  123. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/mne_preprocess.py +0 -0
  124. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/preprocess.py +0 -0
  125. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/util.py +0 -0
  126. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/windowers.py +0 -0
  127. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/regressor.py +0 -0
  128. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/samplers/__init__.py +0 -0
  129. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/samplers/base.py +0 -0
  130. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/samplers/ssl.py +0 -0
  131. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/training/__init__.py +0 -0
  132. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/training/callbacks.py +0 -0
  133. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/training/losses.py +0 -0
  134. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/training/scoring.py +0 -0
  135. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/util.py +0 -0
  136. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/__init__.py +0 -0
  137. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/attribution.py +0 -0
  138. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/confusion_matrices.py +0 -0
  139. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/frequency.py +0 -0
  140. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/metrics.py +0 -0
  141. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/sanity.py +0 -0
  142. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/topology.py +0 -0
  143. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode.egg-info/dependency_links.txt +0 -0
  144. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode.egg-info/requires.txt +0 -0
  145. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode.egg-info/top_level.txt +0 -0
  146. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/Makefile +0 -0
  147. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/_templates/autosummary/class.rst +0 -0
  148. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  149. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/_templates/autosummary/function.rst +0 -0
  150. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  151. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/cite.rst +0 -0
  152. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/conf.py +0 -0
  153. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/help.rst +0 -0
  154. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/index.rst +0 -0
  155. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/install/install.rst +0 -0
  156. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/install/install_pip.rst +0 -0
  157. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/install/install_source.rst +0 -0
  158. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/attention.rst +0 -0
  159. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/channel.rst +0 -0
  160. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/convolution.rst +0 -0
  161. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/filterbank.rst +0 -0
  162. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/gnn.rst +0 -0
  163. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/interpretable.rst +0 -0
  164. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/lbm.rst +0 -0
  165. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/recurrent.rst +0 -0
  166. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/spd.rst +0 -0
  167. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/models.rst +0 -0
  168. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/models_categorization.rst +0 -0
  169. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/models_table.rst +0 -0
  170. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/models_visualization.rst +0 -0
  171. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/sg_execution_times.rst +0 -0
  172. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/pyproject.toml +0 -0
  173. {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.6.1.dev1064
3
+ Version: 1.6.1.dev1065
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>
@@ -55,6 +55,7 @@ from .sleep_stager_chambon_2018 import SleepStagerChambon2018
55
55
  from .sparcnet import SPARCNet
56
56
  from .sstdpn import SSTDPN
57
57
  from .syncnet import SyncNet
58
+ from .tcformer import TCFormer
58
59
  from .tcn import BDTCN, TCN
59
60
  from .tidnet import TIDNet
60
61
  from .tsinception import TSception
@@ -130,6 +131,7 @@ __all__ = [
130
131
  "SPARCNet",
131
132
  "SyncNet",
132
133
  "BDTCN",
134
+ "TCFormer",
133
135
  "TCN",
134
136
  "TIDNet",
135
137
  "TSception",
@@ -56,4 +56,5 @@ 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
+ TCFormer,Motor Imagery,Classification,250,"n_chans, n_outputs, n_times",77820,"TCFormer(n_chans=22, n_outputs=4, n_times=1000)","Convolution,Attention/Transformer"
59
60
  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"
@@ -0,0 +1,643 @@
1
+ # Authors: Hamdi Altaheri <haltaheri@uwaterloo.ca> (original implementation)
2
+ # Bruno Aristimunha <b.aristimunha@gmail.com> (braindecode adaptation)
3
+ #
4
+ # License: MIT
5
+
6
+ from __future__ import annotations
7
+
8
+ import torch
9
+ from einops import rearrange, repeat
10
+ from einops.layers.torch import Rearrange
11
+ from torch import Tensor, nn
12
+
13
+ from braindecode.models.base import EEGModuleMixin
14
+ from braindecode.modules import CausalConv1d, Conv1dWithConstraint, DropPath
15
+
16
+
17
+ class TCFormer(EEGModuleMixin, nn.Module):
18
+ r"""TCFormer from Altaheri et al (2025) [tcformer]_.
19
+
20
+ :bdg-success:`Convolution` :bdg-info:`Attention/Transformer`
21
+
22
+ .. figure:: https://braindecode.org/dev/_static/model/tcformer.png
23
+ :align: center
24
+ :alt: TCFormer Architecture
25
+
26
+ Temporal Convolutional Transformer for EEG-based motor-imagery decoding. It
27
+ couples a multi-kernel convolutional front-end, a grouped-query Transformer
28
+ encoder with rotary positional embeddings, and a grouped temporal
29
+ convolutional network head, reaching state-of-the-art accuracy on BCIC IV-2a,
30
+ IV-2b and the High-Gamma Dataset while remaining compact (~78k parameters)
31
+ [tcformer]_.
32
+
33
+ .. rubric:: Architecture Overview
34
+
35
+ The raw trial ``(batch, n_chans, n_times)`` flows through three stages:
36
+ (1) :class:`_MultiKernelConvBlock` extracts multi-scale spatiotemporal
37
+ features and emits a short sequence of ``d_model`` tokens; (2) a stack of
38
+ :class:`_TransformerBlock` layers models global temporal context with
39
+ grouped-query attention and rotary embeddings; (3) the Transformer output is
40
+ reduced and concatenated with the convolutional tokens, then a grouped
41
+ :class:`_TCN` head with a per-group :class:`_ClassificationHead`
42
+ (``final_layer``) produces class logits.
43
+
44
+ .. rubric:: Macro Components
45
+
46
+ ``TCFormer.conv_block`` (Multi-Kernel Convolutional Embedding)
47
+ **Operations.** Parallel temporal convolutions with kernels
48
+ ``temp_kernel_lengths`` (each ``n_filters_time`` filters, batch-normed),
49
+ concatenated, then a depthwise spatial convolution over electrodes
50
+ (``depth_multiplier``), average pooling, grouped 1x1 channel reduction to
51
+ ``d_model = group_dim * n_groups``, a grouped temporal convolution, a
52
+ grouped squeeze-and-excitation gate, and a second pooling.
53
+ **Role.** Turns the raw montage into ``Tc`` compact feature tokens, one
54
+ group per temporal kernel, encoding band-specific rhythms.
55
+
56
+ ``TCFormer.transformer`` (Grouped-Query Transformer Encoder)
57
+ **Operations.** ``n_transformer_layers`` pre-norm blocks, each applying
58
+ grouped-query self-attention (``q_heads`` queries sharing ``kv_heads``
59
+ key/value groups) with rotary positional embedding, then a position-wise
60
+ MLP (expansion ``mlp_ratio``); residual connections use a quadratic
61
+ DropPath schedule up to ``drop_path_max``.
62
+ **Role.** Adds global temporal context efficiently (fewer K/V projections
63
+ than full multi-head attention).
64
+
65
+ ``TCFormer.tcn`` + ``TCFormer.final_layer`` (Grouped TCN Head)
66
+ **Operations.** The reduced Transformer output is concatenated with the
67
+ convolutional tokens (``d_fused = group_dim * (n_groups + 1)`` channels,
68
+ ``n_groups + 1`` groups), processed by ``tcn_depth`` dilated causal
69
+ residual blocks (kernel ``tcn_kernel_length``, dilations ``2**i``), the
70
+ last timestep is taken, and a grouped 1x1 conv produces per-group logits
71
+ averaged across groups.
72
+ **Role.** Long-range causal temporal decoding and read-out.
73
+
74
+ .. rubric:: Temporal, Spatial, and Spectral Encoding
75
+
76
+ - **Temporal:** multi-kernel and dilated-causal convolutions plus rotary
77
+ self-attention capture short- and long-range temporal dependencies.
78
+ - **Spatial:** a depthwise convolution spanning all electrodes mixes channels
79
+ per feature map.
80
+ - **Spectral:** the three temporal kernel sizes target distinct EEG bands
81
+ (short kernels -> high frequencies, long kernels -> low frequencies).
82
+
83
+ .. rubric:: Additional Mechanisms
84
+
85
+ - Grouped-query attention and rotary embeddings reduce attention cost while
86
+ preserving relative-position information.
87
+ - Group structure is preserved end-to-end: each temporal kernel forms a
88
+ channel group that stays separated through the grouped reductions, grouped
89
+ TCN, and per-group classifier.
90
+
91
+ Parameters
92
+ ----------
93
+ n_filters_time : int, optional
94
+ Number of temporal filters per kernel (``F1``). Default ``32``.
95
+ temp_kernel_lengths : tuple of int, optional
96
+ Temporal kernel lengths; their count is the number of feature groups.
97
+ Default ``(20, 32, 64)``.
98
+ depth_multiplier : int, optional
99
+ Depthwise spatial expansion factor (``D``). Default ``2``.
100
+ pool_length_1, pool_length_2 : int, optional
101
+ Average-pool factors after the depthwise and the second temporal conv.
102
+ Defaults ``8`` and ``7``.
103
+ temp_kernel_length_2 : int, optional
104
+ Kernel length of the grouped second temporal convolution. Default ``16``.
105
+ group_dim : int, optional
106
+ Channels per group (``d_group``); ``d_model = group_dim * n_groups``.
107
+ Default ``16``.
108
+ se_reduction : int, optional
109
+ Reduction ratio of the grouped squeeze-and-excitation block. Default ``4``.
110
+ n_transformer_layers : int, optional
111
+ Number of encoder layers (``N``). Default ``2`` (the 77.8k-parameter
112
+ headline configuration). Use ``5`` for the deeper ~131k variant.
113
+ q_heads, kv_heads : int, optional
114
+ Number of query heads and key/value groups for grouped-query attention
115
+ (``q_heads`` must be divisible by ``kv_heads``). Defaults ``4`` and ``2``.
116
+ mlp_ratio : int, optional
117
+ Feed-forward expansion ratio in each encoder block. Default ``2``.
118
+ drop_path_max : float, optional
119
+ Maximum stochastic-depth rate (quadratic schedule over depth). Default
120
+ ``0.25``.
121
+ tcn_depth : int, optional
122
+ Number of TCN residual blocks (``L``). Default ``2``.
123
+ tcn_kernel_length : int, optional
124
+ Kernel length of the TCN causal convolutions (``KT``). Default ``4``.
125
+ classifier_max_norm : float, optional
126
+ Max-norm constraint on the classifier convolution weights. Default
127
+ ``0.25``.
128
+ drop_prob_conv, drop_prob_trans, drop_prob_tcn : float, optional
129
+ Dropout probabilities of the conv front-end, the Transformer, and the TCN
130
+ head. Defaults ``0.4``, ``0.4``, ``0.3``.
131
+ activation : nn.Module, optional
132
+ Activation class for the convolutional and TCN blocks. Default
133
+ :class:`torch.nn.ELU`.
134
+ activation_ffn : nn.Module, optional
135
+ Activation class for the Transformer feed-forward sublayer. Default
136
+ :class:`torch.nn.GELU`.
137
+
138
+ Notes
139
+ -----
140
+ This implementation is adapted from the original source code [tcformercode]_
141
+ to comply with braindecode's model conventions. The default configuration
142
+ reproduces the paper's headline (Table 1) setup; the released training config
143
+ additionally uses Adam (lr 0.0009, weight_decay 1e-3), linear warm-up + cosine
144
+ decay, per-channel z-scoring, and segmentation-and-reconstruction augmentation
145
+ (handled outside the model, in the training pipeline).
146
+
147
+ .. versionadded:: 1.6.1
148
+
149
+ References
150
+ ----------
151
+ .. [tcformer] Altaheri, H., Karray, F., & Karimi, A.-H. (2025). Temporal
152
+ convolutional transformer for EEG based motor imagery decoding. Scientific
153
+ Reports, 15, 32959. https://doi.org/10.1038/s41598-025-16219-7
154
+ .. [tcformercode] Altaheri, H. (2025). TCFormer source code.
155
+ https://github.com/Altaheri/TCFormer
156
+ """
157
+
158
+ def __init__(
159
+ self,
160
+ # Signal related parameters
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
+ # Model parameters
168
+ n_filters_time: int = 32,
169
+ temp_kernel_lengths: tuple[int, ...] = (20, 32, 64),
170
+ depth_multiplier: int = 2,
171
+ pool_length_1: int = 8,
172
+ pool_length_2: int = 7,
173
+ temp_kernel_length_2: int = 16,
174
+ group_dim: int = 16,
175
+ se_reduction: int = 4,
176
+ n_transformer_layers: int = 2,
177
+ q_heads: int = 4,
178
+ kv_heads: int = 2,
179
+ mlp_ratio: int = 2,
180
+ drop_path_max: float = 0.25,
181
+ tcn_depth: int = 2,
182
+ tcn_kernel_length: int = 4,
183
+ classifier_max_norm: float = 0.25,
184
+ drop_prob_conv: float = 0.4,
185
+ drop_prob_trans: float = 0.4,
186
+ drop_prob_tcn: float = 0.3,
187
+ activation: type[nn.Module] = nn.ELU,
188
+ activation_ffn: type[nn.Module] = nn.GELU,
189
+ ):
190
+ super().__init__(
191
+ n_outputs=n_outputs,
192
+ n_chans=n_chans,
193
+ chs_info=chs_info,
194
+ n_times=n_times,
195
+ input_window_seconds=input_window_seconds,
196
+ sfreq=sfreq,
197
+ )
198
+ del n_outputs, n_chans, chs_info, n_times, input_window_seconds, sfreq
199
+
200
+ n_groups = len(temp_kernel_lengths)
201
+ d_model = group_dim * n_groups
202
+ d_fused = group_dim * (n_groups + 1)
203
+
204
+ self.conv_block = _MultiKernelConvBlock(
205
+ self.n_chans,
206
+ temp_kernel_lengths,
207
+ n_filters_time,
208
+ depth_multiplier,
209
+ pool_length_1,
210
+ pool_length_2,
211
+ temp_kernel_length_2,
212
+ drop_prob_conv,
213
+ group_dim,
214
+ se_reduction,
215
+ activation,
216
+ )
217
+
218
+ self.mix = nn.Sequential(
219
+ nn.Conv1d(d_model, d_model, 1, bias=False),
220
+ nn.BatchNorm1d(d_model),
221
+ nn.SiLU(),
222
+ )
223
+ self.to_tokens = Rearrange("batch feat time -> batch time feat")
224
+
225
+ drop_rates = torch.linspace(0, 1, n_transformer_layers) ** 2 * drop_path_max
226
+ self.transformer = nn.ModuleList(
227
+ [
228
+ _TransformerBlock(
229
+ d_model,
230
+ q_heads,
231
+ kv_heads,
232
+ mlp_ratio,
233
+ drop_prob_trans,
234
+ float(drop_rates[i]),
235
+ activation_ffn,
236
+ )
237
+ for i in range(n_transformer_layers)
238
+ ]
239
+ )
240
+
241
+ # RoPE tables are built per forward from the actual token length.
242
+ self.head_dim = d_model // q_heads
243
+
244
+ self.reduce = nn.Sequential(
245
+ Rearrange("batch time feat -> batch feat time"),
246
+ nn.Conv1d(d_model, group_dim, 1, bias=False),
247
+ nn.BatchNorm1d(group_dim),
248
+ nn.SiLU(),
249
+ )
250
+
251
+ self.tcn = _TCN(
252
+ tcn_depth,
253
+ tcn_kernel_length,
254
+ d_fused,
255
+ n_groups + 1,
256
+ drop_prob_tcn,
257
+ activation,
258
+ )
259
+ self.final_layer = _ClassificationHead(
260
+ d_fused, n_groups + 1, self.n_outputs, classifier_max_norm
261
+ )
262
+ self.apply(self._init_weights)
263
+
264
+ @staticmethod
265
+ def _init_weights(module: nn.Module) -> None:
266
+ if isinstance(module, (nn.Conv1d, nn.Conv2d, nn.Linear)):
267
+ params = getattr(module, "parametrizations", None)
268
+ if params is not None and "weight" in params:
269
+ # max-norm layers expose a computed .weight; init the leaf
270
+ nn.init.xavier_uniform_(params.weight.original)
271
+ elif isinstance(getattr(module, "weight", None), nn.Parameter):
272
+ nn.init.xavier_uniform_(module.weight)
273
+ if getattr(module, "bias", None) is not None:
274
+ nn.init.constant_(module.bias, 0.0)
275
+
276
+ def forward(self, x: Tensor) -> Tensor:
277
+ conv_features = self.conv_block(x) # (batch, d_model, n_tokens)
278
+ tokens = self.to_tokens(self.mix(conv_features)) # (batch, n_tokens, d_model)
279
+ seq_len = tokens.shape[1]
280
+ cos, sin = _build_rotary_cache(
281
+ self.head_dim, seq_len, device=tokens.device, dtype=tokens.dtype
282
+ )
283
+ for block in self.transformer:
284
+ tokens = block(tokens, cos, sin)
285
+ transformer_features = self.reduce(tokens) # (batch, group_dim, n_tokens)
286
+ fused = torch.cat(
287
+ (conv_features, transformer_features), dim=1
288
+ ) # (batch, d_fused, n_tokens)
289
+ decoded = self.tcn(fused)
290
+ last_step = decoded[:, :, -1:] # (batch, d_fused, 1)
291
+ return self.final_layer(last_step) # (batch, n_outputs)
292
+
293
+
294
+ # ----------------------------------------------------------------------------- #
295
+ # Rotary positional embedding (verbatim port of the reference; see plan note on
296
+ # the deliberate NeoX-cache / interleaved-rotate mismatch).
297
+ def _build_rotary_cache(
298
+ head_dim: int, seq_len: int, device=None, dtype=None
299
+ ) -> tuple[Tensor, Tensor]:
300
+ """Return ``(cos, sin)`` each of shape ``(seq_len, head_dim)``.
301
+
302
+ Angles are computed in float32 for precision; the returned cos/sin are
303
+ cast to ``dtype`` (the token dtype) so attention stays in the working
304
+ precision under autocast.
305
+ """
306
+ theta = 1.0 / (
307
+ 10000 ** (torch.arange(0, head_dim, 2, device=device).float() / head_dim)
308
+ )
309
+ seq_idx = torch.arange(seq_len, device=device).float()
310
+ freqs = torch.outer(seq_idx, theta) # (seq_len, head_dim/2)
311
+ emb = torch.cat((freqs, freqs), dim=-1) # (seq_len, head_dim)
312
+ cos, sin = emb.cos(), emb.sin()
313
+ if dtype is not None:
314
+ cos, sin = cos.to(dtype), sin.to(dtype)
315
+ return cos, sin
316
+
317
+
318
+ def _apply_rope(
319
+ query: Tensor, key: Tensor, cos: Tensor, sin: Tensor
320
+ ) -> tuple[Tensor, Tensor]:
321
+ """Apply rotary embedding to ``query`` and ``key``.
322
+
323
+ Both have shape ``(batch, n_heads, seq_len, head_dim)``.
324
+ """
325
+
326
+ def rotate(tensor: Tensor) -> Tensor:
327
+ even = tensor[..., 0::2]
328
+ odd = tensor[..., 1::2]
329
+ pairs = torch.stack((-odd, even), dim=-1)
330
+ return rearrange(pairs, "... half two -> ... (half two)")
331
+
332
+ query_rotated = (query * cos) + (rotate(query) * sin)
333
+ key_rotated = (key * cos) + (rotate(key) * sin)
334
+ return query_rotated, key_rotated
335
+
336
+
337
+ # ----------------------------------------------------------------------------- #
338
+ class _GroupedQueryAttention(nn.Module):
339
+ """Grouped-query self-attention (``q_heads >= kv_heads``) with RoPE."""
340
+
341
+ def __init__(
342
+ self, d_model: int, q_heads: int, kv_heads: int, drop_prob: float = 0.4
343
+ ):
344
+ super().__init__()
345
+ assert d_model % q_heads == 0, "d_model must be divisible by q_heads"
346
+ assert q_heads % kv_heads == 0, "q_heads must be a multiple of kv_heads"
347
+ self.n_query_heads = q_heads
348
+ self.n_kv_heads = kv_heads
349
+ self.head_dim = d_model // q_heads
350
+ assert self.head_dim % 2 == 0, (
351
+ "head_dim (d_model // q_heads) must be even for rotary embeddings"
352
+ )
353
+ self.scale = self.head_dim**-0.5
354
+ self.query_proj = nn.Linear(d_model, d_model, bias=False)
355
+ self.key_value_proj = nn.Linear(
356
+ d_model, 2 * kv_heads * self.head_dim, bias=False
357
+ )
358
+ self.out_proj = nn.Linear(d_model, d_model, bias=False)
359
+ self.drop = nn.Dropout(drop_prob)
360
+
361
+ def forward(self, x: Tensor, cos: Tensor, sin: Tensor) -> Tensor:
362
+ seq_len = x.shape[1]
363
+ query = rearrange(
364
+ self.query_proj(x),
365
+ "batch seq (heads dim) -> batch heads seq dim",
366
+ heads=self.n_query_heads,
367
+ )
368
+ key, value = rearrange(
369
+ self.key_value_proj(x),
370
+ "batch seq (heads two dim) -> two batch heads seq dim",
371
+ two=2,
372
+ heads=self.n_kv_heads,
373
+ )
374
+ # share each key/value group across its query heads
375
+ groups_per_kv = self.n_query_heads // self.n_kv_heads
376
+ key = repeat(
377
+ key,
378
+ "batch heads seq dim -> batch (heads repeats) seq dim",
379
+ repeats=groups_per_kv,
380
+ )
381
+ value = repeat(
382
+ value,
383
+ "batch heads seq dim -> batch (heads repeats) seq dim",
384
+ repeats=groups_per_kv,
385
+ )
386
+ query, key = _apply_rope(query, key, cos[:seq_len], sin[:seq_len])
387
+ key_t = rearrange(key, "batch heads seq dim -> batch heads dim seq")
388
+ attention = ((query @ key_t) * self.scale).softmax(dim=-1)
389
+ attention = self.drop(attention)
390
+ context = rearrange(
391
+ attention @ value, "batch heads seq dim -> batch seq (heads dim)"
392
+ )
393
+ return self.out_proj(context)
394
+
395
+
396
+ # ----------------------------------------------------------------------------- #
397
+ class _TransformerBlock(nn.Module):
398
+ """Pre-norm encoder block: GQA + position-wise MLP, both with DropPath."""
399
+
400
+ def __init__(
401
+ self,
402
+ d_model: int,
403
+ q_heads: int,
404
+ kv_heads: int,
405
+ mlp_ratio: int = 2,
406
+ drop_prob: float = 0.4,
407
+ drop_path_rate: float = 0.0,
408
+ activation: type[nn.Module] = nn.GELU,
409
+ ):
410
+ super().__init__()
411
+ self.norm1 = nn.LayerNorm(d_model)
412
+ self.attn = _GroupedQueryAttention(d_model, q_heads, kv_heads, drop_prob)
413
+ self.drop_path = DropPath(drop_path_rate)
414
+ self.norm2 = nn.LayerNorm(d_model)
415
+ self.mlp = nn.Sequential(
416
+ nn.Linear(d_model, mlp_ratio * d_model),
417
+ activation(),
418
+ nn.Linear(mlp_ratio * d_model, d_model),
419
+ nn.Dropout(drop_prob),
420
+ )
421
+
422
+ def forward(self, x: Tensor, cos: Tensor, sin: Tensor) -> Tensor:
423
+ x = x + self.drop_path(self.attn(self.norm1(x), cos, sin))
424
+ x = x + self.drop_path(self.mlp(self.norm2(x)))
425
+ return x
426
+
427
+
428
+ # ----------------------------------------------------------------------------- #
429
+ class _ChannelGroupAttention(nn.Module):
430
+ """Grouped Squeeze-and-Excitation: one sigmoid gate per channel group."""
431
+
432
+ def __init__(self, in_channels: int, num_groups: int, reduction: int = 4):
433
+ super().__init__()
434
+ assert in_channels % num_groups == 0
435
+ self.in_channels = in_channels
436
+ self.num_groups = num_groups
437
+ self.group_size = in_channels // num_groups
438
+ self.pool = nn.AdaptiveAvgPool2d(1)
439
+ self.att_fc1 = nn.Conv2d(
440
+ in_channels, in_channels // reduction, 1, groups=num_groups, bias=False
441
+ )
442
+ self.att_fc2 = nn.Conv2d(
443
+ in_channels // reduction, num_groups, 1, groups=num_groups, bias=False
444
+ )
445
+ self.relu = nn.ReLU()
446
+ self.sigmoid = nn.Sigmoid()
447
+
448
+ def forward(self, x: Tensor) -> Tensor:
449
+ descriptor = self.pool(x)
450
+ gate = self.sigmoid(self.att_fc2(self.relu(self.att_fc1(descriptor))))
451
+ grouped = rearrange(
452
+ x,
453
+ "batch (groups chans) height width -> batch groups chans height width",
454
+ groups=self.num_groups,
455
+ )
456
+ gate = rearrange(
457
+ gate, "batch groups height width -> batch groups 1 height width"
458
+ )
459
+ return rearrange(
460
+ grouped * gate,
461
+ "batch groups chans height width -> batch (groups chans) height width",
462
+ )
463
+
464
+
465
+ # ----------------------------------------------------------------------------- #
466
+ class _MultiKernelConvBlock(nn.Module):
467
+ """Multi-kernel temporal + depthwise spatial CNN producing feature tokens."""
468
+
469
+ def __init__(
470
+ self,
471
+ n_chans: int,
472
+ temp_kernel_lengths: tuple[int, ...],
473
+ n_filters_time: int,
474
+ depth_multiplier: int,
475
+ pool_length_1: int,
476
+ pool_length_2: int,
477
+ temp_kernel_length_2: int,
478
+ drop_prob: float,
479
+ group_dim: int,
480
+ se_reduction: int,
481
+ activation: type[nn.Module],
482
+ ):
483
+ super().__init__()
484
+ n_groups = len(temp_kernel_lengths)
485
+ self.d_model = group_dim * n_groups
486
+ self.rearrange = Rearrange("batch nchans time -> batch 1 nchans time")
487
+
488
+ # one temporal conv per kernel ('same' time padding via ConstantPad2d),
489
+ # BN only -- NO activation here (matches reference).
490
+ self.temporal_convs = nn.ModuleList(
491
+ [
492
+ nn.Sequential(
493
+ nn.ConstantPad2d(
494
+ (k // 2 - 1, k // 2, 0, 0)
495
+ if k % 2 == 0
496
+ else (k // 2, k // 2, 0, 0),
497
+ 0,
498
+ ),
499
+ nn.Conv2d(1, n_filters_time, (1, k), bias=False),
500
+ nn.BatchNorm2d(n_filters_time),
501
+ )
502
+ for k in temp_kernel_lengths
503
+ ]
504
+ )
505
+
506
+ in_dw = n_filters_time * n_groups
507
+ f2 = in_dw * depth_multiplier
508
+ self.channel_dw_conv = nn.Sequential(
509
+ nn.Conv2d(in_dw, f2, (n_chans, 1), bias=False, groups=in_dw),
510
+ nn.BatchNorm2d(f2),
511
+ activation(),
512
+ )
513
+ self.pool1 = nn.AvgPool2d((1, pool_length_1))
514
+ self.drop1 = nn.Dropout(drop_prob)
515
+
516
+ # grouped 1x1 channel reduction f2 -> d_model (one group per kernel)
517
+ self.use_channel_reduction = self.d_model != f2
518
+ if self.use_channel_reduction:
519
+ self.channel_reduction = nn.Sequential(
520
+ nn.Conv2d(f2, self.d_model, (1, 1), bias=False, groups=n_groups),
521
+ nn.BatchNorm2d(self.d_model),
522
+ )
523
+
524
+ self.temporal_conv_2 = nn.Sequential(
525
+ nn.Conv2d(
526
+ self.d_model,
527
+ self.d_model,
528
+ (1, temp_kernel_length_2),
529
+ padding="same",
530
+ bias=False,
531
+ groups=n_groups,
532
+ ),
533
+ nn.BatchNorm2d(self.d_model),
534
+ activation(),
535
+ )
536
+
537
+ self.use_group_attn = n_groups > 1
538
+ if self.use_group_attn:
539
+ self.group_attn = _ChannelGroupAttention(
540
+ self.d_model, n_groups, se_reduction
541
+ )
542
+
543
+ self.pool2 = nn.AvgPool2d((1, pool_length_2))
544
+ self.drop2 = nn.Dropout(drop_prob)
545
+
546
+ def forward(self, x: Tensor) -> Tensor:
547
+ x = self.rearrange(x)
548
+ x = torch.cat([conv(x) for conv in self.temporal_convs], dim=1)
549
+ x = self.channel_dw_conv(x)
550
+ x = self.drop1(self.pool1(x))
551
+ if self.use_channel_reduction:
552
+ x = self.channel_reduction(x)
553
+ x = self.temporal_conv_2(x)
554
+ if self.use_group_attn:
555
+ x = x + self.group_attn(x)
556
+ x = self.drop2(self.pool2(x))
557
+ # drop the singleton spatial axis -> (batch, d_model, n_tokens)
558
+ return rearrange(x, "batch feat 1 time -> batch feat time")
559
+
560
+
561
+ # ----------------------------------------------------------------------------- #
562
+ class _TCNResidualBlock(nn.Module):
563
+ """Two grouped dilated-causal convs (BN + activation + dropout) + residual."""
564
+
565
+ def __init__(
566
+ self,
567
+ n_filters: int,
568
+ kernel_length: int,
569
+ dilation: int,
570
+ n_groups: int,
571
+ drop_prob: float,
572
+ activation: type[nn.Module],
573
+ ):
574
+ super().__init__()
575
+ self.conv1 = CausalConv1d(
576
+ n_filters, n_filters, kernel_length, dilation=dilation, groups=n_groups
577
+ )
578
+ self.bn1 = nn.BatchNorm1d(n_filters)
579
+ self.act1 = activation()
580
+ self.drop1 = nn.Dropout(drop_prob)
581
+ self.conv2 = CausalConv1d(
582
+ n_filters, n_filters, kernel_length, dilation=dilation, groups=n_groups
583
+ )
584
+ self.bn2 = nn.BatchNorm1d(n_filters)
585
+ self.act2 = activation()
586
+ self.drop2 = nn.Dropout(drop_prob)
587
+ self.act3 = activation()
588
+
589
+ def forward(self, x: Tensor) -> Tensor:
590
+ out = self.drop1(self.act1(self.bn1(self.conv1(x))))
591
+ out = self.drop2(self.act2(self.bn2(self.conv2(out))))
592
+ return self.act3(x + out)
593
+
594
+
595
+ class _TCN(nn.Module):
596
+ """Stack of ``depth`` residual blocks with exponentially growing dilation."""
597
+
598
+ def __init__(
599
+ self,
600
+ depth: int,
601
+ kernel_length: int,
602
+ n_filters: int,
603
+ n_groups: int,
604
+ drop_prob: float,
605
+ activation: type[nn.Module],
606
+ ):
607
+ super().__init__()
608
+ self.blocks = nn.ModuleList(
609
+ [
610
+ _TCNResidualBlock(
611
+ n_filters, kernel_length, 2**i, n_groups, drop_prob, activation
612
+ )
613
+ for i in range(depth)
614
+ ]
615
+ )
616
+
617
+ def forward(self, x: Tensor) -> Tensor:
618
+ for blk in self.blocks:
619
+ x = blk(x)
620
+ return x
621
+
622
+
623
+ # ----------------------------------------------------------------------------- #
624
+ class _ClassificationHead(nn.Module):
625
+ """Grouped 1x1 conv producing per-group logits, averaged across groups."""
626
+
627
+ def __init__(
628
+ self, d_features: int, n_groups: int, n_outputs: int, max_norm: float = 0.25
629
+ ):
630
+ super().__init__()
631
+ self.n_groups = n_groups
632
+ self.n_outputs = n_outputs
633
+ self.conv = Conv1dWithConstraint(
634
+ d_features,
635
+ n_outputs * n_groups,
636
+ kernel_size=1,
637
+ groups=n_groups,
638
+ max_norm=max_norm,
639
+ )
640
+
641
+ def forward(self, x: Tensor) -> Tensor:
642
+ x = self.conv(x).squeeze(-1) # (B, n_outputs*n_groups)
643
+ return x.view(x.size(0), self.n_groups, self.n_outputs).mean(dim=1)
@@ -360,6 +360,7 @@ models_mandatory_parameters: list[
360
360
  ("MSVTNet", ["n_chans", "n_outputs", "n_times"], None),
361
361
  ("EEGMiner", ["n_chans", "n_outputs", "n_times", "sfreq"], {"sfreq": 200.0}),
362
362
  ("CTNet", ["n_chans", "n_outputs", "n_times"], None),
363
+ ("TCFormer", ["n_chans", "n_outputs", "n_times"], None),
363
364
  ("SincShallowNet", ["n_chans", "n_outputs", "n_times", "sfreq"], {"sfreq": 250.0}),
364
365
  ("SCCNet", ["n_chans", "n_outputs", "n_times", "sfreq"], {"sfreq": 200.0}),
365
366
  ("SignalJEPA", ["chs_info"], None),
@@ -0,0 +1 @@
1
+ __version__ = "1.6.1.dev1065"