braindecode 0.5.1__py3-none-any.whl
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-0.5.1.dist-info/LICENSE.txt +31 -0
- Braindecode-0.5.1.dist-info/METADATA +133 -0
- Braindecode-0.5.1.dist-info/RECORD +69 -0
- Braindecode-0.5.1.dist-info/WHEEL +5 -0
- Braindecode-0.5.1.dist-info/top_level.txt +2 -0
- braindecode/__init__.py +4 -0
- braindecode/classifier.py +258 -0
- braindecode/datasets/__init__.py +7 -0
- braindecode/datasets/base.py +293 -0
- braindecode/datasets/bbci.py +679 -0
- braindecode/datasets/moabb.py +110 -0
- braindecode/datasets/sleep_physionet.py +103 -0
- braindecode/datasets/tuh.py +230 -0
- braindecode/datautil/__init__.py +11 -0
- braindecode/datautil/mne.py +136 -0
- braindecode/datautil/preprocess.py +364 -0
- braindecode/datautil/serialization.py +111 -0
- braindecode/datautil/util.py +36 -0
- braindecode/datautil/windowers.py +435 -0
- braindecode/datautil/xy.py +77 -0
- braindecode/models/__init__.py +12 -0
- braindecode/models/deep4.py +252 -0
- braindecode/models/eegnet.py +339 -0
- braindecode/models/eegresnet.py +232 -0
- braindecode/models/functions.py +43 -0
- braindecode/models/hybrid.py +103 -0
- braindecode/models/modules.py +150 -0
- braindecode/models/shallow_fbcsp.py +158 -0
- braindecode/models/sleep_stager_chambon_2018.py +113 -0
- braindecode/models/tcn.py +143 -0
- braindecode/models/util.py +66 -0
- braindecode/regressor.py +240 -0
- braindecode/samplers/__init__.py +5 -0
- braindecode/samplers/base.py +78 -0
- braindecode/samplers/ssl.py +118 -0
- braindecode/training/__init__.py +8 -0
- braindecode/training/callbacks.py +21 -0
- braindecode/training/losses.py +17 -0
- braindecode/training/scoring.py +296 -0
- braindecode/util.py +362 -0
- braindecode/version.py +1 -0
- braindecode/visualization/__init__.py +5 -0
- braindecode/visualization/gradients.py +53 -0
- test/__init__.py +0 -0
- test/acceptance_tests/__init__.py +0 -0
- test/acceptance_tests/test_cropped_decoding.py +171 -0
- test/acceptance_tests/test_eeg_classifier.py +255 -0
- test/acceptance_tests/test_model_architectures.py +54 -0
- test/acceptance_tests/test_trialwise_decoding.py +168 -0
- test/unit_tests/__init__.py +0 -0
- test/unit_tests/datasets/__init__.py +0 -0
- test/unit_tests/datasets/test_dataset.py +227 -0
- test/unit_tests/datasets/test_mne.py +145 -0
- test/unit_tests/datasets/test_sleep_physionet.py +23 -0
- test/unit_tests/datasets/test_tuh.py +97 -0
- test/unit_tests/datasets/test_xy.py +31 -0
- test/unit_tests/datautil/__init__.py +0 -0
- test/unit_tests/datautil/test_preprocess.py +270 -0
- test/unit_tests/datautil/test_serialization.py +127 -0
- test/unit_tests/datautil/test_windowers.py +519 -0
- test/unit_tests/models/__init__.py +0 -0
- test/unit_tests/models/test_models.py +136 -0
- test/unit_tests/models/test_util.py +22 -0
- test/unit_tests/test_eeg_classifier.py +73 -0
- test/unit_tests/test_eeg_regressor.py +72 -0
- test/unit_tests/test_scoring.py +282 -0
- test/unit_tests/test_util.py +30 -0
- test/unit_tests/visualization/__init__.py +0 -0
- test/unit_tests/visualization/test_gradients.py +38 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""Preprocessors that work on Raw or Epochs objects.
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
# Authors: Hubert Banville <hubert.jbanville@gmail.com>
|
|
5
|
+
# Lukas Gemein <l.gemein@gmail.com>
|
|
6
|
+
# Simon Brandt <simonbrandt@protonmail.com>
|
|
7
|
+
# David Sabbagh <dav.sabbagh@gmail.com>
|
|
8
|
+
#
|
|
9
|
+
# License: BSD (3-clause)
|
|
10
|
+
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
from functools import partial
|
|
13
|
+
from warnings import warn
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
from numpy.lib.shape_base import apply_along_axis
|
|
17
|
+
import pandas as pd
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Preprocessor(object):
|
|
21
|
+
"""Preprocessor for an MNE Raw or Epochs object.
|
|
22
|
+
|
|
23
|
+
Applies the provided preprocessing function to the data of a Raw or Epochs
|
|
24
|
+
object.
|
|
25
|
+
If the function is provided as a string, the method with that name will be
|
|
26
|
+
used (e.g., 'pick_channels', 'filter', etc.).
|
|
27
|
+
If it is provided as a callable and `apply_on_array` is True, the
|
|
28
|
+
`apply_function` method of Raw and Epochs object will be used to apply the
|
|
29
|
+
function on the internal arrays of Raw and Epochs.
|
|
30
|
+
If `apply_on_array` is False, the callable must directly modify the Raw or
|
|
31
|
+
Epochs object (e.g., by calling its method(s) or directly moraw_timepoint
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
fn: str or callable
|
|
36
|
+
If str, the Raw/Epochs object must have a method with that name.
|
|
37
|
+
If callable, directly apply the callable to the object.
|
|
38
|
+
apply_on_array : bool
|
|
39
|
+
Ignored if `fn` is not a callable. If True, the `apply_function` of Raw
|
|
40
|
+
and Epochs object will be used to run `fn` on the underlying arrays
|
|
41
|
+
directly. If False, `fn` must directly modify the Raw or Epochs object.
|
|
42
|
+
kwargs:
|
|
43
|
+
Keyword arguments to be forwarded to the MNE function.
|
|
44
|
+
"""
|
|
45
|
+
def __init__(self, fn, apply_on_array=True, **kwargs):
|
|
46
|
+
if callable(fn) and apply_on_array:
|
|
47
|
+
channel_wise = kwargs.pop('channel_wise', False)
|
|
48
|
+
kwargs = dict(fun=partial(fn, **kwargs), channel_wise=channel_wise)
|
|
49
|
+
fn = 'apply_function'
|
|
50
|
+
self.fn = fn
|
|
51
|
+
self.kwargs = kwargs
|
|
52
|
+
|
|
53
|
+
def apply(self, raw_or_epochs):
|
|
54
|
+
try:
|
|
55
|
+
self._try_apply(raw_or_epochs)
|
|
56
|
+
except RuntimeError:
|
|
57
|
+
# Maybe the function needs the data to be loaded and the data was
|
|
58
|
+
# not loaded yet. Not all MNE functions need data to be loaded,
|
|
59
|
+
# most importantly the 'crop' function can be lazily applied
|
|
60
|
+
# without preloading data which can make the overall preprocessing
|
|
61
|
+
# pipeline substantially faster.
|
|
62
|
+
raw_or_epochs.load_data()
|
|
63
|
+
self._try_apply(raw_or_epochs)
|
|
64
|
+
|
|
65
|
+
def _try_apply(self, raw_or_epochs):
|
|
66
|
+
if callable(self.fn):
|
|
67
|
+
self.fn(raw_or_epochs, **self.kwargs)
|
|
68
|
+
else:
|
|
69
|
+
if not hasattr(raw_or_epochs, self.fn):
|
|
70
|
+
raise AttributeError(
|
|
71
|
+
f'MNE object does not have a {self.fn} method.')
|
|
72
|
+
getattr(raw_or_epochs, self.fn)(**self.kwargs)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class MNEPreproc(Preprocessor):
|
|
76
|
+
"""Preprocessor for an MNE-raw/epoch.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
fn: str or callable
|
|
81
|
+
if str, the raw/epoch object must have a member function with that name.
|
|
82
|
+
if callable, directly apply the callable to the mne raw/epoch.
|
|
83
|
+
kwargs:
|
|
84
|
+
Keyword arguments will be forwarded to the mne function
|
|
85
|
+
"""
|
|
86
|
+
def __init__(self, fn, **kwargs):
|
|
87
|
+
warn('MNEPreproc is deprecated. Use Preprocessor with '
|
|
88
|
+
'`apply_on_array=False` instead.')
|
|
89
|
+
super().__init__(fn, apply_on_array=False, **kwargs)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class NumpyPreproc(Preprocessor):
|
|
93
|
+
"""Preprocessor that directly operates on the underlying numpy array of an mne raw/epoch.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
fn: callable
|
|
98
|
+
Function that preprocesses the numpy array
|
|
99
|
+
channel_wise: bool
|
|
100
|
+
Whether to apply the function channel-wise.
|
|
101
|
+
kwargs:
|
|
102
|
+
Keyword arguments will be forwarded to the function
|
|
103
|
+
"""
|
|
104
|
+
def __init__(self, fn, channel_wise=False, **kwargs):
|
|
105
|
+
warn('NumpyPreproc is deprecated. Use Preprocessor with '
|
|
106
|
+
'`apply_on_array=True` instead.')
|
|
107
|
+
assert callable(fn), 'fn must be callable.'
|
|
108
|
+
super().__init__(fn, apply_on_array=True, channel_wise=channel_wise,
|
|
109
|
+
**kwargs)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def preprocess(concat_ds, preprocessors):
|
|
113
|
+
"""Apply preprocessors to a concat dataset.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
concat_ds: BaseConcatDataset
|
|
118
|
+
A concat of BaseDataset or WindowsDataset datasets to be preprocessed.
|
|
119
|
+
preprocessors: list(Preprocessor)
|
|
120
|
+
List of Preprocessor objects to apply to the dataset.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
BaseConcatDataset:
|
|
125
|
+
Preprocessed dataset.
|
|
126
|
+
"""
|
|
127
|
+
if not isinstance(preprocessors, Iterable):
|
|
128
|
+
raise ValueError(
|
|
129
|
+
'preprocessors must be a list of Preprocessor objects.')
|
|
130
|
+
for elem in preprocessors:
|
|
131
|
+
assert hasattr(elem, 'apply'), (
|
|
132
|
+
'Preprocessor object needs an `apply` method.')
|
|
133
|
+
|
|
134
|
+
for ds in concat_ds.datasets:
|
|
135
|
+
if hasattr(ds, 'raw'):
|
|
136
|
+
_preprocess(ds.raw, preprocessors)
|
|
137
|
+
elif hasattr(ds, 'windows'):
|
|
138
|
+
_preprocess(ds.windows, preprocessors)
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
'Can only preprocess concatenation of BaseDataset or '
|
|
142
|
+
'WindowsDataset, with either a `raw` or `windows` attribute.')
|
|
143
|
+
|
|
144
|
+
# Recompute cumulative sizes as the transforms might have changed them
|
|
145
|
+
# XXX: Ultimately, the best solution would be to have cumulative_size be
|
|
146
|
+
# a property of BaseConcatDataset.
|
|
147
|
+
concat_ds.cumulative_sizes = concat_ds.cumsum(concat_ds.datasets)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _preprocess(raw_or_epochs, preprocessors):
|
|
151
|
+
"""Apply preprocessor(s) to Raw or Epochs object.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
raw_or_epochs: mne.io.Raw or mne.Epochs
|
|
156
|
+
Object to preprocess.
|
|
157
|
+
preprocessors: list(Preprocessor)
|
|
158
|
+
List of preprocessors to apply to the dataset.
|
|
159
|
+
"""
|
|
160
|
+
for preproc in preprocessors:
|
|
161
|
+
preproc.apply(raw_or_epochs)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def exponential_moving_standardize(
|
|
165
|
+
data, factor_new=0.001, init_block_size=None, eps=1e-4
|
|
166
|
+
):
|
|
167
|
+
r"""Perform exponential moving standardization.
|
|
168
|
+
|
|
169
|
+
Compute the exponental moving mean :math:`m_t` at time `t` as
|
|
170
|
+
:math:`m_t=\mathrm{factornew} \cdot mean(x_t) + (1 - \mathrm{factornew}) \cdot m_{t-1}`.
|
|
171
|
+
|
|
172
|
+
Then, compute exponential moving variance :math:`v_t` at time `t` as
|
|
173
|
+
:math:`v_t=\mathrm{factornew} \cdot (m_t - x_t)^2 + (1 - \mathrm{factornew}) \cdot v_{t-1}`.
|
|
174
|
+
|
|
175
|
+
Finally, standardize the data point :math:`x_t` at time `t` as:
|
|
176
|
+
:math:`x'_t=(x_t - m_t) / max(\sqrt{->v_t}, eps)`.
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
data: np.ndarray (n_channels, n_times)
|
|
182
|
+
factor_new: float
|
|
183
|
+
init_block_size: int
|
|
184
|
+
Standardize data before to this index with regular standardization.
|
|
185
|
+
eps: float
|
|
186
|
+
Stabilizer for division by zero variance.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
standardized: np.ndarray (n_channels, n_times)
|
|
191
|
+
Standardized data.
|
|
192
|
+
"""
|
|
193
|
+
data = data.T
|
|
194
|
+
df = pd.DataFrame(data)
|
|
195
|
+
meaned = df.ewm(alpha=factor_new).mean()
|
|
196
|
+
demeaned = df - meaned
|
|
197
|
+
squared = demeaned * demeaned
|
|
198
|
+
square_ewmed = squared.ewm(alpha=factor_new).mean()
|
|
199
|
+
standardized = demeaned / np.maximum(eps, np.sqrt(np.array(square_ewmed)))
|
|
200
|
+
standardized = np.array(standardized)
|
|
201
|
+
if init_block_size is not None:
|
|
202
|
+
i_time_axis = 0
|
|
203
|
+
init_mean = np.mean(
|
|
204
|
+
data[0:init_block_size], axis=i_time_axis, keepdims=True
|
|
205
|
+
)
|
|
206
|
+
init_std = np.std(
|
|
207
|
+
data[0:init_block_size], axis=i_time_axis, keepdims=True
|
|
208
|
+
)
|
|
209
|
+
init_block_standardized = (
|
|
210
|
+
data[0:init_block_size] - init_mean) / np.maximum(eps, init_std)
|
|
211
|
+
standardized[0:init_block_size] = init_block_standardized
|
|
212
|
+
return standardized.T
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def exponential_moving_demean(data, factor_new=0.001, init_block_size=None):
|
|
216
|
+
r"""Perform exponential moving demeanining.
|
|
217
|
+
|
|
218
|
+
Compute the exponental moving mean :math:`m_t` at time `t` as
|
|
219
|
+
:math:`m_t=\mathrm{factornew} \cdot mean(x_t) + (1 - \mathrm{factornew}) \cdot m_{t-1}`.
|
|
220
|
+
|
|
221
|
+
Deman the data point :math:`x_t` at time `t` as:
|
|
222
|
+
:math:`x'_t=(x_t - m_t)`.
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
data: np.ndarray (n_channels, n_times)
|
|
228
|
+
factor_new: float
|
|
229
|
+
init_block_size: int
|
|
230
|
+
Demean data before to this index with regular demeaning.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
demeaned: np.ndarray (n_channels, n_times)
|
|
235
|
+
Demeaned data.
|
|
236
|
+
"""
|
|
237
|
+
data = data.T
|
|
238
|
+
df = pd.DataFrame(data)
|
|
239
|
+
meaned = df.ewm(alpha=factor_new).mean()
|
|
240
|
+
demeaned = df - meaned
|
|
241
|
+
demeaned = np.array(demeaned)
|
|
242
|
+
if init_block_size is not None:
|
|
243
|
+
i_time_axis = 0
|
|
244
|
+
init_mean = np.mean(
|
|
245
|
+
data[0:init_block_size], axis=i_time_axis, keepdims=True
|
|
246
|
+
)
|
|
247
|
+
demeaned[0:init_block_size] = data[0:init_block_size] - init_mean
|
|
248
|
+
return demeaned.T
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def zscore(data):
|
|
252
|
+
"""Zscore normalize continuous or windowed data in-place.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
data: np.ndarray (n_channels, n_times) or (n_windows, n_channels, n_times)
|
|
257
|
+
continuous or windowed signal
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
zscored: np.ndarray (n_channels x n_times) or (n_windows x n_channels x
|
|
262
|
+
n_times)
|
|
263
|
+
normalized continuous or windowed data
|
|
264
|
+
|
|
265
|
+
..note:
|
|
266
|
+
If this function is supposed to preprocess continuous data, it should be
|
|
267
|
+
given to raw.apply_function().
|
|
268
|
+
"""
|
|
269
|
+
zscored = data - np.mean(data, keepdims=True, axis=-1)
|
|
270
|
+
zscored = zscored / np.std(zscored, keepdims=True, axis=-1)
|
|
271
|
+
# TODO: the overriding of protected '_data' should be implemented in the
|
|
272
|
+
# TODO: dataset when transforms are applied to windows
|
|
273
|
+
if hasattr(data, '_data'):
|
|
274
|
+
data._data = zscored
|
|
275
|
+
return zscored
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def scale(data, factor):
|
|
279
|
+
"""Scale continuous or windowed data in-place
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
data: np.ndarray (n_channels x n_times) or (n_windows x n_channels x
|
|
284
|
+
n_times)
|
|
285
|
+
continuous or windowed signal
|
|
286
|
+
factor: float
|
|
287
|
+
multiplication factor
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
scaled: np.ndarray (n_channels x n_times) or (n_windows x n_channels x
|
|
292
|
+
n_times)
|
|
293
|
+
normalized continuous or windowed data
|
|
294
|
+
..note:
|
|
295
|
+
If this function is supposed to preprocess continuous data, it should be
|
|
296
|
+
given to raw.apply_function().
|
|
297
|
+
"""
|
|
298
|
+
scaled = np.multiply(data, factor)
|
|
299
|
+
# TODO: the overriding of protected '_data' should be implemented in the
|
|
300
|
+
# TODO: dataset when transforms are applied to windows
|
|
301
|
+
if hasattr(data, '_data'):
|
|
302
|
+
data._data = scaled
|
|
303
|
+
return scaled
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def filterbank(raw, frequency_bands, drop_original_signals=True,
|
|
307
|
+
order_by_frequency_band=False, **mne_filter_kwargs):
|
|
308
|
+
"""Applies multiple bandpass filters to the signals in raw. The raw will be
|
|
309
|
+
modified in-place and number of channels in raw will be updated to
|
|
310
|
+
len(frequency_bands) * len(raw.ch_names) (-len(raw.ch_names) if
|
|
311
|
+
drop_original_signals).
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
raw: mne.io.Raw
|
|
316
|
+
The raw signals to be filtered.
|
|
317
|
+
frequency_bands: list(tuple)
|
|
318
|
+
The frequency bands to be filtered for (e.g. [(4, 8), (8, 13)]).
|
|
319
|
+
drop_original_signals: bool
|
|
320
|
+
Whether to drop the original unfiltered signals
|
|
321
|
+
order_by_frequency_band: bool
|
|
322
|
+
If True will return channels odered by frequency bands, so if there
|
|
323
|
+
are channels Cz, O1 and filterbank ranges [(4,8), (8,13)], returned
|
|
324
|
+
channels will be [Cz_4-8, O1_4-8, Cz_8-13, O1_8-13]. If False, order
|
|
325
|
+
will be [Cz_4-8, Cz_8-13, O1_4-8, O1_8-13].
|
|
326
|
+
mne_filter_kwargs: dict
|
|
327
|
+
Keyword arguments for filtering supported by mne.io.Raw.filter().
|
|
328
|
+
Please refer to mne for a detailed explanation.
|
|
329
|
+
"""
|
|
330
|
+
if not frequency_bands:
|
|
331
|
+
raise ValueError(f"Expected at least one frequency band, got"
|
|
332
|
+
f" {frequency_bands}")
|
|
333
|
+
if not all([len(ch_name) < 8 for ch_name in raw.ch_names]):
|
|
334
|
+
warn("Try to use shorter channel names, since frequency band "
|
|
335
|
+
"annotation requires an estimated 4-8 chars depending on the "
|
|
336
|
+
"frequency ranges. Will truncate to 15 chars (mne max).")
|
|
337
|
+
original_ch_names = raw.ch_names
|
|
338
|
+
all_filtered = []
|
|
339
|
+
for (l_freq, h_freq) in frequency_bands:
|
|
340
|
+
filtered = raw.copy()
|
|
341
|
+
filtered.filter(l_freq=l_freq, h_freq=h_freq, **mne_filter_kwargs)
|
|
342
|
+
# mne automatically changes the highpass/lowpass info values
|
|
343
|
+
# when applying filters and channels cant be added if they have
|
|
344
|
+
# different such parameters. Not needed when making picks as
|
|
345
|
+
# high pass is not modified by filter if pick is specified
|
|
346
|
+
filtered.info["highpass"] = raw.info["highpass"]
|
|
347
|
+
filtered.info["lowpass"] = raw.info["lowpass"]
|
|
348
|
+
# add frequency band annotation to channel names
|
|
349
|
+
# truncate to a max of 15 characters, since mne does not allow for more
|
|
350
|
+
filtered.rename_channels({
|
|
351
|
+
old_name: (old_name + f"_{l_freq}-{h_freq}")[-15:]
|
|
352
|
+
for old_name in filtered.ch_names})
|
|
353
|
+
all_filtered.append(filtered)
|
|
354
|
+
raw.add_channels(all_filtered)
|
|
355
|
+
if not order_by_frequency_band:
|
|
356
|
+
# order channels by name and not by frequency band:
|
|
357
|
+
# index the list with a stepsize of the number of channels for each of
|
|
358
|
+
# the original channels
|
|
359
|
+
chs_by_freq_band = []
|
|
360
|
+
for i in range(len(original_ch_names)):
|
|
361
|
+
chs_by_freq_band.extend(raw.ch_names[i::len(original_ch_names)])
|
|
362
|
+
raw.reorder_channels(chs_by_freq_band)
|
|
363
|
+
if drop_original_signals:
|
|
364
|
+
raw.drop_channels(original_ch_names)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Convenience functions for storing and loading of windows datasets.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Authors: Lukas Gemein <l.gemein@gmail.com>
|
|
6
|
+
#
|
|
7
|
+
# License: BSD (3-clause)
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from glob import glob
|
|
12
|
+
import warnings
|
|
13
|
+
|
|
14
|
+
import mne
|
|
15
|
+
import pandas as pd
|
|
16
|
+
|
|
17
|
+
from braindecode.datasets.base import BaseDataset, BaseConcatDataset, WindowsDataset
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def save_concat_dataset(path, concat_dataset, overwrite=False):
|
|
21
|
+
warnings.warn('"save_concat_dataset()" is deprecated and will be removed in the future. '
|
|
22
|
+
'Use dataset.save() instead.')
|
|
23
|
+
concat_dataset.save(path=path, overwrite=overwrite)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_concat_dataset(path, preload, ids_to_load=None, target_name=None):
|
|
27
|
+
"""Load a stored BaseConcatDataset of BaseDatasets or WindowsDatasets from
|
|
28
|
+
files.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
path: str
|
|
33
|
+
Path to the directory of the .fif / -epo.fif and .json files.
|
|
34
|
+
preload: bool
|
|
35
|
+
Whether to preload the data.
|
|
36
|
+
ids_to_load: None | list(int)
|
|
37
|
+
Ids of specific files to load.
|
|
38
|
+
target_name: None or str
|
|
39
|
+
Load specific description column as target. If not given, take saved target name.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
concat_dataset: BaseConcatDataset of BaseDatasets or WindowsDatasets
|
|
44
|
+
"""
|
|
45
|
+
# assume we have a single concat dataset to load
|
|
46
|
+
concat_of_raws = os.path.isfile(os.path.join(path, '0-raw.fif'))
|
|
47
|
+
assert not (not concat_of_raws and target_name is not None), (
|
|
48
|
+
'Setting a new target is only supported for raws.')
|
|
49
|
+
concat_of_epochs = os.path.isfile(os.path.join(path, '0-epo.fif'))
|
|
50
|
+
paths = [path]
|
|
51
|
+
# assume we have multiple concat datasets to load
|
|
52
|
+
if not (concat_of_raws or concat_of_epochs):
|
|
53
|
+
concat_of_raws = os.path.isfile(os.path.join(path, '0', '0-raw.fif'))
|
|
54
|
+
concat_of_epochs = os.path.isfile(os.path.join(path, '0', '0-epo.fif'))
|
|
55
|
+
path = os.path.join(path, '*', '')
|
|
56
|
+
paths = glob(path)
|
|
57
|
+
paths = sorted(paths, key=lambda p: int(p.split(os.sep)[-2]))
|
|
58
|
+
if ids_to_load is not None:
|
|
59
|
+
paths = [paths[i] for i in ids_to_load]
|
|
60
|
+
ids_to_load = None
|
|
61
|
+
# if we have neither a single nor multiple datasets, something went wrong
|
|
62
|
+
assert concat_of_raws or concat_of_epochs, (
|
|
63
|
+
f'Expect either raw or epo to exist in {path} or in '
|
|
64
|
+
f'{os.path.join(path, "0")}')
|
|
65
|
+
|
|
66
|
+
datasets = []
|
|
67
|
+
for path in paths:
|
|
68
|
+
if concat_of_raws and target_name is None:
|
|
69
|
+
target_file_name = os.path.join(path, 'target_name.json')
|
|
70
|
+
target_name = json.load(open(target_file_name, "r"))['target_name']
|
|
71
|
+
|
|
72
|
+
all_signals, description = _load_signals_and_description(
|
|
73
|
+
path=path, preload=preload, raws=concat_of_raws,
|
|
74
|
+
ids_to_load=ids_to_load
|
|
75
|
+
)
|
|
76
|
+
for i_signal, signal in enumerate(all_signals):
|
|
77
|
+
if concat_of_raws:
|
|
78
|
+
datasets.append(
|
|
79
|
+
BaseDataset(signal, description.iloc[i_signal],
|
|
80
|
+
target_name=target_name))
|
|
81
|
+
else:
|
|
82
|
+
datasets.append(
|
|
83
|
+
WindowsDataset(signal, description.iloc[i_signal])
|
|
84
|
+
)
|
|
85
|
+
return BaseConcatDataset(datasets)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _load_signals_and_description(path, preload, raws, ids_to_load=None):
|
|
89
|
+
all_signals = []
|
|
90
|
+
file_name = "{}-raw.fif" if raws else "{}-epo.fif"
|
|
91
|
+
description_df = pd.read_json(os.path.join(path, "description.json"))
|
|
92
|
+
if ids_to_load is None:
|
|
93
|
+
file_names = glob(os.path.join(path, f"*{file_name.lstrip('{}')}"))
|
|
94
|
+
# Extract ids, e.g.,
|
|
95
|
+
# '/home/schirrmr/data/preproced-tuh/all-sensors/11-raw.fif' ->
|
|
96
|
+
# '11-raw.fif' -> 11
|
|
97
|
+
ids_to_load = sorted(
|
|
98
|
+
[int(os.path.split(f)[-1].split('-')[0]) for f in file_names])
|
|
99
|
+
for i in ids_to_load:
|
|
100
|
+
fif_file = os.path.join(path, file_name.format(i))
|
|
101
|
+
all_signals.append(_load_signals(fif_file, preload, raws))
|
|
102
|
+
description_df = description_df.iloc[ids_to_load]
|
|
103
|
+
return all_signals, description_df
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _load_signals(fif_file, preload, raws):
|
|
107
|
+
if raws:
|
|
108
|
+
signals = mne.io.read_raw_fif(fif_file, preload=preload)
|
|
109
|
+
else:
|
|
110
|
+
signals = mne.read_epochs(fif_file, preload=preload)
|
|
111
|
+
return signals
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
def ms_to_samples(ms, fs):
|
|
2
|
+
"""
|
|
3
|
+
Compute milliseconds to number of samples.
|
|
4
|
+
|
|
5
|
+
Parameters
|
|
6
|
+
----------
|
|
7
|
+
ms: number
|
|
8
|
+
Milliseconds
|
|
9
|
+
fs: number
|
|
10
|
+
Sampling rate
|
|
11
|
+
|
|
12
|
+
Returns
|
|
13
|
+
-------
|
|
14
|
+
n_samples: int
|
|
15
|
+
Number of samples
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
return ms * fs / 1000.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def samples_to_ms(n_samples, fs):
|
|
22
|
+
"""
|
|
23
|
+
Compute milliseconds to number of samples.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
n_samples: number
|
|
28
|
+
Number of samples
|
|
29
|
+
fs: number
|
|
30
|
+
Sampling rate
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
milliseconds: int
|
|
35
|
+
"""
|
|
36
|
+
return n_samples * 1000.0 / fs
|