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.
Files changed (180) hide show
  1. {braindecode-1.8.0.dev169208086/braindecode.egg-info → braindecode-1.8.0.dev170432067}/PKG-INFO +2 -2
  2. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/augmentation/base.py +25 -1
  3. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/augmentation/functional.py +2 -4
  4. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/augmentation/transforms.py +5 -3
  5. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/classifier.py +2 -1
  6. braindecode-1.8.0.dev170432067/braindecode/datasets/_notebook_viewer.py +255 -0
  7. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/base.py +18 -0
  8. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/hub.py +90 -25
  9. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/hub_io.py +67 -5
  10. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/sleep_physionet.py +9 -3
  11. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/eegneuralnet.py +4 -2
  12. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/__init__.py +2 -0
  13. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/atcnet.py +2 -2
  14. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/attn_sleep.py +87 -28
  15. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/base.py +43 -4
  16. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/ctnet.py +9 -6
  17. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegsimpleconv.py +8 -6
  18. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/ifnet.py +8 -5
  19. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/luna.py +29 -4
  20. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sleep_stager_blanco_2020.py +5 -9
  21. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sleep_stager_chambon_2018.py +1 -7
  22. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sparcnet.py +2 -2
  23. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/summary.csv +2 -1
  24. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/tidnet.py +1 -7
  25. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/usleep.py +3 -7
  26. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/util.py +9 -0
  27. braindecode-1.8.0.dev170432067/braindecode/models/zuna.py +626 -0
  28. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/eegprep_preprocess.py +1 -1
  29. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/windowers.py +126 -30
  30. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/regressor.py +2 -1
  31. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/samplers/base.py +40 -2
  32. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/training/losses.py +7 -4
  33. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/util.py +16 -10
  34. braindecode-1.8.0.dev170432067/braindecode/version.py +1 -0
  35. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067/braindecode.egg-info}/PKG-INFO +2 -2
  36. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode.egg-info/SOURCES.txt +2 -0
  37. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode.egg-info/requires.txt +1 -1
  38. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/api.rst +1 -0
  39. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/whats_new.rst +152 -4
  40. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/pyproject.toml +1 -1
  41. braindecode-1.8.0.dev169208086/braindecode/version.py +0 -1
  42. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/LICENSE.txt +0 -0
  43. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/MANIFEST.in +0 -0
  44. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/NOTICE.txt +0 -0
  45. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/README.rst +0 -0
  46. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/__init__.py +0 -0
  47. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/augmentation/__init__.py +0 -0
  48. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/__init__.py +0 -0
  49. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bbci.py +0 -0
  50. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bcicomp.py +0 -0
  51. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/__init__.py +0 -0
  52. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/datasets.py +0 -0
  53. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/format.py +0 -0
  54. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/hub_format.py +0 -0
  55. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/hub_validation.py +0 -0
  56. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/bids/iterable.py +0 -0
  57. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/chb_mit.py +0 -0
  58. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/collate.py +0 -0
  59. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/mne.py +0 -0
  60. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/moabb.py +0 -0
  61. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/nmt.py +0 -0
  62. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/registry.py +0 -0
  63. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/siena.py +0 -0
  64. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
  65. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/tuh.py +0 -0
  66. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/utils.py +0 -0
  67. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datasets/xy.py +0 -0
  68. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/__init__.py +0 -0
  69. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/channel_utils.py +0 -0
  70. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/hub_formats.py +0 -0
  71. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/serialization.py +0 -0
  72. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/datautil/util.py +0 -0
  73. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/functional/__init__.py +0 -0
  74. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/functional/functions.py +0 -0
  75. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/functional/initialization.py +0 -0
  76. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/attentionbasenet.py +0 -0
  77. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/bendr.py +0 -0
  78. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/biot.py +0 -0
  79. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/brainmodule.py +0 -0
  80. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/cbramod.py +0 -0
  81. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/codebrain.py +0 -0
  82. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/config.py +0 -0
  83. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/contrawr.py +0 -0
  84. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/dance.py +0 -0
  85. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/deep4.py +0 -0
  86. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/deepsleepnet.py +0 -0
  87. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/dgcnn.py +0 -0
  88. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegconformer.py +0 -0
  89. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegdino.py +0 -0
  90. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eeginception_erp.py +0 -0
  91. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eeginception_mi.py +0 -0
  92. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegitnet.py +0 -0
  93. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegminer.py +0 -0
  94. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegnet.py +0 -0
  95. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegnex.py +0 -0
  96. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegpt.py +0 -0
  97. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegsym.py +0 -0
  98. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/eegtcnet.py +0 -0
  99. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/emg2qwerty.py +0 -0
  100. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/fbcnet.py +0 -0
  101. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/fblightconvnet.py +0 -0
  102. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/fbmsnet.py +0 -0
  103. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/hybrid.py +0 -0
  104. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/interpolated.py +0 -0
  105. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/labram.py +0 -0
  106. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/medformer.py +0 -0
  107. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/meta_neuromotor.py +0 -0
  108. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/msvtnet.py +0 -0
  109. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/mvpformer.py +0 -0
  110. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/patchedtransformer.py +0 -0
  111. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/reve.py +0 -0
  112. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sccnet.py +0 -0
  113. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/shallow_fbcsp.py +0 -0
  114. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/signal_jepa.py +0 -0
  115. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sinc_shallow.py +0 -0
  116. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/sstdpn.py +0 -0
  117. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/steegformer.py +0 -0
  118. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/syncnet.py +0 -0
  119. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/tcformer.py +0 -0
  120. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/tcn.py +0 -0
  121. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/models/tsinception.py +0 -0
  122. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/__init__.py +0 -0
  123. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/activation.py +0 -0
  124. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/attention.py +0 -0
  125. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/blocks.py +0 -0
  126. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/convolution.py +0 -0
  127. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/dance_modules.py +0 -0
  128. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/filter.py +0 -0
  129. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/interpolation.py +0 -0
  130. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/layers.py +0 -0
  131. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/linear.py +0 -0
  132. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/parametrization.py +0 -0
  133. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/stats.py +0 -0
  134. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/util.py +0 -0
  135. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/modules/wrapper.py +0 -0
  136. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/__init__.py +0 -0
  137. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/mne_preprocess.py +0 -0
  138. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/preprocess.py +0 -0
  139. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/preprocessing/util.py +0 -0
  140. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/samplers/__init__.py +0 -0
  141. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/samplers/ssl.py +0 -0
  142. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/training/__init__.py +0 -0
  143. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/training/callbacks.py +0 -0
  144. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/training/scoring.py +0 -0
  145. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/__init__.py +0 -0
  146. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/attribution.py +0 -0
  147. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/confusion_matrices.py +0 -0
  148. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/frequency.py +0 -0
  149. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/metrics.py +0 -0
  150. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/sanity.py +0 -0
  151. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode/visualization/topology.py +0 -0
  152. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode.egg-info/dependency_links.txt +0 -0
  153. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/braindecode.egg-info/top_level.txt +0 -0
  154. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/Makefile +0 -0
  155. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/_templates/autosummary/class.rst +0 -0
  156. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
  157. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/_templates/autosummary/function.rst +0 -0
  158. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
  159. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/cite.rst +0 -0
  160. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/conf.py +0 -0
  161. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/help.rst +0 -0
  162. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/index.rst +0 -0
  163. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/install/install.rst +0 -0
  164. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/install/install_pip.rst +0 -0
  165. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/install/install_source.rst +0 -0
  166. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/attention.rst +0 -0
  167. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/channel.rst +0 -0
  168. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/convolution.rst +0 -0
  169. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/filterbank.rst +0 -0
  170. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/gnn.rst +0 -0
  171. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/interpretable.rst +0 -0
  172. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/lbm.rst +0 -0
  173. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/recurrent.rst +0 -0
  174. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/categorization/spd.rst +0 -0
  175. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/models.rst +0 -0
  176. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/models_categorization.rst +0 -0
  177. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/models_table.rst +0 -0
  178. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/models/models_visualization.rst +0 -0
  179. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/docs/sg_execution_times.rst +0 -0
  180. {braindecode-1.8.0.dev169208086 → braindecode-1.8.0.dev170432067}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braindecode
3
- Version: 1.8.0.dev169208086
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
@@ -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 = torch.arange(batch_size).to(device)
1069
- y_b = torch.arange(batch_size).to(device)
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.module,
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, "&lt;") + "</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
+ )
@@ -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:
@@ -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
- # Keep reference to first dataset for preprocessing kwargs
695
- first_ds = self.datasets[0]
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 (check first dataset, assuming uniform preprocessing)
710
- # These are typically set by windowing functions on individual datasets
711
- for kwarg_name in [
712
- "raw_preproc_kwargs",
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 = ds.windows.info.to_json_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 = ds.raw.info.to_json_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 = ds.raw.info.to_json_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 = json.loads(root.attrs[kwarg_name])
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