Braindecode 1.8.0.dev169208086__tar.gz → 1.8.0.dev170432067__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.8.0.dev169208086/braindecode.egg-info → braindecode-1.8.0.dev170432067}/PKG-INFO +2 -2
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/augmentation/base.py +25 -1
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/augmentation/functional.py +2 -4
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/augmentation/transforms.py +5 -3
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/classifier.py +2 -1
- braindecode-1.8.0.dev170432067/braindecode/datasets/_notebook_viewer.py +255 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/base.py +18 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/hub.py +90 -25
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/hub_io.py +67 -5
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/sleep_physionet.py +9 -3
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/eegneuralnet.py +4 -2
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/__init__.py +2 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/atcnet.py +2 -2
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/attn_sleep.py +87 -28
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/base.py +43 -4
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/ctnet.py +9 -6
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegsimpleconv.py +8 -6
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/ifnet.py +8 -5
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/luna.py +29 -4
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sleep_stager_blanco_2020.py +5 -9
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sleep_stager_chambon_2018.py +1 -7
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sparcnet.py +2 -2
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/summary.csv +2 -1
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/tidnet.py +1 -7
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/usleep.py +3 -7
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/util.py +9 -0
- braindecode-1.8.0.dev170432067/braindecode/models/zuna.py +626 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/eegprep_preprocess.py +1 -1
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/windowers.py +126 -30
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/regressor.py +2 -1
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/samplers/base.py +40 -2
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/training/losses.py +7 -4
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/util.py +16 -10
- braindecode-1.8.0.dev170432067/braindecode/version.py +1 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067/braindecode.egg-info}/PKG-INFO +2 -2
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode.egg-info/SOURCES.txt +2 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode.egg-info/requires.txt +1 -1
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/api.rst +1 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/whats_new.rst +152 -4
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/pyproject.toml +1 -1
- braindecode-1.8.0.dev169208086/braindecode/version.py +0 -1
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/LICENSE.txt +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/MANIFEST.in +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/NOTICE.txt +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/README.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/augmentation/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bbci.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bcicomp.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/datasets.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/format.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/hub_format.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/hub_validation.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/iterable.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/chb_mit.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/collate.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/mne.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/moabb.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/nmt.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/registry.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/siena.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/tuh.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/utils.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/xy.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/channel_utils.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/hub_formats.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/serialization.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/util.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/functional/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/functional/functions.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/functional/initialization.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/attentionbasenet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/bendr.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/biot.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/brainmodule.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/cbramod.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/codebrain.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/config.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/contrawr.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/dance.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/deep4.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/deepsleepnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/dgcnn.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegconformer.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegdino.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eeginception_erp.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eeginception_mi.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegitnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegminer.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegnex.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegpt.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegsym.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegtcnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/emg2qwerty.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/fbcnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/fblightconvnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/fbmsnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/hybrid.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/interpolated.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/labram.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/medformer.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/meta_neuromotor.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/msvtnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/mvpformer.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/patchedtransformer.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/reve.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sccnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/shallow_fbcsp.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/signal_jepa.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sinc_shallow.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sstdpn.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/steegformer.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/syncnet.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/tcformer.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/tcn.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/tsinception.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/activation.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/attention.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/blocks.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/convolution.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/dance_modules.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/filter.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/interpolation.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/layers.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/linear.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/parametrization.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/stats.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/util.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/wrapper.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/mne_preprocess.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/preprocess.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/util.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/samplers/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/samplers/ssl.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/training/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/training/callbacks.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/training/scoring.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/__init__.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/attribution.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/confusion_matrices.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/frequency.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/metrics.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/sanity.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/topology.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode.egg-info/dependency_links.txt +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode.egg-info/top_level.txt +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/Makefile +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/_templates/autosummary/class.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/_templates/autosummary/function.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/cite.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/conf.py +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/help.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/index.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/install/install.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/install/install_pip.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/install/install_source.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/attention.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/channel.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/convolution.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/filterbank.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/gnn.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/interpretable.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/lbm.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/recurrent.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/spd.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/models.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/models_categorization.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/models_table.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/models_visualization.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/sg_execution_times.rst +0 -0
- {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/setup.cfg +0 -0
{braindecode-1.8.0.dev169208086/braindecode.egg-info → braindecode-1.8.0.dev170432067}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: braindecode
|
|
3
|
-
Version: 1.8.0.
|
|
3
|
+
Version: 1.8.0.dev170432067
|
|
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>
|
|
@@ -34,7 +34,6 @@ Requires-Dist: pandas
|
|
|
34
34
|
Requires-Dist: wfdb>=4.3.1
|
|
35
35
|
Requires-Dist: linear_attention_transformer
|
|
36
36
|
Requires-Dist: docstring_inheritance
|
|
37
|
-
Requires-Dist: rotary_embedding_torch
|
|
38
37
|
Requires-Dist: pydantic>=2.0
|
|
39
38
|
Provides-Extra: moabb
|
|
40
39
|
Requires-Dist: moabb>=1.4.3; extra == "moabb"
|
|
@@ -51,6 +50,7 @@ Requires-Dist: pytest-cov; extra == "tests"
|
|
|
51
50
|
Requires-Dist: codecov; extra == "tests"
|
|
52
51
|
Requires-Dist: pytest_cases; extra == "tests"
|
|
53
52
|
Requires-Dist: mypy; extra == "tests"
|
|
53
|
+
Requires-Dist: ipython; extra == "tests"
|
|
54
54
|
Requires-Dist: transformers>=4.57.0; extra == "tests"
|
|
55
55
|
Requires-Dist: bids_validator; extra == "tests"
|
|
56
56
|
Provides-Extra: typing
|
{braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/augmentation/base.py
RENAMED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Bruno Aristimunha <b.aristimunha@gmail.com>
|
|
4
4
|
# Martin Wimpff <martin.wimpff@iss.uni-stuttgart.de>
|
|
5
5
|
# Valentin Iovene <val@too.gy>
|
|
6
|
+
# Sarthak Tayal <sarthaktayal2@gmail.com>
|
|
6
7
|
# License: BSD (3-clause)
|
|
7
8
|
|
|
8
9
|
from numbers import Real
|
|
@@ -176,6 +177,14 @@ class Compose(Transform):
|
|
|
176
177
|
return X, y
|
|
177
178
|
|
|
178
179
|
|
|
180
|
+
def _as_mixed_target(y, lam_dtype):
|
|
181
|
+
# an untouched target is the same as being mixed with itself with lam of one
|
|
182
|
+
if isinstance(y, (tuple, list)) and len(y) == 3:
|
|
183
|
+
return tuple(y)
|
|
184
|
+
lam = torch.ones(y.shape[0], device=y.device, dtype=lam_dtype)
|
|
185
|
+
return y, y, lam
|
|
186
|
+
|
|
187
|
+
|
|
179
188
|
class _AugmentationCollate:
|
|
180
189
|
"""Collate that applies a transform to each batch, with optional expansion.
|
|
181
190
|
|
|
@@ -193,7 +202,10 @@ class _AugmentationCollate:
|
|
|
193
202
|
``0`` (default) applies the transform in place (batch size unchanged).
|
|
194
203
|
``> 0`` keeps the clean originals and appends ``n_augmentation``
|
|
195
204
|
independently transformed copies, returning ``(X, y)`` of
|
|
196
|
-
``(1 + n_augmentation)`` times the original size.
|
|
205
|
+
``(1 + n_augmentation)`` times the original size. When the transform
|
|
206
|
+
mixes targets, as :class:`braindecode.augmentation.Mixup` does, ``y``
|
|
207
|
+
stays the ``(y_a, y_b, lam)`` triple and the clean originals get a
|
|
208
|
+
mixing coefficient of one.
|
|
197
209
|
"""
|
|
198
210
|
|
|
199
211
|
def __init__(self, transform, device=None, n_augmentation=0):
|
|
@@ -216,6 +228,18 @@ class _AugmentationCollate:
|
|
|
216
228
|
aug_X, aug_y = self.transform(X, y)
|
|
217
229
|
xs.append(aug_X)
|
|
218
230
|
ys.append(aug_y)
|
|
231
|
+
mixed_ys = [
|
|
232
|
+
aug_y
|
|
233
|
+
for aug_y in ys
|
|
234
|
+
if isinstance(aug_y, (tuple, list)) and len(aug_y) == 3
|
|
235
|
+
]
|
|
236
|
+
if mixed_ys:
|
|
237
|
+
# a target-mixing transform such as Mixup returns (y_a, y_b, lam),
|
|
238
|
+
# so the parts are concatenated one by one and the untouched copies
|
|
239
|
+
# are given a mixing coefficient of one
|
|
240
|
+
lam_dtype = mixed_ys[0][2].dtype
|
|
241
|
+
ys = [_as_mixed_target(aug_y, lam_dtype) for aug_y in ys]
|
|
242
|
+
return torch.cat(xs), tuple(torch.cat(part) for part in zip(*ys))
|
|
219
243
|
return torch.cat(xs), torch.cat(ys)
|
|
220
244
|
|
|
221
245
|
|
|
@@ -1065,13 +1065,11 @@ def mixup(
|
|
|
1065
1065
|
batch_size, n_channels, n_times = X.shape
|
|
1066
1066
|
|
|
1067
1067
|
X_mix = torch.zeros((batch_size, n_channels, n_times)).to(device)
|
|
1068
|
-
y_a =
|
|
1069
|
-
y_b =
|
|
1068
|
+
y_a = y.clone()
|
|
1069
|
+
y_b = y[idx_perm].clone()
|
|
1070
1070
|
|
|
1071
1071
|
for idx in range(batch_size):
|
|
1072
1072
|
X_mix[idx] = lam[idx] * X[idx] + (1 - lam[idx]) * X[idx_perm[idx]]
|
|
1073
|
-
y_a[idx] = y[idx]
|
|
1074
|
-
y_b[idx] = y[idx_perm[idx]]
|
|
1075
1073
|
|
|
1076
1074
|
return X_mix, (y_a, y_b, lam)
|
|
1077
1075
|
|
|
@@ -1068,16 +1068,18 @@ class Mixup(Transform):
|
|
|
1068
1068
|
device = X.device
|
|
1069
1069
|
batch_size, _, _ = X.shape
|
|
1070
1070
|
|
|
1071
|
+
# lam follows the dtype of X, numpy draws float64 and that would leak
|
|
1072
|
+
# into the mixed signal and into the loss returned by mixup_criterion
|
|
1071
1073
|
if self.alpha > 0:
|
|
1072
1074
|
if self.beta_per_sample:
|
|
1073
1075
|
lam = torch.as_tensor(
|
|
1074
1076
|
self.rng.beta(self.alpha, self.alpha, batch_size)
|
|
1075
|
-
).to(device)
|
|
1077
|
+
).to(device=device, dtype=X.dtype)
|
|
1076
1078
|
else:
|
|
1077
|
-
lam = torch.ones(batch_size).to(device)
|
|
1079
|
+
lam = torch.ones(batch_size, dtype=X.dtype).to(device)
|
|
1078
1080
|
lam *= self.rng.beta(self.alpha, self.alpha)
|
|
1079
1081
|
else:
|
|
1080
|
-
lam = torch.ones(batch_size).to(device)
|
|
1082
|
+
lam = torch.ones(batch_size, dtype=X.dtype).to(device)
|
|
1081
1083
|
|
|
1082
1084
|
idx_perm = torch.as_tensor(
|
|
1083
1085
|
self.rng.permutation(
|
|
@@ -228,8 +228,9 @@ class EEGClassifier(_EEGNeuralNet, NeuralNetClassifier):
|
|
|
228
228
|
if return_targets:
|
|
229
229
|
return preds, X.get_metadata()["target"].to_numpy()
|
|
230
230
|
return preds
|
|
231
|
+
self.check_is_fitted()
|
|
231
232
|
return predict_trials(
|
|
232
|
-
module=self.
|
|
233
|
+
module=self.module_,
|
|
233
234
|
dataset=X,
|
|
234
235
|
return_targets=return_targets,
|
|
235
236
|
batch_size=self.batch_size,
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# Authors: Bruno Aristimunha <b.aristimunha@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# License: BSD (3-clause)
|
|
4
|
+
"""Serverless in-notebook viewer for file-backed recordings.
|
|
5
|
+
|
|
6
|
+
The bytes on disk behind a dataset element are inlined in the cell output
|
|
7
|
+
as base64 and handed to the deployed eegdash-viewer over its ``postMessage``
|
|
8
|
+
bridge (``docs/embedding.md`` in https://github.com/eegdash/eegdash-viewer):
|
|
9
|
+
no server, no CORS. The output is an iframe plus an inline script, so it
|
|
10
|
+
renders when the cell ran in your session or the saved notebook is trusted
|
|
11
|
+
(``jupyter trust``).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import base64
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import uuid
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from urllib.parse import urlsplit
|
|
22
|
+
|
|
23
|
+
import mne_bids
|
|
24
|
+
from mne.utils import _soft_import
|
|
25
|
+
|
|
26
|
+
CDN = "https://eegdash.github.io/eegdash-viewer"
|
|
27
|
+
MAX_BYTES = 64 * 2**20 # base64 output per call; it is saved with the notebook
|
|
28
|
+
EXTENSIONS = {
|
|
29
|
+
".set",
|
|
30
|
+
".edf",
|
|
31
|
+
".bdf",
|
|
32
|
+
".vhdr",
|
|
33
|
+
".fif",
|
|
34
|
+
".snirf",
|
|
35
|
+
".nwb",
|
|
36
|
+
} # viewer readers
|
|
37
|
+
_BIDS = {"eeg", "ieeg", "emg", "meg", "nirs"}
|
|
38
|
+
_SIBLINGS = {".vhdr": (".eeg", ".vmrk"), ".set": (".fdt",)} # travel with the header
|
|
39
|
+
_HEADER = {s: h for h, ss in _SIBLINGS.items() for s in ss} # data file -> header
|
|
40
|
+
|
|
41
|
+
_SCRIPT = """<iframe id=%(id)s title="eegdash trace viewer" style="width:100%%;height:%(height)spx;
|
|
42
|
+
border:1px solid var(--jp-border-color1,#d9dce1);border-radius:6px;background:transparent"></iframe>
|
|
43
|
+
<script>
|
|
44
|
+
(function () {
|
|
45
|
+
var self = document.currentScript, id = %(id)s; // Lab re-runs scripts in place; VS Code/nbclassic elsewhere
|
|
46
|
+
var frame = (self && self.previousElementSibling && self.previousElementSibling.tagName === "IFRAME")
|
|
47
|
+
? self.previousElementSibling : document.getElementById(id);
|
|
48
|
+
if (!frame) { console.error("eegdash viewer: output iframe " + id + " not found"); return; }
|
|
49
|
+
var payload = %(payload)s, origin = %(origin)s, files = null, pose = null;
|
|
50
|
+
function decode(b64) {
|
|
51
|
+
if (Uint8Array.fromBase64) return Uint8Array.fromBase64(b64);
|
|
52
|
+
var bin = atob(b64), out = new Uint8Array(bin.length);
|
|
53
|
+
for (var i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function send(target) {
|
|
57
|
+
try {
|
|
58
|
+
if (!files) {
|
|
59
|
+
files = payload.files.map(function (f) { return new File([decode(f.b64)], f.name); });
|
|
60
|
+
pose = payload.pose ? "data:application/json;base64," + payload.pose : null;
|
|
61
|
+
payload = null;
|
|
62
|
+
}
|
|
63
|
+
frame.contentWindow.postMessage({ type: "eegdash-viewer:open", files: files, pose: pose }, target || origin);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
frame.insertAdjacentHTML("afterend", '<div style="font:12px system-ui;color:#b3261e">eegdash viewer: '
|
|
66
|
+
+ String(err.message).replace(/</g, "<") + "</div>");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
window.addEventListener("message", function onMessage(e) {
|
|
70
|
+
if (e.source === frame.contentWindow && e.data && e.data.type === "eegdash-viewer:ready") send(e.origin);
|
|
71
|
+
else if (!frame.isConnected) window.removeEventListener("message", onMessage);
|
|
72
|
+
});
|
|
73
|
+
frame.src = %(src)s; // after the listener, so "ready" can never precede it
|
|
74
|
+
})();
|
|
75
|
+
</script>"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def recording_files(recording: Path) -> tuple[list[Path], Path | None]:
|
|
79
|
+
"""``(files, pose)`` to inline for a recording: the header first, then the
|
|
80
|
+
split-format siblings, the BIDS-inherited ``_channels.tsv``/``_events.tsv``
|
|
81
|
+
(``mne_bids`` parses the name; plain names get none) and, separately, the
|
|
82
|
+
``<prefix>_desc-pose.json`` hand-pose sidecar next to it. Symlinks keep
|
|
83
|
+
their name (git-annex/datalad)."""
|
|
84
|
+
rec = Path(recording)
|
|
85
|
+
if not rec.exists():
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"{rec.name}: file not found (a datalad symlink may need `datalad get`)"
|
|
88
|
+
)
|
|
89
|
+
if (
|
|
90
|
+
rec.is_dir()
|
|
91
|
+
or rec.suffix.lower() not in EXTENSIONS
|
|
92
|
+
or rec.stem.endswith("_epo")
|
|
93
|
+
):
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"{rec.name}: the viewer opens raw recordings in {' '.join(sorted(EXTENSIONS))}"
|
|
96
|
+
)
|
|
97
|
+
sidecars: list[Path | None] = []
|
|
98
|
+
try:
|
|
99
|
+
bids = mne_bids.get_bids_path_from_fname(rec, check=False)
|
|
100
|
+
if bids.subject is not None: # hyphen-free names parse, but are not BIDS
|
|
101
|
+
sidecars = [
|
|
102
|
+
bids.find_matching_sidecar(
|
|
103
|
+
suffix=s, extension=".tsv", on_error="ignore"
|
|
104
|
+
)
|
|
105
|
+
for s in ("channels", "events")
|
|
106
|
+
]
|
|
107
|
+
except (
|
|
108
|
+
KeyError,
|
|
109
|
+
ValueError,
|
|
110
|
+
): # not a BIDS name / unknown entity somewhere in the tree
|
|
111
|
+
pass
|
|
112
|
+
files = [rec]
|
|
113
|
+
for p in [
|
|
114
|
+
rec.with_suffix(e) for e in _SIBLINGS.get(rec.suffix.lower(), ())
|
|
115
|
+
] + sidecars:
|
|
116
|
+
if p and p.is_file():
|
|
117
|
+
if p not in files:
|
|
118
|
+
files.append(p)
|
|
119
|
+
elif p and p.is_symlink(): # dangling: git-annex/datalad content not fetched
|
|
120
|
+
raise ValueError(f"{p.name}: dangling symlink (try `datalad get`)")
|
|
121
|
+
stem, _, token = rec.stem.rpartition("_")
|
|
122
|
+
pose = rec.with_name((stem if token in _BIDS else rec.stem) + "_desc-pose.json")
|
|
123
|
+
return files, pose if pose.is_file() else None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def build_viewer_html(
|
|
127
|
+
recording: Path,
|
|
128
|
+
*,
|
|
129
|
+
height: int = 520,
|
|
130
|
+
cdn_url: str = CDN,
|
|
131
|
+
max_bytes: int = MAX_BYTES,
|
|
132
|
+
) -> str:
|
|
133
|
+
"""Viewer iframe + inlined bytes + bridge script for one recording."""
|
|
134
|
+
url = urlsplit(cdn_url)
|
|
135
|
+
if (
|
|
136
|
+
url.scheme not in ("http", "https")
|
|
137
|
+
or not url.netloc
|
|
138
|
+
or url.username
|
|
139
|
+
or url.query
|
|
140
|
+
or url.fragment
|
|
141
|
+
or url.path.endswith(("index.html", "index.htm"))
|
|
142
|
+
):
|
|
143
|
+
raise ValueError(
|
|
144
|
+
f"cdn_url must be the viewer's base http(s) URL, got {cdn_url!r}"
|
|
145
|
+
)
|
|
146
|
+
files, pose = recording_files(recording)
|
|
147
|
+
encoded = sum(
|
|
148
|
+
4 * -(-p.stat().st_size // 3) for p in files + ([pose] if pose else [])
|
|
149
|
+
)
|
|
150
|
+
if encoded > max_bytes:
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"{files[0].name}: {encoded / 2**20:.1f} MiB of base64 would be inlined into the "
|
|
153
|
+
f"notebook output (max_bytes={max_bytes / 2**20:.1f} MiB); crop/downsample or raise it"
|
|
154
|
+
)
|
|
155
|
+
rec = files[0]
|
|
156
|
+
# The viewer picks the recording by its *_<datatype>.<ext> name: plain names
|
|
157
|
+
# are posted as <stem>_eeg<ext>, an EEGLAB .fdt next to the posted .set name.
|
|
158
|
+
head = (
|
|
159
|
+
rec.name
|
|
160
|
+
if rec.stem.rpartition("_")[2] in _BIDS
|
|
161
|
+
else f"{rec.stem}_eeg{rec.suffix.lower()}"
|
|
162
|
+
)
|
|
163
|
+
names = [head] + [
|
|
164
|
+
head.rsplit("_", 1)[0] + "_eeg.fdt" if p.suffix.lower() == ".fdt" else p.name
|
|
165
|
+
for p in files[1:]
|
|
166
|
+
]
|
|
167
|
+
b64 = [base64.b64encode(p.read_bytes()).decode() for p in files]
|
|
168
|
+
literals = {
|
|
169
|
+
"id": f"eegdash-viewer-{uuid.uuid4().hex[:8]}",
|
|
170
|
+
"height": int(height),
|
|
171
|
+
"payload": {
|
|
172
|
+
"files": [{"name": n, "b64": b} for n, b in zip(names, b64)],
|
|
173
|
+
"pose": base64.b64encode(pose.read_bytes()).decode() if pose else None,
|
|
174
|
+
},
|
|
175
|
+
"origin": f"{url.scheme}://{url.netloc}",
|
|
176
|
+
"src": f"{url.geturl().rstrip('/')}/index.html?embed=1",
|
|
177
|
+
}
|
|
178
|
+
return _SCRIPT % {
|
|
179
|
+
k: json.dumps(v).replace("<", "\\u003c") for k, v in literals.items()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _recording(dataset, index: int) -> Path:
|
|
184
|
+
"""File behind ``dataset.datasets[index]``: the one its mne ``raw`` reads (a
|
|
185
|
+
data file maps back to its header; lazily downloading datasets fetch it when
|
|
186
|
+
``raw`` is accessed) or the recorded ``description["path"]``."""
|
|
187
|
+
ds = dataset.datasets[index]
|
|
188
|
+
names = [
|
|
189
|
+
Path(f)
|
|
190
|
+
for f in getattr(getattr(ds, "raw", None), "filenames", None) or ()
|
|
191
|
+
if isinstance(f, (str, os.PathLike))
|
|
192
|
+
]
|
|
193
|
+
if len(names) > 1:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"{type(dataset).__name__}[{index}]: split recordings are not supported"
|
|
196
|
+
)
|
|
197
|
+
desc = getattr(ds, "description", None) # dict or pandas Series
|
|
198
|
+
recorded = desc.get("path") if desc is not None else None
|
|
199
|
+
path = (
|
|
200
|
+
names[0]
|
|
201
|
+
if names
|
|
202
|
+
else Path(recorded)
|
|
203
|
+
if isinstance(recorded, (str, os.PathLike))
|
|
204
|
+
else None
|
|
205
|
+
)
|
|
206
|
+
if path is None:
|
|
207
|
+
raise ValueError(
|
|
208
|
+
f"{type(dataset).__name__}[{index}] is not backed by a recording file"
|
|
209
|
+
)
|
|
210
|
+
header = path.with_suffix(_HEADER.get(path.suffix.lower(), path.suffix))
|
|
211
|
+
return header if header.is_file() else path
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def plot(
|
|
215
|
+
dataset,
|
|
216
|
+
index: int = 0,
|
|
217
|
+
*,
|
|
218
|
+
height: int = 520,
|
|
219
|
+
cdn_url: str = CDN,
|
|
220
|
+
max_bytes: int = MAX_BYTES,
|
|
221
|
+
):
|
|
222
|
+
"""Show one recording in the eegdash-viewer inside a Jupyter cell.
|
|
223
|
+
|
|
224
|
+
Serverless: the recording bytes (as on disk) are inlined in the output and
|
|
225
|
+
pushed to the viewer at ``cdn_url`` over ``postMessage``; the output renders
|
|
226
|
+
when the cell ran in your session or the notebook is trusted. A
|
|
227
|
+
``*_desc-pose.json`` sidecar next to the recording adds the synchronized
|
|
228
|
+
hand-pose panel. Needs IPython (soft dependency).
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
index : int
|
|
233
|
+
Recording to display.
|
|
234
|
+
height : int
|
|
235
|
+
Viewer height in pixels.
|
|
236
|
+
cdn_url : str
|
|
237
|
+
Base URL of a deployed eegdash-viewer.
|
|
238
|
+
max_bytes : int
|
|
239
|
+
Refuse to inline more than this much base64 (default 64 MiB); the
|
|
240
|
+
payload is saved with the notebook and, like any cell output, stays
|
|
241
|
+
referenced by IPython's ``Out`` history for the session.
|
|
242
|
+
|
|
243
|
+
Returns
|
|
244
|
+
-------
|
|
245
|
+
IPython.display.HTML
|
|
246
|
+
"""
|
|
247
|
+
ipython = _soft_import("IPython", purpose=f"{type(dataset).__name__}.plot()")
|
|
248
|
+
return ipython.display.HTML(
|
|
249
|
+
build_viewer_html(
|
|
250
|
+
_recording(dataset, index),
|
|
251
|
+
height=height,
|
|
252
|
+
cdn_url=cdn_url,
|
|
253
|
+
max_bytes=max_bytes,
|
|
254
|
+
)
|
|
255
|
+
)
|
{braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/base.py
RENAMED
|
@@ -32,6 +32,7 @@ from mne.utils.docs import deprecated
|
|
|
32
32
|
from torch.utils.data import ConcatDataset, Dataset, IterableDataset
|
|
33
33
|
from typing_extensions import TypeVar
|
|
34
34
|
|
|
35
|
+
from ._notebook_viewer import plot as _viewer_plot
|
|
35
36
|
from .bids.hub import HubDatasetMixin
|
|
36
37
|
from .bids.hub_io import _restore_nan_from_json
|
|
37
38
|
from .registry import register_dataset
|
|
@@ -59,6 +60,7 @@ def _html_row(label, value):
|
|
|
59
60
|
|
|
60
61
|
_METADATA_INTERNAL_COLS = {
|
|
61
62
|
"i_window_in_trial",
|
|
63
|
+
"i_trial_in_dataset",
|
|
62
64
|
"i_start_in_trial",
|
|
63
65
|
"i_stop_in_trial",
|
|
64
66
|
"target",
|
|
@@ -1173,6 +1175,8 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
|
|
|
1173
1175
|
If True, defer computing cumulative sizes until length or item access.
|
|
1174
1176
|
"""
|
|
1175
1177
|
|
|
1178
|
+
plot = _viewer_plot # eegdash-viewer embed; a direct member so the API docs list it
|
|
1179
|
+
|
|
1176
1180
|
datasets: list[T]
|
|
1177
1181
|
|
|
1178
1182
|
def __init__(
|
|
@@ -1374,6 +1378,20 @@ class BaseConcatDataset(ConcatDataset, HubDatasetMixin, Generic[T]):
|
|
|
1374
1378
|
"datasets are WindowsDataset."
|
|
1375
1379
|
)
|
|
1376
1380
|
|
|
1381
|
+
for ds in self.datasets:
|
|
1382
|
+
if hasattr(ds, "_windows") and ds._windows is not None:
|
|
1383
|
+
df = ds._windows.metadata
|
|
1384
|
+
else:
|
|
1385
|
+
df = ds.metadata
|
|
1386
|
+
if (
|
|
1387
|
+
"i_trial_in_dataset" in df.columns
|
|
1388
|
+
and "i_trial_in_dataset" in ds.description
|
|
1389
|
+
):
|
|
1390
|
+
raise ValueError(
|
|
1391
|
+
"Dataset descriptions cannot contain the reserved window "
|
|
1392
|
+
"metadata key 'i_trial_in_dataset'."
|
|
1393
|
+
)
|
|
1394
|
+
|
|
1377
1395
|
all_dfs = list()
|
|
1378
1396
|
for ds in self.datasets:
|
|
1379
1397
|
if hasattr(ds, "_windows") and ds._windows is not None:
|
{braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/hub.py
RENAMED
|
@@ -28,6 +28,7 @@ The format follows a BIDS-inspired sourcedata structure:
|
|
|
28
28
|
# License: BSD (3-clause)
|
|
29
29
|
|
|
30
30
|
import contextlib
|
|
31
|
+
import copy
|
|
31
32
|
import json
|
|
32
33
|
import logging
|
|
33
34
|
import tempfile
|
|
@@ -56,6 +57,7 @@ from .hub_io import (
|
|
|
56
57
|
_load_eegwindows_from_zarr,
|
|
57
58
|
_load_raw_from_zarr,
|
|
58
59
|
_load_windows_from_zarr,
|
|
60
|
+
_prepare_info_for_json,
|
|
59
61
|
_save_eegwindows_to_zarr,
|
|
60
62
|
_save_raw_to_zarr,
|
|
61
63
|
_save_windows_to_zarr,
|
|
@@ -72,6 +74,33 @@ log = logging.getLogger(__name__)
|
|
|
72
74
|
_LOCK_FILE = "format_info.json"
|
|
73
75
|
|
|
74
76
|
|
|
77
|
+
def _normalize_kwargs_for_json(value, field_name):
|
|
78
|
+
"""Return one preprocessing-kwargs value as native strict JSON."""
|
|
79
|
+
|
|
80
|
+
def _convert(obj):
|
|
81
|
+
if isinstance(obj, np.ndarray):
|
|
82
|
+
return _convert(obj.tolist())
|
|
83
|
+
if isinstance(obj, dict):
|
|
84
|
+
return {key: _convert(item) for key, item in obj.items()}
|
|
85
|
+
if isinstance(obj, (list, tuple)):
|
|
86
|
+
return [_convert(item) for item in obj]
|
|
87
|
+
if isinstance(obj, np.generic):
|
|
88
|
+
return _convert(obj.item())
|
|
89
|
+
return obj
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
converted = _convert(value)
|
|
93
|
+
if isinstance(converted, str):
|
|
94
|
+
raise ValueError(
|
|
95
|
+
"a root string is ambiguous with the legacy encoded format"
|
|
96
|
+
)
|
|
97
|
+
return json.loads(json.dumps(converted, allow_nan=False))
|
|
98
|
+
except (OverflowError, RecursionError, TypeError, ValueError) as error:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"{field_name} must contain only finite JSON-serializable values"
|
|
101
|
+
) from error
|
|
102
|
+
|
|
103
|
+
|
|
75
104
|
class HubDatasetMixin:
|
|
76
105
|
"""
|
|
77
106
|
Mixin class for Hugging Face Hub integration with EEG datasets.
|
|
@@ -685,14 +714,57 @@ class HubDatasetMixin:
|
|
|
685
714
|
f"{output_path} already exists. Set overwrite=True to replace it."
|
|
686
715
|
)
|
|
687
716
|
|
|
688
|
-
# Create zarr store (zarr v3 API)
|
|
689
|
-
root = zarr.open(str(output_path), mode="w")
|
|
690
|
-
|
|
691
717
|
# Validate uniformity across all datasets using shared validation
|
|
692
718
|
dataset_type, _, _ = hub_validation.validate_dataset_uniformity(self.datasets)
|
|
693
719
|
|
|
694
|
-
#
|
|
695
|
-
|
|
720
|
+
# Normalize every JSON value before opening the output store. The cached
|
|
721
|
+
# values below are the exact values handed to the write helpers.
|
|
722
|
+
prepared_infos = []
|
|
723
|
+
for ds in self.datasets:
|
|
724
|
+
if dataset_type == "WindowsDataset":
|
|
725
|
+
info = ds.windows.info
|
|
726
|
+
elif dataset_type in ("EEGWindowsDataset", "RawDataset"):
|
|
727
|
+
info = ds.raw.info
|
|
728
|
+
prepared_infos.append(_prepare_info_for_json(info.to_json_dict()))
|
|
729
|
+
|
|
730
|
+
prepared_kwargs = {}
|
|
731
|
+
for kwarg_name in [
|
|
732
|
+
"raw_preproc_kwargs",
|
|
733
|
+
"window_kwargs",
|
|
734
|
+
"window_preproc_kwargs",
|
|
735
|
+
]:
|
|
736
|
+
expected_present = hasattr(self.datasets[0], kwarg_name)
|
|
737
|
+
expected_value = None
|
|
738
|
+
expected_token = None
|
|
739
|
+
for i_ds, ds in enumerate(self.datasets):
|
|
740
|
+
present = hasattr(ds, kwarg_name)
|
|
741
|
+
if present != expected_present:
|
|
742
|
+
raise ValueError(
|
|
743
|
+
f"{kwarg_name} on dataset {i_ds} has inconsistent presence; "
|
|
744
|
+
"the Zarr format stores one global value"
|
|
745
|
+
)
|
|
746
|
+
if not present:
|
|
747
|
+
continue
|
|
748
|
+
value = _normalize_kwargs_for_json(
|
|
749
|
+
getattr(ds, kwarg_name), f"{kwarg_name} on dataset {i_ds}"
|
|
750
|
+
)
|
|
751
|
+
value_token = json.dumps(
|
|
752
|
+
value, sort_keys=True, separators=(",", ":"), allow_nan=False
|
|
753
|
+
)
|
|
754
|
+
if i_ds == 0:
|
|
755
|
+
expected_value = value
|
|
756
|
+
expected_token = value_token
|
|
757
|
+
elif value_token != expected_token:
|
|
758
|
+
raise ValueError(
|
|
759
|
+
f"{kwarg_name} on dataset {i_ds} differs from dataset 0; "
|
|
760
|
+
"the Zarr format stores one global value"
|
|
761
|
+
)
|
|
762
|
+
if expected_present:
|
|
763
|
+
prepared_kwargs[kwarg_name] = expected_value
|
|
764
|
+
|
|
765
|
+
# Create compressor and zarr store (zarr v3 API) only after preflight.
|
|
766
|
+
compressor = _create_compressor(compression, compression_level)
|
|
767
|
+
root = zarr.open(str(output_path), mode="w")
|
|
696
768
|
|
|
697
769
|
# Store global metadata
|
|
698
770
|
root.attrs["n_datasets"] = len(self.datasets)
|
|
@@ -706,21 +778,10 @@ class HubDatasetMixin:
|
|
|
706
778
|
root.attrs["zarr_version"] = zarr.__version__
|
|
707
779
|
root.attrs["scipy_version"] = scipy.__version__
|
|
708
780
|
|
|
709
|
-
# Save preprocessing kwargs
|
|
710
|
-
#
|
|
711
|
-
for kwarg_name in
|
|
712
|
-
|
|
713
|
-
"window_kwargs",
|
|
714
|
-
"window_preproc_kwargs",
|
|
715
|
-
]:
|
|
716
|
-
# Check first dataset for these attributes
|
|
717
|
-
if hasattr(first_ds, kwarg_name):
|
|
718
|
-
kwargs = getattr(first_ds, kwarg_name)
|
|
719
|
-
if kwargs:
|
|
720
|
-
root.attrs[kwarg_name] = json.dumps(kwargs)
|
|
721
|
-
|
|
722
|
-
# Create compressor
|
|
723
|
-
compressor = _create_compressor(compression, compression_level)
|
|
781
|
+
# Save preprocessing kwargs from the preflight cache. These are
|
|
782
|
+
# typically set by windowing functions on individual datasets.
|
|
783
|
+
for kwarg_name, kwargs in prepared_kwargs.items():
|
|
784
|
+
root.attrs[kwarg_name] = kwargs
|
|
724
785
|
|
|
725
786
|
# Save each recording
|
|
726
787
|
for i_ds, ds in enumerate(self.datasets):
|
|
@@ -731,7 +792,7 @@ class HubDatasetMixin:
|
|
|
731
792
|
data = ds.windows.get_data()
|
|
732
793
|
metadata = ds.windows.metadata
|
|
733
794
|
description = ds.description
|
|
734
|
-
info_dict =
|
|
795
|
+
info_dict = prepared_infos[i_ds]
|
|
735
796
|
target_name = ds.target_name if hasattr(ds, "target_name") else None
|
|
736
797
|
|
|
737
798
|
# Save using inlined function
|
|
@@ -750,7 +811,7 @@ class HubDatasetMixin:
|
|
|
750
811
|
raw = ds.raw
|
|
751
812
|
metadata = ds.metadata
|
|
752
813
|
description = ds.description
|
|
753
|
-
info_dict =
|
|
814
|
+
info_dict = prepared_infos[i_ds]
|
|
754
815
|
targets_from = ds.targets_from
|
|
755
816
|
last_target_only = ds.last_target_only
|
|
756
817
|
|
|
@@ -771,7 +832,7 @@ class HubDatasetMixin:
|
|
|
771
832
|
# Get continuous raw data from RawDataset
|
|
772
833
|
raw = ds.raw
|
|
773
834
|
description = ds.description
|
|
774
|
-
info_dict =
|
|
835
|
+
info_dict = prepared_infos[i_ds]
|
|
775
836
|
target_name = ds.target_name if hasattr(ds, "target_name") else None
|
|
776
837
|
|
|
777
838
|
# Save using inlined function
|
|
@@ -956,10 +1017,14 @@ class HubDatasetMixin:
|
|
|
956
1017
|
"window_preproc_kwargs",
|
|
957
1018
|
]:
|
|
958
1019
|
if kwarg_name in root.attrs:
|
|
959
|
-
kwargs =
|
|
1020
|
+
kwargs = root.attrs[kwarg_name]
|
|
1021
|
+
if isinstance(kwargs, str):
|
|
1022
|
+
# Stores written by older braindecode versions kept
|
|
1023
|
+
# these attributes as double-encoded JSON strings.
|
|
1024
|
+
kwargs = json.loads(kwargs)
|
|
960
1025
|
# Set on each individual dataset (where they were originally stored)
|
|
961
1026
|
for ds in datasets:
|
|
962
|
-
setattr(ds, kwarg_name, kwargs)
|
|
1027
|
+
setattr(ds, kwarg_name, copy.deepcopy(kwargs))
|
|
963
1028
|
|
|
964
1029
|
return concat_ds
|
|
965
1030
|
|