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,435 @@
|
|
|
1
|
+
"""Get epochs from mne.Raw
|
|
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
|
+
import warnings
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import mne
|
|
15
|
+
import pandas as pd
|
|
16
|
+
from joblib import Parallel, delayed
|
|
17
|
+
|
|
18
|
+
from ..datasets.base import WindowsDataset, BaseConcatDataset
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_windows_from_events(
|
|
22
|
+
concat_ds, trial_start_offset_samples, trial_stop_offset_samples,
|
|
23
|
+
window_size_samples=None, window_stride_samples=None,
|
|
24
|
+
drop_last_window=False, mapping=None, preload=False,
|
|
25
|
+
drop_bad_windows=True, picks=None, reject=None, flat=None,
|
|
26
|
+
on_missing='error', n_jobs=1):
|
|
27
|
+
"""Create windows based on events in mne.Raw.
|
|
28
|
+
|
|
29
|
+
This function extracts windows of size window_size_samples in the interval
|
|
30
|
+
[trial onset + trial_start_offset_samples, trial onset + trial duration +
|
|
31
|
+
trial_stop_offset_samples] around each trial, with a separation of
|
|
32
|
+
window_stride_samples between consecutive windows. If the last window
|
|
33
|
+
around an event does not end at trial_stop_offset_samples and
|
|
34
|
+
drop_last_window is set to False, an additional overlapping window that
|
|
35
|
+
ends at trial_stop_offset_samples is created.
|
|
36
|
+
|
|
37
|
+
Windows are extracted from the interval defined by the following:
|
|
38
|
+
|
|
39
|
+
trial onset +
|
|
40
|
+
trial onset duration
|
|
41
|
+
|--------------------|------------------------------|---------------------|
|
|
42
|
+
trial onset - trial onset +
|
|
43
|
+
trial_start_offset_samples duration +
|
|
44
|
+
trial_stop_offset_samples
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
concat_ds: BaseConcatDataset
|
|
49
|
+
A concat of base datasets each holding raw and description.
|
|
50
|
+
trial_start_offset_samples: int
|
|
51
|
+
Start offset from original trial onsets, in samples.
|
|
52
|
+
trial_stop_offset_samples: int
|
|
53
|
+
Stop offset from original trial stop, in samples.
|
|
54
|
+
window_size_samples: int | None
|
|
55
|
+
Window size. If None, the window size is inferred from the original
|
|
56
|
+
trial size of the first trial and trial_start_offset_samples and
|
|
57
|
+
trial_stop_offset_samples.
|
|
58
|
+
window_stride_samples: int | None
|
|
59
|
+
Stride between windows, in samples. If None, the window stride is
|
|
60
|
+
inferred from the original trial size of the first trial and
|
|
61
|
+
trial_start_offset_samples and trial_stop_offset_samples.
|
|
62
|
+
drop_last_window: bool
|
|
63
|
+
If False, an additional overlapping window that ends at
|
|
64
|
+
trial_stop_offset_samples will be extracted around each event when the
|
|
65
|
+
last window does not end exactly at trial_stop_offset_samples.
|
|
66
|
+
mapping: dict(str: int)
|
|
67
|
+
Mapping from event description to numerical target value.
|
|
68
|
+
preload: bool
|
|
69
|
+
If True, preload the data of the Epochs objects. This is useful to
|
|
70
|
+
reduce disk reading overhead when returning windows in a training
|
|
71
|
+
scenario, however very large data might not fit into memory.
|
|
72
|
+
drop_bad_windows: bool
|
|
73
|
+
If True, call `.drop_bad()` on the resulting mne.Epochs object. This
|
|
74
|
+
step allows identifying e.g., windows that fall outside of the
|
|
75
|
+
continuous recording. It is suggested to run this step here as otherwise
|
|
76
|
+
the BaseConcatDataset has to be updated as well.
|
|
77
|
+
picks: str | list | slice | None
|
|
78
|
+
Channels to include. If None, all available channels are used. See
|
|
79
|
+
mne.Epochs.
|
|
80
|
+
reject: dict | None
|
|
81
|
+
Epoch rejection parameters based on peak-to-peak amplitude. If None, no
|
|
82
|
+
rejection is done based on peak-to-peak amplitude. See mne.Epochs.
|
|
83
|
+
flat: dict | None
|
|
84
|
+
Epoch rejection parameters based on flatness of signals. If None, no
|
|
85
|
+
rejection based on flatness is done. See mne.Epochs.
|
|
86
|
+
on_missing: str
|
|
87
|
+
What to do if one or several event ids are not found in the recording.
|
|
88
|
+
Valid keys are ‘error’ | ‘warning’ | ‘ignore’. See mne.Epochs.
|
|
89
|
+
n_jobs: int
|
|
90
|
+
Number of jobs to use to parallelize the windowing.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
windows_ds: WindowsDataset
|
|
95
|
+
Dataset containing the extracted windows.
|
|
96
|
+
"""
|
|
97
|
+
_check_windowing_arguments(
|
|
98
|
+
trial_start_offset_samples, trial_stop_offset_samples,
|
|
99
|
+
window_size_samples, window_stride_samples)
|
|
100
|
+
|
|
101
|
+
# If user did not specify mapping, we extract all events from all datasets
|
|
102
|
+
# and map them to increasing integers starting from 0
|
|
103
|
+
infer_mapping = mapping is None
|
|
104
|
+
mapping = dict() if infer_mapping else mapping
|
|
105
|
+
infer_window_size_stride = window_size_samples is None
|
|
106
|
+
|
|
107
|
+
list_of_windows_ds = Parallel(n_jobs=n_jobs)(
|
|
108
|
+
delayed(_create_windows_from_events)(
|
|
109
|
+
ds, infer_mapping, infer_window_size_stride,
|
|
110
|
+
trial_start_offset_samples, trial_stop_offset_samples,
|
|
111
|
+
window_size_samples, window_stride_samples, drop_last_window,
|
|
112
|
+
mapping, preload, drop_bad_windows, picks, reject, flat,
|
|
113
|
+
'error') for ds in concat_ds.datasets)
|
|
114
|
+
|
|
115
|
+
return BaseConcatDataset(list_of_windows_ds)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def create_fixed_length_windows(
|
|
119
|
+
concat_ds, start_offset_samples, stop_offset_samples,
|
|
120
|
+
window_size_samples, window_stride_samples, drop_last_window,
|
|
121
|
+
mapping=None, preload=False, drop_bad_windows=True, picks=None,
|
|
122
|
+
reject=None, flat=None, on_missing='error', n_jobs=1):
|
|
123
|
+
"""Windower that creates sliding windows.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
concat_ds: ConcatDataset
|
|
128
|
+
A concat of base datasets each holding raw and descpription.
|
|
129
|
+
start_offset_samples: int
|
|
130
|
+
Start offset from beginning of recording in samples.
|
|
131
|
+
stop_offset_samples: int | None
|
|
132
|
+
Stop offset from beginning of recording in samples. If None, set to be
|
|
133
|
+
the end of the recording.
|
|
134
|
+
window_size_samples: int
|
|
135
|
+
Window size.
|
|
136
|
+
window_stride_samples: int
|
|
137
|
+
Stride between windows.
|
|
138
|
+
drop_last_window: bool
|
|
139
|
+
Whether or not have a last overlapping window, when windows do not
|
|
140
|
+
equally divide the continuous signal.
|
|
141
|
+
mapping: dict(str: int)
|
|
142
|
+
Mapping from event description to target value.
|
|
143
|
+
preload: bool
|
|
144
|
+
If True, preload the data of the Epochs objects.
|
|
145
|
+
drop_bad_windows: bool
|
|
146
|
+
If True, call `.drop_bad()` on the resulting mne.Epochs object. This
|
|
147
|
+
step allows identifying e.g., windows that fall outside of the
|
|
148
|
+
continuous recording. It is suggested to run this step here as
|
|
149
|
+
otherwise the BaseConcatDataset has to be updated as well.
|
|
150
|
+
picks: str | list | slice | None
|
|
151
|
+
Channels to include. If None, all available channels are used. See
|
|
152
|
+
mne.Epochs.
|
|
153
|
+
reject: dict | None
|
|
154
|
+
Epoch rejection parameters based on peak-to-peak amplitude. If None, no
|
|
155
|
+
rejection is done based on peak-to-peak amplitude. See mne.Epochs.
|
|
156
|
+
flat: dict | None
|
|
157
|
+
Epoch rejection parameters based on flatness of signals. If None, no
|
|
158
|
+
rejection based on flatness is done. See mne.Epochs.
|
|
159
|
+
on_missing: str
|
|
160
|
+
What to do if one or several event ids are not found in the recording.
|
|
161
|
+
Valid keys are ‘error’ | ‘warning’ | ‘ignore’. See mne.Epochs.
|
|
162
|
+
n_jobs: int
|
|
163
|
+
Number of jobs to use to parallelize the windowing.
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
windows_ds: WindowsDataset
|
|
168
|
+
Dataset containing the extracted windows.
|
|
169
|
+
"""
|
|
170
|
+
_check_windowing_arguments(
|
|
171
|
+
start_offset_samples, stop_offset_samples,
|
|
172
|
+
window_size_samples, window_stride_samples)
|
|
173
|
+
if stop_offset_samples == 0:
|
|
174
|
+
warnings.warn(
|
|
175
|
+
'Meaning of `trial_stop_offset_samples`=0 has changed, use `None` '
|
|
176
|
+
'to indicate end of trial/recording. Using `None`.')
|
|
177
|
+
stop_offset_samples = None
|
|
178
|
+
|
|
179
|
+
list_of_windows_ds = Parallel(n_jobs=n_jobs)(
|
|
180
|
+
delayed(_create_fixed_length_windows)(
|
|
181
|
+
ds, start_offset_samples, stop_offset_samples, window_size_samples,
|
|
182
|
+
window_stride_samples, drop_last_window, mapping, preload,
|
|
183
|
+
drop_bad_windows, picks, reject, flat, on_missing)
|
|
184
|
+
for ds in concat_ds.datasets)
|
|
185
|
+
|
|
186
|
+
return BaseConcatDataset(list_of_windows_ds)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _create_windows_from_events(
|
|
190
|
+
ds, infer_mapping, infer_window_size_stride,
|
|
191
|
+
trial_start_offset_samples, trial_stop_offset_samples,
|
|
192
|
+
window_size_samples=None, window_stride_samples=None,
|
|
193
|
+
drop_last_window=False, mapping=None, preload=False,
|
|
194
|
+
drop_bad_windows=True, picks=None, reject=None, flat=None,
|
|
195
|
+
on_missing='error'):
|
|
196
|
+
"""Create WindowsDataset from BaseDataset based on events.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
ds : BaseDataset
|
|
201
|
+
Dataset containing continuous data and description.
|
|
202
|
+
infer_mapping : bool
|
|
203
|
+
If True, extract all events from all datasets and map them to
|
|
204
|
+
increasing integers starting from 0.
|
|
205
|
+
infer_window_size_stride : bool
|
|
206
|
+
If True, infer the stride from the original trial size of the first
|
|
207
|
+
trial and trial_start_offset_samples and trial_stop_offset_samples.
|
|
208
|
+
|
|
209
|
+
See `create_windows_from_events` for description of other parameters.
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
WindowsDataset :
|
|
214
|
+
Windowed dataset.
|
|
215
|
+
"""
|
|
216
|
+
if infer_mapping:
|
|
217
|
+
unique_events = np.unique(ds.raw.annotations.description)
|
|
218
|
+
new_unique_events = [x for x in unique_events if x not in mapping]
|
|
219
|
+
# mapping event descriptions to integers from 0 on
|
|
220
|
+
max_id_mapping = len(mapping)
|
|
221
|
+
mapping.update(
|
|
222
|
+
{v: k + max_id_mapping for k, v in enumerate(new_unique_events)}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
events, events_id = mne.events_from_annotations(ds.raw, mapping)
|
|
226
|
+
onsets = events[:, 0]
|
|
227
|
+
# Onsets are relative to the beginning of the recording
|
|
228
|
+
filtered_durations = np.array(
|
|
229
|
+
[a['duration'] for a in ds.raw.annotations
|
|
230
|
+
if a['description'] in events_id]
|
|
231
|
+
)
|
|
232
|
+
stops = onsets + (filtered_durations * ds.raw.info['sfreq']).astype(int)
|
|
233
|
+
# XXX This could probably be simplified by using chunk_duration in
|
|
234
|
+
# `events_from_annotations`
|
|
235
|
+
|
|
236
|
+
last_samp = ds.raw.first_samp + ds.raw.n_times
|
|
237
|
+
if stops[-1] + trial_stop_offset_samples > last_samp:
|
|
238
|
+
raise ValueError(
|
|
239
|
+
'"trial_stop_offset_samples" too large. Stop of last trial '
|
|
240
|
+
f'({stops[-1]}) + "trial_stop_offset_samples" '
|
|
241
|
+
f'({trial_stop_offset_samples}) must be smaller than length of'
|
|
242
|
+
f' recording ({len(ds)}).')
|
|
243
|
+
|
|
244
|
+
if infer_window_size_stride:
|
|
245
|
+
# window size is trial size
|
|
246
|
+
if window_size_samples is None:
|
|
247
|
+
window_size_samples = stops[0] + trial_stop_offset_samples - (
|
|
248
|
+
onsets[0] + trial_start_offset_samples)
|
|
249
|
+
window_stride_samples = window_size_samples
|
|
250
|
+
this_trial_sizes = (stops + trial_stop_offset_samples) - (
|
|
251
|
+
onsets + trial_start_offset_samples)
|
|
252
|
+
# Maybe actually this is not necessary?
|
|
253
|
+
# We could also just say we just assume window size=trial size
|
|
254
|
+
# in case not given, without this condition...
|
|
255
|
+
# but then would have to change functions overall
|
|
256
|
+
# to deal with varying window sizes hmmhmh
|
|
257
|
+
assert np.all(this_trial_sizes == window_size_samples), (
|
|
258
|
+
'All trial sizes should be the same if you do not supply a window '
|
|
259
|
+
'size.')
|
|
260
|
+
|
|
261
|
+
description = events[:, -1]
|
|
262
|
+
|
|
263
|
+
i_trials, i_window_in_trials, starts, stops = _compute_window_inds(
|
|
264
|
+
onsets, stops, trial_start_offset_samples,
|
|
265
|
+
trial_stop_offset_samples, window_size_samples,
|
|
266
|
+
window_stride_samples, drop_last_window)
|
|
267
|
+
|
|
268
|
+
events = [[start, window_size_samples, description[i_trials[i_start]]]
|
|
269
|
+
for i_start, start in enumerate(starts)]
|
|
270
|
+
events = np.array(events)
|
|
271
|
+
|
|
272
|
+
if any(np.diff(events[:, 0]) <= 0):
|
|
273
|
+
raise NotImplementedError('Trial overlap not implemented.')
|
|
274
|
+
|
|
275
|
+
description = events[:, -1]
|
|
276
|
+
|
|
277
|
+
metadata = pd.DataFrame({
|
|
278
|
+
'i_window_in_trial': i_window_in_trials,
|
|
279
|
+
'i_start_in_trial': starts,
|
|
280
|
+
'i_stop_in_trial': stops,
|
|
281
|
+
'target': description})
|
|
282
|
+
|
|
283
|
+
# window size - 1, since tmax is inclusive
|
|
284
|
+
mne_epochs = mne.Epochs(
|
|
285
|
+
ds.raw, events, events_id, baseline=None, tmin=0,
|
|
286
|
+
tmax=(window_size_samples - 1) / ds.raw.info['sfreq'],
|
|
287
|
+
metadata=metadata, preload=preload, picks=picks, reject=reject,
|
|
288
|
+
flat=flat, on_missing=on_missing)
|
|
289
|
+
|
|
290
|
+
if drop_bad_windows:
|
|
291
|
+
mne_epochs.drop_bad()
|
|
292
|
+
|
|
293
|
+
return WindowsDataset(mne_epochs, ds.description)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _create_fixed_length_windows(
|
|
297
|
+
ds, start_offset_samples, stop_offset_samples, window_size_samples,
|
|
298
|
+
window_stride_samples, drop_last_window, mapping=None, preload=False,
|
|
299
|
+
drop_bad_windows=True, picks=None, reject=None, flat=None,
|
|
300
|
+
on_missing='error'):
|
|
301
|
+
"""Create WindowsDataset from BaseDataset with sliding windows.
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
ds : BaseDataset
|
|
306
|
+
Dataset containing continuous data and description.
|
|
307
|
+
|
|
308
|
+
See `create_fixed_length_windows` for description of other parameters.
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
WindowsDataset :
|
|
313
|
+
Windowed dataset.
|
|
314
|
+
"""
|
|
315
|
+
stop = ds.raw.n_times \
|
|
316
|
+
if stop_offset_samples is None else stop_offset_samples
|
|
317
|
+
stop = stop - window_size_samples + ds.raw.first_samp
|
|
318
|
+
# already includes last incomplete window start
|
|
319
|
+
starts = np.arange(
|
|
320
|
+
ds.raw.first_samp + start_offset_samples, stop + 1,
|
|
321
|
+
window_stride_samples)
|
|
322
|
+
|
|
323
|
+
if not drop_last_window and starts[-1] < stop:
|
|
324
|
+
# if last window does not end at trial stop, make it stop there
|
|
325
|
+
starts = np.append(starts, stop)
|
|
326
|
+
|
|
327
|
+
# TODO: handle multi-target case / non-integer target case
|
|
328
|
+
target = -1 if ds.target is None else ds.target
|
|
329
|
+
if mapping is not None:
|
|
330
|
+
target = mapping[target]
|
|
331
|
+
|
|
332
|
+
fake_events = [[start, window_size_samples, -1] for start in starts]
|
|
333
|
+
metadata = pd.DataFrame({
|
|
334
|
+
'i_window_in_trial': np.arange(len(fake_events)),
|
|
335
|
+
'i_start_in_trial': starts,
|
|
336
|
+
'i_stop_in_trial': starts + window_size_samples,
|
|
337
|
+
'target': len(fake_events) * [target]
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
# window size - 1, since tmax is inclusive
|
|
341
|
+
mne_epochs = mne.Epochs(
|
|
342
|
+
ds.raw, fake_events, baseline=None, tmin=0,
|
|
343
|
+
tmax=(window_size_samples - 1) / ds.raw.info['sfreq'],
|
|
344
|
+
metadata=metadata, preload=preload, picks=picks, reject=reject,
|
|
345
|
+
flat=flat, on_missing=on_missing)
|
|
346
|
+
|
|
347
|
+
if drop_bad_windows:
|
|
348
|
+
mne_epochs.drop_bad()
|
|
349
|
+
|
|
350
|
+
return WindowsDataset(mne_epochs, ds.description)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _compute_window_inds(
|
|
354
|
+
starts, stops, start_offset, stop_offset, size, stride,
|
|
355
|
+
drop_last_window):
|
|
356
|
+
"""Compute window start and stop indices.
|
|
357
|
+
|
|
358
|
+
Create window starts from trial onsets (shifted by start_offset) to trial
|
|
359
|
+
end (shifted by stop_offset) separated by stride, as long as window size
|
|
360
|
+
fits into trial.
|
|
361
|
+
|
|
362
|
+
Parameters
|
|
363
|
+
----------
|
|
364
|
+
starts: array-like
|
|
365
|
+
Trial starts in samples.
|
|
366
|
+
stops: array-like
|
|
367
|
+
Trial stops in samples.
|
|
368
|
+
start_offset: int
|
|
369
|
+
Start offset from original trial onsets in samples.
|
|
370
|
+
stop_offset: int
|
|
371
|
+
Stop offset from original trial stop in samples.
|
|
372
|
+
size: int
|
|
373
|
+
Window size.
|
|
374
|
+
stride: int
|
|
375
|
+
Stride between windows.
|
|
376
|
+
drop_last_window: bool
|
|
377
|
+
Toggles of shifting last window within range or dropping last samples.
|
|
378
|
+
|
|
379
|
+
Returns
|
|
380
|
+
-------
|
|
381
|
+
result_lists: (list, list, list, list)
|
|
382
|
+
Trial, i_window_in_trial, start sample and stop sample of windows.
|
|
383
|
+
"""
|
|
384
|
+
starts = np.array([starts]) if isinstance(starts, int) else starts
|
|
385
|
+
stops = np.array([stops]) if isinstance(stops, int) else stops
|
|
386
|
+
|
|
387
|
+
starts += start_offset
|
|
388
|
+
stops += stop_offset
|
|
389
|
+
|
|
390
|
+
i_window_in_trials, i_trials, window_starts = [], [], []
|
|
391
|
+
for start_i, (start, stop) in enumerate(zip(starts, stops)):
|
|
392
|
+
# Generate possible window starts with given stride between original
|
|
393
|
+
# trial onsets (shifted by start_offset) and stops
|
|
394
|
+
possible_starts = np.arange(start, stop, stride)
|
|
395
|
+
|
|
396
|
+
# Possible window start is actually a start, if window size fits in
|
|
397
|
+
# trial start and stop
|
|
398
|
+
for i_window, s in enumerate(possible_starts):
|
|
399
|
+
if (s + size) <= stop:
|
|
400
|
+
window_starts.append(s)
|
|
401
|
+
i_window_in_trials.append(i_window)
|
|
402
|
+
i_trials.append(start_i)
|
|
403
|
+
|
|
404
|
+
# If the last window start + window size is not the same as
|
|
405
|
+
# stop + stop_offset, create another window that overlaps and stops
|
|
406
|
+
# at onset + stop_offset
|
|
407
|
+
if not drop_last_window:
|
|
408
|
+
if window_starts[-1] + size != stop:
|
|
409
|
+
window_starts.append(stop - size)
|
|
410
|
+
i_window_in_trials.append(i_window_in_trials[-1] + 1)
|
|
411
|
+
i_trials.append(start_i)
|
|
412
|
+
|
|
413
|
+
# Update stops to now be event stops instead of trial stops
|
|
414
|
+
window_stops = np.array(window_starts) + size
|
|
415
|
+
if not (len(i_window_in_trials) == len(window_starts) == len(window_stops)):
|
|
416
|
+
raise ValueError(f'{len(i_window_in_trials)} == '
|
|
417
|
+
f'{len(window_starts)} == {len(window_stops)}')
|
|
418
|
+
|
|
419
|
+
return i_trials, i_window_in_trials, window_starts, window_stops
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _check_windowing_arguments(
|
|
423
|
+
trial_start_offset_samples, trial_stop_offset_samples,
|
|
424
|
+
window_size_samples, window_stride_samples):
|
|
425
|
+
assert isinstance(trial_start_offset_samples, (int, np.integer))
|
|
426
|
+
assert (isinstance(trial_stop_offset_samples, (int, np.integer)) or
|
|
427
|
+
(trial_stop_offset_samples is None))
|
|
428
|
+
assert isinstance(window_size_samples, (int, np.integer, type(None)))
|
|
429
|
+
assert isinstance(window_stride_samples, (int, np.integer, type(None)))
|
|
430
|
+
assert (window_size_samples is None) == (window_stride_samples is None)
|
|
431
|
+
if window_size_samples is not None:
|
|
432
|
+
assert window_size_samples > 0, (
|
|
433
|
+
"window size has to be larger than 0")
|
|
434
|
+
assert window_stride_samples > 0, (
|
|
435
|
+
"window stride has to be larger than 0")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import logging
|
|
4
|
+
import mne
|
|
5
|
+
|
|
6
|
+
from braindecode.datasets.base import BaseDataset, BaseConcatDataset
|
|
7
|
+
from braindecode.datautil.windowers import (
|
|
8
|
+
create_fixed_length_windows,)
|
|
9
|
+
|
|
10
|
+
log = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_from_X_y(
|
|
14
|
+
X, y, drop_last_window, sfreq=None, ch_names=None, window_size_samples=None,
|
|
15
|
+
window_stride_samples=None):
|
|
16
|
+
"""Create a BaseConcatDataset of WindowsDatasets from X and y to be used for
|
|
17
|
+
decoding with skorch and braindecode, where X is a list of pre-cut trials
|
|
18
|
+
and y are corresponding targets.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
X: array-like
|
|
23
|
+
list of pre-cut trials as n_trials x n_channels x n_times
|
|
24
|
+
y: array-like
|
|
25
|
+
targets corresponding to the trials
|
|
26
|
+
sfreq: common sampling frequency of all trials
|
|
27
|
+
ch_names: array-like
|
|
28
|
+
channel names of the trials
|
|
29
|
+
drop_last_window: bool
|
|
30
|
+
whether or not have a last overlapping window, when
|
|
31
|
+
windows/windows do not equally divide the continuous signal
|
|
32
|
+
window_size_samples: int
|
|
33
|
+
window size
|
|
34
|
+
window_stride_samples: int
|
|
35
|
+
stride between windows
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
windows_datasets: BaseConcatDataset
|
|
40
|
+
X and y transformed to a dataset format that is compatible with skorch
|
|
41
|
+
and braindecode
|
|
42
|
+
"""
|
|
43
|
+
n_samples_per_x = []
|
|
44
|
+
base_datasets = []
|
|
45
|
+
if sfreq is None:
|
|
46
|
+
sfreq = 100
|
|
47
|
+
log.info("No sampling frequency given, set to 100 Hz.")
|
|
48
|
+
if ch_names is None:
|
|
49
|
+
ch_names = [str(i) for i in range(X.shape[1])]
|
|
50
|
+
log.info(f"No channel names given, set to 0-{X.shape[1]}).")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
for x, target in zip(X, y):
|
|
54
|
+
n_samples_per_x.append(x.shape[1])
|
|
55
|
+
info = mne.create_info(ch_names=ch_names, sfreq=sfreq)
|
|
56
|
+
raw = mne.io.RawArray(x, info)
|
|
57
|
+
base_dataset = BaseDataset(raw, pd.Series({"target": target}),
|
|
58
|
+
target_name="target")
|
|
59
|
+
base_datasets.append(base_dataset)
|
|
60
|
+
base_datasets = BaseConcatDataset(base_datasets)
|
|
61
|
+
|
|
62
|
+
if window_size_samples is None and window_stride_samples is None:
|
|
63
|
+
if not len(np.unique(n_samples_per_x)) == 1:
|
|
64
|
+
raise ValueError(f"if 'window_size_samples' and "
|
|
65
|
+
f"'window_stride_samples' are None, "
|
|
66
|
+
f"all trials have to have the same length")
|
|
67
|
+
window_size_samples = n_samples_per_x[0]
|
|
68
|
+
window_stride_samples = n_samples_per_x[0]
|
|
69
|
+
windows_datasets = create_fixed_length_windows(
|
|
70
|
+
base_datasets,
|
|
71
|
+
start_offset_samples=0,
|
|
72
|
+
stop_offset_samples=None,
|
|
73
|
+
window_size_samples=window_size_samples,
|
|
74
|
+
window_stride_samples=window_stride_samples,
|
|
75
|
+
drop_last_window=drop_last_window
|
|
76
|
+
)
|
|
77
|
+
return windows_datasets
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Some predefined network architectures for EEG decoding.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .deep4 import Deep4Net
|
|
6
|
+
from .eegnet import EEGNetv4, EEGNetv1
|
|
7
|
+
from .hybrid import HybridNet
|
|
8
|
+
from .shallow_fbcsp import ShallowFBCSPNet
|
|
9
|
+
from .eegresnet import EEGResNet
|
|
10
|
+
from .tcn import TCN
|
|
11
|
+
from .sleep_stager_chambon_2018 import SleepStagerChambon2018
|
|
12
|
+
from .util import get_output_shape
|