braindecode 1.6.1.dev1048__tar.gz → 1.6.1.dev1049__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.
- {braindecode-1.6.1.dev1048/braindecode.egg-info → braindecode-1.6.1.dev1049}/PKG-INFO +1 -1
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/moabb.py +0 -10
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/__init__.py +3 -2
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/attn_sleep.py +0 -10
- braindecode-1.6.1.dev1049/braindecode/models/eegdino.py +533 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eegnet.py +1 -10
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/summary.csv +1 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/tsinception.py +1 -11
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/util.py +1 -2
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/__init__.py +2 -1
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/blocks.py +82 -0
- braindecode-1.6.1.dev1049/braindecode/version.py +1 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049/braindecode.egg-info}/PKG-INFO +1 -1
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode.egg-info/SOURCES.txt +1 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/api.rst +2 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/whats_new.rst +8 -1
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/pyproject.toml +6 -0
- braindecode-1.6.1.dev1048/braindecode/version.py +0 -1
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/LICENSE.txt +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/MANIFEST.in +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/NOTICE.txt +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/README.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/augmentation/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/augmentation/base.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/augmentation/functional.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/augmentation/transforms.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/classifier.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/base.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bbci.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bcicomp.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bids/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bids/datasets.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bids/format.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bids/hub.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bids/hub_format.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bids/hub_io.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bids/hub_validation.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/bids/iterable.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/chb_mit.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/mne.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/nmt.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/registry.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/siena.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/sleep_physionet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/tuh.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/utils.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datasets/xy.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datautil/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datautil/channel_utils.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datautil/hub_formats.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datautil/serialization.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/datautil/util.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/eegneuralnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/functional/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/functional/functions.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/functional/initialization.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/atcnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/attentionbasenet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/base.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/bendr.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/biot.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/brainmodule.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/cbramod.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/codebrain.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/config.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/contrawr.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/ctnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/deep4.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/deepsleepnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/dgcnn.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eegconformer.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eeginception_erp.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eeginception_mi.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eegitnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eegminer.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eegnex.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eegpt.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eegsimpleconv.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eegsym.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/eegtcnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/emg2qwerty.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/fbcnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/fblightconvnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/fbmsnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/hybrid.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/ifnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/interpolated.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/labram.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/luna.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/medformer.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/meta_neuromotor.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/msvtnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/patchedtransformer.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/reve.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/sccnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/shallow_fbcsp.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/signal_jepa.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/sinc_shallow.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/sparcnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/sstdpn.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/syncnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/tcn.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/tidnet.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/models/usleep.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/activation.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/attention.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/convolution.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/filter.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/interpolation.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/layers.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/linear.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/parametrization.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/stats.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/util.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/modules/wrapper.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/preprocessing/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/preprocessing/mne_preprocess.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/preprocessing/preprocess.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/preprocessing/util.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/preprocessing/windowers.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/regressor.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/samplers/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/samplers/base.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/samplers/ssl.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/training/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/training/callbacks.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/training/losses.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/training/scoring.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/util.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/visualization/__init__.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/visualization/attribution.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/visualization/confusion_matrices.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/visualization/frequency.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/visualization/metrics.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/visualization/sanity.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode/visualization/topology.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode.egg-info/dependency_links.txt +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode.egg-info/requires.txt +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/braindecode.egg-info/top_level.txt +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/Makefile +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/_templates/autosummary/class.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/_templates/autosummary/function.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/cite.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/conf.py +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/help.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/index.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/install/install.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/install/install_pip.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/install/install_source.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/categorization/attention.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/categorization/channel.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/categorization/convolution.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/categorization/filterbank.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/categorization/gnn.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/categorization/interpretable.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/categorization/lbm.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/categorization/recurrent.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/categorization/spd.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/models.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/models_categorization.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/models_table.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/models/models_visualization.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/docs/sg_execution_times.rst +0 -0
- {braindecode-1.6.1.dev1048 → braindecode-1.6.1.dev1049}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: braindecode
|
|
3
|
-
Version: 1.6.1.
|
|
3
|
+
Version: 1.6.1.dev1049
|
|
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>
|
|
@@ -15,7 +15,6 @@ from typing import Any
|
|
|
15
15
|
|
|
16
16
|
import mne
|
|
17
17
|
import pandas as pd
|
|
18
|
-
from mne.utils import deprecated
|
|
19
18
|
|
|
20
19
|
from braindecode.util import _update_moabb_docstring
|
|
21
20
|
|
|
@@ -208,12 +207,3 @@ class HGD(MOABBDataset):
|
|
|
208
207
|
|
|
209
208
|
def __init__(self, subject_ids):
|
|
210
209
|
super().__init__("Schirrmeister2017", subject_ids=subject_ids)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
@deprecated(
|
|
214
|
-
"`BNCI2014001` was renamed to `BNCI2014_001` in v1.13; this alias will be removed in v1.14."
|
|
215
|
-
)
|
|
216
|
-
class BNCI2014001(BNCI2014_001):
|
|
217
|
-
"""Deprecated alias for BNCI2014001."""
|
|
218
|
-
|
|
219
|
-
pass
|
|
@@ -15,11 +15,12 @@ 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
|
|
21
22
|
from .eegminer import EEGMiner
|
|
22
|
-
from .eegnet import EEGNet
|
|
23
|
+
from .eegnet import EEGNet
|
|
23
24
|
from .eegnex import EEGNeX
|
|
24
25
|
from .eegpt import EEGPT, InterpolatedEEGPT
|
|
25
26
|
from .eegsimpleconv import EEGSimpleConv
|
|
@@ -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",
|
|
@@ -91,7 +93,6 @@ __all__ = [
|
|
|
91
93
|
"EEGITNet",
|
|
92
94
|
"EEGMiner",
|
|
93
95
|
"EEGNet",
|
|
94
|
-
"EEGNetv4",
|
|
95
96
|
"EEGNeX",
|
|
96
97
|
"EEGSym",
|
|
97
98
|
"EEGSimpleConv",
|
|
@@ -8,7 +8,6 @@ from copy import deepcopy
|
|
|
8
8
|
|
|
9
9
|
import torch
|
|
10
10
|
import torch.nn.functional as F
|
|
11
|
-
from mne.utils import deprecated
|
|
12
11
|
from torch import nn
|
|
13
12
|
|
|
14
13
|
from braindecode.models.base import EEGModuleMixin
|
|
@@ -538,12 +537,3 @@ class _PositionwiseFeedForward(nn.Module):
|
|
|
538
537
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
539
538
|
"""Implements FFN equation."""
|
|
540
539
|
return self.w_2(self.dropout(self.activate(self.w_1(x))))
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
@deprecated(
|
|
544
|
-
"`SleepStagerEldele2021` was renamed to `AttnSleep` in v1.12 to follow original author's name; this alias will be removed in v1.14."
|
|
545
|
-
)
|
|
546
|
-
class SleepStagerEldele2021(AttnSleep):
|
|
547
|
-
r"""Deprecated alias for SleepStagerEldele2021."""
|
|
548
|
-
|
|
549
|
-
pass
|
|
@@ -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)
|
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
from typing import Dict, Optional
|
|
7
7
|
|
|
8
8
|
from einops.layers.torch import Rearrange
|
|
9
|
-
from mne.utils import
|
|
9
|
+
from mne.utils import warn
|
|
10
10
|
from torch import nn
|
|
11
11
|
|
|
12
12
|
from braindecode.functional import glorot_weight_zero_bias
|
|
@@ -348,12 +348,3 @@ class EEGNet(EEGModuleMixin, nn.Sequential):
|
|
|
348
348
|
self.add_module("final_layer", module)
|
|
349
349
|
|
|
350
350
|
glorot_weight_zero_bias(self)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
@deprecated(
|
|
354
|
-
"`EEGNetv4` was renamed to `EEGNet` in v1.12; this alias will be removed in v1.14."
|
|
355
|
-
)
|
|
356
|
-
class EEGNetv4(EEGNet):
|
|
357
|
-
r"""Deprecated alias for EEGNet."""
|
|
358
|
-
|
|
359
|
-
pass
|
|
@@ -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"
|
|
@@ -7,7 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
import torch
|
|
8
8
|
import torch.nn as nn
|
|
9
9
|
from einops.layers.torch import Rearrange
|
|
10
|
-
from mne.utils import
|
|
10
|
+
from mne.utils import warn
|
|
11
11
|
|
|
12
12
|
from braindecode.models.base import EEGModuleMixin
|
|
13
13
|
|
|
@@ -283,13 +283,3 @@ class TSception(EEGModuleMixin, nn.Module):
|
|
|
283
283
|
activation(),
|
|
284
284
|
nn.AvgPool2d(kernel_size=(1, pool_size), stride=(1, pool_size)),
|
|
285
285
|
)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
@deprecated(
|
|
289
|
-
"`TSceptionV1` was renamed to `TSception` in v1.12; "
|
|
290
|
-
"this alias will be removed in v1.14."
|
|
291
|
-
)
|
|
292
|
-
class TSceptionV1(TSception):
|
|
293
|
-
r"""Deprecated alias for TSception."""
|
|
294
|
-
|
|
295
|
-
pass
|
|
@@ -189,8 +189,6 @@ def _init_models_dict():
|
|
|
189
189
|
issubclass(m[1], models.base.EEGModuleMixin)
|
|
190
190
|
and m[1] != models.base.EEGModuleMixin
|
|
191
191
|
):
|
|
192
|
-
if m[1].__name__ == "EEGNetv4":
|
|
193
|
-
continue
|
|
194
192
|
models_dict[m[0]] = m[1]
|
|
195
193
|
|
|
196
194
|
|
|
@@ -434,6 +432,7 @@ models_mandatory_parameters: list[
|
|
|
434
432
|
{"n_chans": 19, "n_times": 6000},
|
|
435
433
|
),
|
|
436
434
|
("DGCNN", ["n_chans", "n_outputs", "n_times", "chs_info"], None),
|
|
435
|
+
("EEGDINO", ["n_chans", "n_outputs", "n_times"], None),
|
|
437
436
|
]
|
|
438
437
|
|
|
439
438
|
################################################################
|