braindecode 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {Braindecode-0.2.1 → Braindecode-0.3.0}/Braindecode.egg-info/PKG-INFO +1 -1
- {Braindecode-0.2.1 → Braindecode-0.3.0}/PKG-INFO +1 -1
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datasets/bcic_iv_2a.py +2 -2
- Braindecode-0.3.0/braindecode/datautil/trial_segment.py +628 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/experiments/monitors.py +37 -3
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/models/shallow_fbcsp.py +17 -11
- Braindecode-0.3.0/braindecode/version.py +1 -0
- Braindecode-0.3.0/docs/notebooks/BBCI_Data.ipynb +886 -0
- Braindecode-0.3.0/docs/notebooks/BBCI_Data_Start_Stop.ipynb +899 -0
- Braindecode-0.3.0/docs/notebooks/BBCI_Data_Trialwise.ipynb +761 -0
- Braindecode-0.3.0/docs/notebooks/Experiment_Class.ipynb +804 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/notebooks/TrialWise_Decoding.ipynb +12 -12
- Braindecode-0.3.0/docs/notebooks/visualization/Perturbation.ipynb +759 -0
- Braindecode-0.2.1/braindecode/datautil/trial_segment.py +0 -312
- Braindecode-0.2.1/braindecode/version.py +0 -1
- Braindecode-0.2.1/docs/notebooks/BBCI_Data.ipynb +0 -845
- Braindecode-0.2.1/docs/notebooks/BBCI_Data_Start_Stop.ipynb +0 -802
- Braindecode-0.2.1/docs/notebooks/BBCI_Data_Trialwise.ipynb +0 -839
- Braindecode-0.2.1/docs/notebooks/Experiment_Class.ipynb +0 -804
- Braindecode-0.2.1/docs/notebooks/visualization/Perturbation.ipynb +0 -753
- {Braindecode-0.2.1 → Braindecode-0.3.0}/Braindecode.egg-info/SOURCES.txt +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/Braindecode.egg-info/dependency_links.txt +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/Braindecode.egg-info/not-zip-safe +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/Braindecode.egg-info/requires.txt +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/Braindecode.egg-info/top_level.txt +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/LICENSE.txt +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/MANIFEST.in +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/README.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/__init__.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datasets/__init__.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datasets/bbci.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datasets/multiple.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datasets/sensor_positions.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datautil/__init__.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datautil/iterators.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datautil/signal_target.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datautil/signalproc.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datautil/splitters.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/datautil/util.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/experiments/__init__.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/experiments/experiment.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/experiments/stopcriteria.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/mne_ext/__init__.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/mne_ext/signalproc.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/models/__init__.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/models/deep4.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/models/eegnet.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/models/util.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/torch_ext/__init__.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/torch_ext/constraints.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/torch_ext/functions.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/torch_ext/init.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/torch_ext/losses.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/torch_ext/modules.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/torch_ext/util.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/util.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/visualization/__init__.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/visualization/perturbation.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/braindecode/visualization/plot.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/Makefile +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/conf.py +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/index.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/notebooks/Cropped_Decoding.ipynb +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/source/braindecode.datasets.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/source/braindecode.datautil.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/source/braindecode.experiments.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/source/braindecode.mne_ext.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/source/braindecode.models.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/source/braindecode.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/source/braindecode.torch_ext.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/source/braindecode.visualization.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/docs/source/modules.rst +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/setup.cfg +0 -0
- {Braindecode-0.2.1 → Braindecode-0.3.0}/setup.py +0 -0
|
@@ -33,7 +33,7 @@ class BCICompetition4Set2A(object):
|
|
|
33
33
|
mask = np.isnan(data[i_chan])
|
|
34
34
|
chan_mean = np.nanmean(data[i_chan])
|
|
35
35
|
data[i_chan, mask] = chan_mean
|
|
36
|
-
gdf_events = raw_edf.
|
|
36
|
+
gdf_events = raw_edf.find_edf_events()
|
|
37
37
|
raw_edf = mne.io.RawArray(data, raw_edf.info, verbose='WARNING')
|
|
38
38
|
# remember gdf events
|
|
39
39
|
raw_edf.info['gdf_events'] = gdf_events
|
|
@@ -76,4 +76,4 @@ class BCICompetition4Set2A(object):
|
|
|
76
76
|
events[:,0] = trial_events[:,0]
|
|
77
77
|
events[:,2] = trial_events[:,1]
|
|
78
78
|
|
|
79
|
-
return events, artifact_trial_mask
|
|
79
|
+
return events, artifact_trial_mask
|
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections import OrderedDict, Counter
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from braindecode.datautil.signal_target import SignalAndTarget
|
|
8
|
+
from braindecode.datautil.util import ms_to_samples
|
|
9
|
+
|
|
10
|
+
log = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
marker_def = OrderedDict(
|
|
13
|
+
(['Right', 1], ['Left', [2]], ['Rest', 3], ['Feet', 4]))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_signal_target_from_raw_mne(
|
|
17
|
+
raw, name_to_start_codes, epoch_ival_ms,
|
|
18
|
+
name_to_stop_codes=None,
|
|
19
|
+
prepad_trials_to_n_samples=None,
|
|
20
|
+
one_hot_labels=False,
|
|
21
|
+
one_label_per_trial=True):
|
|
22
|
+
"""
|
|
23
|
+
Create SignalTarget set from given `mne.io.RawArray`.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
raw: `mne.io.RawArray`
|
|
28
|
+
name_to_start_codes: OrderedDict (str -> int or list of int)
|
|
29
|
+
Ordered dictionary mapping class names to marker code or marker codes.
|
|
30
|
+
y-labels will be assigned in increasing key order, i.e.
|
|
31
|
+
first classname gets y-value 0, second classname y-value 1, etc.
|
|
32
|
+
epoch_ival_ms: iterable of (int,int)
|
|
33
|
+
Epoching interval in milliseconds. In case only `name_to_codes` given,
|
|
34
|
+
represents start offset and stop offset from start markers. In case
|
|
35
|
+
`name_to_stop_codes` given, represents offset from start marker
|
|
36
|
+
and offset from stop marker. E.g. [500, -500] would mean 500ms
|
|
37
|
+
after the start marker until 500 ms before the stop marker.
|
|
38
|
+
name_to_stop_codes: dict (str -> int or list of int), optional
|
|
39
|
+
Dictionary mapping class names to stop marker code or stop marker codes.
|
|
40
|
+
Order does not matter, dictionary should contain each class in
|
|
41
|
+
`name_to_codes` dictionary.
|
|
42
|
+
prepad_trials_to_n_samples: int
|
|
43
|
+
Pad trials that would be too short with the signal before it (only
|
|
44
|
+
valid if name_to_stop_codes is not None).
|
|
45
|
+
one_hot_labels: bool, optional
|
|
46
|
+
Whether to have the labels in a one-hot format, e.g. [0,0,1] or to
|
|
47
|
+
have them just as an int, e.g. 2
|
|
48
|
+
one_label_per_trial: bool, optional
|
|
49
|
+
Whether to have a timeseries of labels or just a single label per trial.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
dataset: :class:`.SignalAndTarget`
|
|
54
|
+
Dataset with `X` as the trial signals and `y` as the trial labels.
|
|
55
|
+
"""
|
|
56
|
+
data = raw.get_data()
|
|
57
|
+
events = np.array([raw.info['events'][:,0],
|
|
58
|
+
raw.info['events'][:,2]]).T
|
|
59
|
+
fs = raw.info['sfreq']
|
|
60
|
+
return create_signal_target(
|
|
61
|
+
data, events, fs, name_to_start_codes,
|
|
62
|
+
epoch_ival_ms,
|
|
63
|
+
name_to_stop_codes=name_to_stop_codes,
|
|
64
|
+
prepad_trials_to_n_samples=prepad_trials_to_n_samples,
|
|
65
|
+
one_hot_labels=one_hot_labels,
|
|
66
|
+
one_label_per_trial=one_label_per_trial)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def create_signal_target(data, events, fs, name_to_start_codes, epoch_ival_ms,
|
|
70
|
+
name_to_stop_codes=None, prepad_trials_to_n_samples=None,
|
|
71
|
+
one_hot_labels=False, one_label_per_trial=True):
|
|
72
|
+
"""
|
|
73
|
+
Create SignalTarget set given continuous data.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
data: 2d-array of number
|
|
78
|
+
The continuous recorded data. Channels x times order.
|
|
79
|
+
events: 2d-array
|
|
80
|
+
Dimensions: Number of events, 2. For each event, should contain sample
|
|
81
|
+
index and marker code.
|
|
82
|
+
fs: number
|
|
83
|
+
Sampling rate.
|
|
84
|
+
name_to_start_codes: OrderedDict (str -> int or list of int)
|
|
85
|
+
Ordered dictionary mapping class names to marker code or marker codes.
|
|
86
|
+
y-labels will be assigned in increasing key order, i.e.
|
|
87
|
+
first classname gets y-value 0, second classname y-value 1, etc.
|
|
88
|
+
epoch_ival_ms: iterable of (int,int)
|
|
89
|
+
Epoching interval in milliseconds. In case only `name_to_codes` given,
|
|
90
|
+
represents start offset and stop offset from start markers. In case
|
|
91
|
+
`name_to_stop_codes` given, represents offset from start marker
|
|
92
|
+
and offset from stop marker. E.g. [500, -500] would mean 500ms
|
|
93
|
+
after the start marker until 500 ms before the stop marker.
|
|
94
|
+
name_to_stop_codes: dict (str -> int or list of int), optional
|
|
95
|
+
Dictionary mapping class names to stop marker code or stop marker codes.
|
|
96
|
+
Order does not matter, dictionary should contain each class in
|
|
97
|
+
`name_to_codes` dictionary.
|
|
98
|
+
prepad_trials_to_n_samples: int, optional
|
|
99
|
+
Pad trials that would be too short with the signal before it (only
|
|
100
|
+
valid if name_to_stop_codes is not None).
|
|
101
|
+
one_hot_labels: bool, optional
|
|
102
|
+
Whether to have the labels in a one-hot format, e.g. [0,0,1] or to
|
|
103
|
+
have them just as an int, e.g. 2
|
|
104
|
+
one_label_per_trial: bool, optional
|
|
105
|
+
Whether to have a timeseries of labels or just a single label per trial.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
dataset: :class:`.SignalAndTarget`
|
|
110
|
+
Dataset with `X` as the trial signals and `y` as the trial labels.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
if name_to_stop_codes is None:
|
|
114
|
+
return _create_signal_target_from_start_and_ival(
|
|
115
|
+
data, events, fs, name_to_start_codes, epoch_ival_ms,
|
|
116
|
+
one_hot_labels=one_hot_labels,
|
|
117
|
+
one_label_per_trial=one_label_per_trial)
|
|
118
|
+
else:
|
|
119
|
+
return _create_signal_target_from_start_and_stop(
|
|
120
|
+
data, events, fs, name_to_start_codes, epoch_ival_ms,
|
|
121
|
+
name_to_stop_codes, prepad_trials_to_n_samples,
|
|
122
|
+
one_hot_labels=one_hot_labels,
|
|
123
|
+
one_label_per_trial=one_label_per_trial)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _to_mrk_code_to_name_and_y(name_to_codes):
|
|
127
|
+
# Create mapping from marker code to class name and y=classindex
|
|
128
|
+
mrk_code_to_name_and_y = {}
|
|
129
|
+
for i_class, class_name in enumerate(name_to_codes):
|
|
130
|
+
codes = name_to_codes[class_name]
|
|
131
|
+
if hasattr(codes, '__len__'):
|
|
132
|
+
for code in codes:
|
|
133
|
+
assert code not in mrk_code_to_name_and_y
|
|
134
|
+
mrk_code_to_name_and_y[code] = (class_name, i_class)
|
|
135
|
+
else:
|
|
136
|
+
assert codes not in mrk_code_to_name_and_y
|
|
137
|
+
mrk_code_to_name_and_y[codes] = (class_name, i_class)
|
|
138
|
+
return mrk_code_to_name_and_y
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _create_signal_target_from_start_and_ival(
|
|
142
|
+
data, events, fs, name_to_codes, epoch_ival_ms,
|
|
143
|
+
one_hot_labels, one_label_per_trial):
|
|
144
|
+
cnt_y, i_start_stops = _create_cnt_y_and_trial_bounds_from_start_and_ival(
|
|
145
|
+
data.shape[1], events, fs, name_to_codes, epoch_ival_ms
|
|
146
|
+
)
|
|
147
|
+
signal_target = _create_signal_target_from_cnt_y_start_stops(
|
|
148
|
+
data, cnt_y, i_start_stops, prepad_trials_to_n_samples=None,
|
|
149
|
+
one_hot_labels=one_hot_labels,
|
|
150
|
+
one_label_per_trial=one_label_per_trial)
|
|
151
|
+
# make into arrray as all should have same dimensions
|
|
152
|
+
signal_target.X = np.array(signal_target.X, dtype=np.float32)
|
|
153
|
+
signal_target.y = np.array(signal_target.y, dtype=np.int64)
|
|
154
|
+
return signal_target
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _create_cnt_y_and_trial_bounds_from_start_and_ival(
|
|
158
|
+
n_samples, events, fs, name_to_start_codes, epoch_ival_ms):
|
|
159
|
+
ival_in_samples = ms_to_samples(np.array(epoch_ival_ms), fs)
|
|
160
|
+
start_offset = np.int32(np.round(ival_in_samples[0]))
|
|
161
|
+
# we will use ceil but exclusive...
|
|
162
|
+
stop_offset = np.int32(np.ceil(ival_in_samples[1]))
|
|
163
|
+
mrk_code_to_name_and_y = _to_mrk_code_to_name_and_y(name_to_start_codes)
|
|
164
|
+
class_to_n_trials = Counter()
|
|
165
|
+
n_classes = len(name_to_start_codes)
|
|
166
|
+
cnt_y = np.zeros((n_samples, n_classes), dtype=np.int64)
|
|
167
|
+
i_start_stops = []
|
|
168
|
+
for i_sample, mrk_code in zip(events[:, 0], events[:, 1]):
|
|
169
|
+
start_sample = int(i_sample) + start_offset
|
|
170
|
+
stop_sample = int(i_sample) + stop_offset
|
|
171
|
+
if mrk_code in mrk_code_to_name_and_y:
|
|
172
|
+
if start_sample < 0:
|
|
173
|
+
log.warning(
|
|
174
|
+
"Ignore trial with marker code {:d}, would start at "
|
|
175
|
+
"sample {:d}".format(mrk_code, start_sample))
|
|
176
|
+
continue
|
|
177
|
+
if stop_sample > n_samples:
|
|
178
|
+
log.warning("Ignore trial with marker code {:d}, would end at "
|
|
179
|
+
"sample {:d} of {:d}".format(
|
|
180
|
+
mrk_code, stop_sample - 1, n_samples - 1))
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
name, this_y = mrk_code_to_name_and_y[mrk_code]
|
|
184
|
+
i_start_stops.append((start_sample, stop_sample))
|
|
185
|
+
cnt_y[start_sample:stop_sample, this_y] = 1
|
|
186
|
+
class_to_n_trials[name] += 1
|
|
187
|
+
log.info("Trial per class:\n{:s}".format(str(class_to_n_trials)))
|
|
188
|
+
return cnt_y, i_start_stops
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _create_signal_target_from_start_and_stop(
|
|
192
|
+
data, events, fs, name_to_start_codes, epoch_ival_ms,
|
|
193
|
+
name_to_stop_codes, prepad_trials_to_n_samples,
|
|
194
|
+
one_hot_labels, one_label_per_trial, ):
|
|
195
|
+
assert np.array_equal(list(name_to_start_codes.keys()),
|
|
196
|
+
list(name_to_stop_codes.keys()))
|
|
197
|
+
cnt_y, i_start_stops = _create_cnt_y_and_trial_bounds_from_start_stop(
|
|
198
|
+
data.shape[1],events, fs,name_to_start_codes, epoch_ival_ms,
|
|
199
|
+
name_to_stop_codes)
|
|
200
|
+
signal_target = _create_signal_target_from_cnt_y_start_stops(
|
|
201
|
+
data, cnt_y, i_start_stops,
|
|
202
|
+
prepad_trials_to_n_samples=prepad_trials_to_n_samples,
|
|
203
|
+
one_hot_labels=one_hot_labels, one_label_per_trial=one_label_per_trial)
|
|
204
|
+
return signal_target
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _create_cnt_y_and_trial_bounds_from_start_stop(
|
|
208
|
+
n_samples, events, fs, name_to_start_codes, epoch_ival_ms,
|
|
209
|
+
name_to_stop_codes):
|
|
210
|
+
"""
|
|
211
|
+
Create a one-hot-encoded continuous marker array (cnt_y).
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
n_samples: int
|
|
216
|
+
Number of samples=timesteps in the recorded data.
|
|
217
|
+
events: 2d-array
|
|
218
|
+
Dimensions: Number of events, 2. For each event, should contain sample
|
|
219
|
+
index and marker code.
|
|
220
|
+
fs: number
|
|
221
|
+
Sampling rate.
|
|
222
|
+
name_to_start_codes: OrderedDict (str -> int or list of int)
|
|
223
|
+
Ordered dictionary mapping class names to marker code or marker codes.
|
|
224
|
+
y-labels will be assigned in increasing key order, i.e.
|
|
225
|
+
first classname gets y-value 0, second classname y-value 1, etc.
|
|
226
|
+
epoch_ival_ms: iterable of (int,int)
|
|
227
|
+
Epoching interval in milliseconds. In case only `name_to_codes` given,
|
|
228
|
+
represents start offset and stop offset from start markers. In case
|
|
229
|
+
`name_to_stop_codes` given, represents offset from start marker
|
|
230
|
+
and offset from stop marker. E.g. [500, -500] would mean 500ms
|
|
231
|
+
after the start marker until 500 ms before the stop marker.
|
|
232
|
+
name_to_stop_codes: dict (str -> int or list of int), optional
|
|
233
|
+
Dictionary mapping class names to stop marker code or stop marker codes.
|
|
234
|
+
Order does not matter, dictionary should contain each class in
|
|
235
|
+
`name_to_codes` dictionary.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
cnt_y: 2d-array
|
|
240
|
+
Timeseries of one-hot-labels, time x classes.
|
|
241
|
+
trial_bounds: list of (int,int)
|
|
242
|
+
List of (trial_start, trial_stop) tuples.
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
assert np.array_equal(list(name_to_start_codes.keys()),
|
|
247
|
+
list(name_to_stop_codes.keys()))
|
|
248
|
+
events = np.asarray(events)
|
|
249
|
+
ival_in_samples = ms_to_samples(np.array(epoch_ival_ms), fs)
|
|
250
|
+
start_offset = np.int32(np.round(ival_in_samples[0]))
|
|
251
|
+
# we will use ceil but exclusive...
|
|
252
|
+
stop_offset = np.int32(np.ceil(ival_in_samples[1]))
|
|
253
|
+
start_code_to_name_and_y = _to_mrk_code_to_name_and_y(name_to_start_codes)
|
|
254
|
+
# Ensure all stop marker codes are iterables
|
|
255
|
+
for name in name_to_stop_codes:
|
|
256
|
+
codes = name_to_stop_codes[name]
|
|
257
|
+
if not hasattr(codes, '__len__'):
|
|
258
|
+
name_to_stop_codes[name] = [codes]
|
|
259
|
+
all_stop_codes = np.concatenate(list(name_to_stop_codes.values())
|
|
260
|
+
).astype(np.int64)
|
|
261
|
+
class_to_n_trials = Counter()
|
|
262
|
+
n_classes = len(name_to_start_codes)
|
|
263
|
+
cnt_y = np.zeros((n_samples, n_classes), dtype=np.int64)
|
|
264
|
+
|
|
265
|
+
event_samples = events[:, 0]
|
|
266
|
+
event_codes = events[:, 1]
|
|
267
|
+
i_start_stops = []
|
|
268
|
+
i_event = 0
|
|
269
|
+
first_start_code_found = False
|
|
270
|
+
while i_event < len(events):
|
|
271
|
+
while i_event < len(events) and (
|
|
272
|
+
event_codes[i_event] not in start_code_to_name_and_y):
|
|
273
|
+
i_event += 1
|
|
274
|
+
if i_event < len(events):
|
|
275
|
+
start_sample = event_samples[i_event]
|
|
276
|
+
start_code = event_codes[i_event]
|
|
277
|
+
start_name = start_code_to_name_and_y[start_code][0]
|
|
278
|
+
start_y = start_code_to_name_and_y[start_code][1]
|
|
279
|
+
i_event += 1
|
|
280
|
+
first_start_code_found = True
|
|
281
|
+
waiting_for_end_code = True
|
|
282
|
+
|
|
283
|
+
while i_event < len(events) and (
|
|
284
|
+
event_codes[i_event] not in all_stop_codes):
|
|
285
|
+
if event_codes[i_event] in start_code_to_name_and_y:
|
|
286
|
+
log.warning(
|
|
287
|
+
"New start marker {:.0f} at {:.0f} samples found, "
|
|
288
|
+
"no end marker for earlier start marker {:.0f} "
|
|
289
|
+
"at {:.0f} samples found.".format(
|
|
290
|
+
event_codes[i_event], event_samples[i_event],
|
|
291
|
+
start_code, start_sample))
|
|
292
|
+
start_sample = event_samples[i_event]
|
|
293
|
+
start_name = start_code_to_name_and_y[start_code][0]
|
|
294
|
+
start_code = event_codes[i_event]
|
|
295
|
+
start_y = start_code_to_name_and_y[start_code][1]
|
|
296
|
+
i_event += 1
|
|
297
|
+
if i_event == len(events):
|
|
298
|
+
if waiting_for_end_code:
|
|
299
|
+
log.warning(("No end marker for start marker code {:.0f} "
|
|
300
|
+
"at sample {:.0f} found.").format(start_code,
|
|
301
|
+
start_sample))
|
|
302
|
+
elif (not first_start_code_found):
|
|
303
|
+
log.warning("No markers found at all.")
|
|
304
|
+
break
|
|
305
|
+
stop_sample = event_samples[i_event]
|
|
306
|
+
stop_code = event_codes[i_event]
|
|
307
|
+
assert stop_code in name_to_stop_codes[start_name]
|
|
308
|
+
i_start = int(start_sample) + start_offset
|
|
309
|
+
i_stop = int(stop_sample) + stop_offset
|
|
310
|
+
cnt_y[i_start:i_stop, start_y] = 1
|
|
311
|
+
i_start_stops.append((i_start, i_stop))
|
|
312
|
+
class_to_n_trials[start_name] += 1
|
|
313
|
+
waiting_for_end_code = False
|
|
314
|
+
|
|
315
|
+
log.info("Trial per class:\n{:s}".format(str(class_to_n_trials)))
|
|
316
|
+
return cnt_y, i_start_stops
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _create_signal_target_from_cnt_y_start_stops(
|
|
320
|
+
data,
|
|
321
|
+
cnt_y,
|
|
322
|
+
i_start_stops,
|
|
323
|
+
prepad_trials_to_n_samples,
|
|
324
|
+
one_hot_labels,
|
|
325
|
+
one_label_per_trial):
|
|
326
|
+
if prepad_trials_to_n_samples is not None:
|
|
327
|
+
new_i_start_stops = []
|
|
328
|
+
for i_start, i_stop in i_start_stops:
|
|
329
|
+
if (i_stop - i_start) > prepad_trials_to_n_samples:
|
|
330
|
+
new_i_start_stops.append((i_start, i_stop))
|
|
331
|
+
elif i_stop >= prepad_trials_to_n_samples:
|
|
332
|
+
new_i_start_stops.append(
|
|
333
|
+
(i_stop - prepad_trials_to_n_samples, i_stop))
|
|
334
|
+
else:
|
|
335
|
+
log.warning("Could not pad trial enough, therefore not "
|
|
336
|
+
"not using trial from {:d} to {:d}".format(
|
|
337
|
+
i_start, i_stop
|
|
338
|
+
))
|
|
339
|
+
continue
|
|
340
|
+
|
|
341
|
+
else:
|
|
342
|
+
new_i_start_stops = i_start_stops
|
|
343
|
+
|
|
344
|
+
X = []
|
|
345
|
+
y = []
|
|
346
|
+
for i_start, i_stop in new_i_start_stops:
|
|
347
|
+
if i_start < 0:
|
|
348
|
+
log.warning("Trial start too early, therefore not "
|
|
349
|
+
"not using trial from {:d} to {:d}".format(
|
|
350
|
+
i_start, i_stop
|
|
351
|
+
))
|
|
352
|
+
continue
|
|
353
|
+
if i_stop > data.shape[1]:
|
|
354
|
+
log.warning("Trial stop too late (past {:d}), therefore not "
|
|
355
|
+
"not using trial from {:d} to {:d}".format(
|
|
356
|
+
data.shape[1] - 1,
|
|
357
|
+
i_start, i_stop
|
|
358
|
+
))
|
|
359
|
+
continue
|
|
360
|
+
X.append(data[:, i_start:i_stop].astype(np.float32))
|
|
361
|
+
y.append(cnt_y[i_start:i_stop])
|
|
362
|
+
|
|
363
|
+
# take last label always
|
|
364
|
+
if one_label_per_trial:
|
|
365
|
+
new_y = []
|
|
366
|
+
for this_y in y:
|
|
367
|
+
# if destroying one hot later, just set most occuring class to 1
|
|
368
|
+
unique_labels, counts = np.unique(
|
|
369
|
+
this_y, axis=0, return_counts=True)
|
|
370
|
+
if not one_hot_labels:
|
|
371
|
+
meaned_y = np.mean(this_y, axis=0)
|
|
372
|
+
this_new_y = np.zeros_like(meaned_y)
|
|
373
|
+
this_new_y[np.argmax(meaned_y)] = 1
|
|
374
|
+
else:
|
|
375
|
+
# take most frequency occurring label combination
|
|
376
|
+
this_new_y = unique_labels[np.argmax(counts)]
|
|
377
|
+
|
|
378
|
+
if len(unique_labels) > 1:
|
|
379
|
+
log.warning("Different labels within one trial: {:s},"
|
|
380
|
+
"setting single trial label to {:s}".format(
|
|
381
|
+
str(unique_labels), str(this_new_y)
|
|
382
|
+
))
|
|
383
|
+
new_y.append(this_new_y)
|
|
384
|
+
y = new_y
|
|
385
|
+
if not one_hot_labels:
|
|
386
|
+
# change from one-hot-encoding to regular encoding
|
|
387
|
+
# with -1 as indication none of the classes are present
|
|
388
|
+
new_y = []
|
|
389
|
+
for this_y in y:
|
|
390
|
+
if one_label_per_trial:
|
|
391
|
+
if np.sum(this_y) == 0:
|
|
392
|
+
this_new_y = -1
|
|
393
|
+
else:
|
|
394
|
+
this_new_y = np.argmax(this_y)
|
|
395
|
+
if np.sum(this_y) > 1:
|
|
396
|
+
log.warning(
|
|
397
|
+
"Have multiple active classes and will convert to "
|
|
398
|
+
"lowest class")
|
|
399
|
+
else:
|
|
400
|
+
if np.max(np.sum(this_y, axis=1)) > 1:
|
|
401
|
+
log.warning(
|
|
402
|
+
"Have multiple active classes and will convert to "
|
|
403
|
+
"lowest class")
|
|
404
|
+
this_new_y = np.argmax(this_y, axis=1)
|
|
405
|
+
this_new_y[np.sum(this_y, axis=1) == 0] = -1
|
|
406
|
+
new_y.append(this_new_y)
|
|
407
|
+
y = new_y
|
|
408
|
+
if one_label_per_trial:
|
|
409
|
+
y = np.array(y, dtype=np.int64)
|
|
410
|
+
|
|
411
|
+
return SignalAndTarget(X, y)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def create_signal_target_with_breaks_from_mne(
|
|
415
|
+
cnt, name_to_start_codes,
|
|
416
|
+
trial_epoch_ival_ms,
|
|
417
|
+
name_to_stop_codes,
|
|
418
|
+
min_break_length_ms, max_break_length_ms,
|
|
419
|
+
break_epoch_ival_ms,
|
|
420
|
+
prepad_trials_to_n_samples=None):
|
|
421
|
+
"""
|
|
422
|
+
Create SignalTarget set from given `mne.io.RawArray`.
|
|
423
|
+
|
|
424
|
+
Parameters
|
|
425
|
+
----------
|
|
426
|
+
cnt: `mne.io.RawArray`
|
|
427
|
+
name_to_start_codes: OrderedDict (str -> int or list of int)
|
|
428
|
+
Ordered dictionary mapping class names to marker code or marker codes.
|
|
429
|
+
y-labels will be assigned in increasing key order, i.e.
|
|
430
|
+
first classname gets y-value 0, second classname y-value 1, etc.
|
|
431
|
+
trial_epoch_ival_ms: iterable of (int,int)
|
|
432
|
+
Epoching interval in milliseconds. Represents offset from start marker
|
|
433
|
+
and offset from stop marker. E.g. [500, -500] would mean 500ms
|
|
434
|
+
after the start marker until 500 ms before the stop marker.
|
|
435
|
+
name_to_stop_codes: dict (str -> int or list of int), optional
|
|
436
|
+
Dictionary mapping class names to stop marker code or stop marker codes.
|
|
437
|
+
Order does not matter, dictionary should contain each class in
|
|
438
|
+
`name_to_codes` dictionary.
|
|
439
|
+
min_break_length_ms: number
|
|
440
|
+
Breaks below this length are excluded.
|
|
441
|
+
max_break_length_ms: number
|
|
442
|
+
Breaks above this length are excluded.
|
|
443
|
+
break_epoch_ival_ms: number
|
|
444
|
+
Break ival, offset from trial end to start of the break in ms and
|
|
445
|
+
offset from trial start to end of break in ms.
|
|
446
|
+
prepad_trials_to_n_samples: int
|
|
447
|
+
Pad trials that would be too short with the signal before it (only
|
|
448
|
+
valid if name_to_stop_codes is not None).
|
|
449
|
+
|
|
450
|
+
Returns
|
|
451
|
+
-------
|
|
452
|
+
dataset: :class:`.SignalAndTarget`
|
|
453
|
+
Dataset with `X` as the trial signals and `y` as the trial labels.
|
|
454
|
+
Labels as timeseries and of integers, i.e., not one-hot encoded.
|
|
455
|
+
"""
|
|
456
|
+
assert 'Break' not in name_to_start_codes
|
|
457
|
+
# Create new marker codes for start and stop of breaks
|
|
458
|
+
# Use marker codes that did not exist in the given marker codes...
|
|
459
|
+
all_start_codes = np.concatenate(
|
|
460
|
+
[np.atleast_1d(vals) for vals in name_to_start_codes.values()])
|
|
461
|
+
all_stop_codes = np.concatenate(
|
|
462
|
+
[np.atleast_1d(vals) for vals in name_to_stop_codes.values()])
|
|
463
|
+
break_start_code = -1
|
|
464
|
+
while break_start_code in np.concatenate((all_start_codes, all_stop_codes)):
|
|
465
|
+
break_start_code -= 1
|
|
466
|
+
break_stop_code = break_start_code - 1
|
|
467
|
+
while break_stop_code in np.concatenate((all_start_codes, all_stop_codes)):
|
|
468
|
+
break_stop_code -= 1
|
|
469
|
+
|
|
470
|
+
events = cnt.info['events'][:, [0, 2]]
|
|
471
|
+
# later trial segment ival will be added when creating set
|
|
472
|
+
# so remove it here
|
|
473
|
+
break_epoch_ival_ms = np.array(break_epoch_ival_ms) - (
|
|
474
|
+
np.array(trial_epoch_ival_ms))
|
|
475
|
+
events_with_breaks = add_breaks(events, cnt.info['sfreq'],
|
|
476
|
+
break_start_code, break_stop_code,
|
|
477
|
+
name_to_start_codes, name_to_stop_codes,
|
|
478
|
+
min_break_length_ms=min_break_length_ms,
|
|
479
|
+
max_break_length_ms=max_break_length_ms,
|
|
480
|
+
break_start_offset_ms=break_epoch_ival_ms[
|
|
481
|
+
0],
|
|
482
|
+
break_stop_offset_ms=break_epoch_ival_ms[
|
|
483
|
+
1])
|
|
484
|
+
|
|
485
|
+
name_to_start_codes_with_breaks = deepcopy(name_to_start_codes)
|
|
486
|
+
name_to_start_codes_with_breaks['Break'] = break_start_code
|
|
487
|
+
name_to_stop_codes_with_breaks = deepcopy(name_to_stop_codes)
|
|
488
|
+
name_to_stop_codes_with_breaks['Break'] = break_stop_code
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
data = cnt.get_data()
|
|
492
|
+
fs = cnt.info['sfreq']
|
|
493
|
+
signal_target = create_signal_target(
|
|
494
|
+
data, events_with_breaks, fs,
|
|
495
|
+
name_to_start_codes_with_breaks, trial_epoch_ival_ms,
|
|
496
|
+
name_to_stop_codes_with_breaks,
|
|
497
|
+
prepad_trials_to_n_samples=prepad_trials_to_n_samples,
|
|
498
|
+
one_hot_labels=False,
|
|
499
|
+
one_label_per_trial=False)
|
|
500
|
+
|
|
501
|
+
return signal_target
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def add_breaks(
|
|
505
|
+
events, fs, break_start_code, break_stop_code, name_to_start_codes,
|
|
506
|
+
name_to_stop_codes, min_break_length_ms=None,
|
|
507
|
+
max_break_length_ms=None, break_start_offset_ms=None,
|
|
508
|
+
break_stop_offset_ms=None):
|
|
509
|
+
"""
|
|
510
|
+
Add break events to given events.
|
|
511
|
+
|
|
512
|
+
Parameters
|
|
513
|
+
----------
|
|
514
|
+
events: 2d-array
|
|
515
|
+
Dimensions: Number of events, 2. For each event, should contain sample
|
|
516
|
+
index and marker code.
|
|
517
|
+
fs: number
|
|
518
|
+
Sampling rate.
|
|
519
|
+
break_start_code: int
|
|
520
|
+
Marker code that will be used for break start markers.
|
|
521
|
+
break_stop_code: int
|
|
522
|
+
Marker code that will be used for break stop markers.
|
|
523
|
+
name_to_start_codes: OrderedDict (str -> int or list of int)
|
|
524
|
+
Ordered dictionary mapping class names to start marker code or
|
|
525
|
+
start marker codes.
|
|
526
|
+
name_to_stop_codes: dict (str -> int or list of int), optional
|
|
527
|
+
Dictionary mapping class names to stop marker code or stop marker codes.
|
|
528
|
+
min_break_length_ms: number, optional
|
|
529
|
+
Minimum length in milliseconds a break should have to be included.
|
|
530
|
+
max_break_length_ms: number, optional
|
|
531
|
+
Maximum length in milliseconds a break can have to be included.
|
|
532
|
+
break_start_offset_ms: number, optional
|
|
533
|
+
What offset from trial end to start of the break in ms.
|
|
534
|
+
break_stop_offset_ms: number, optional
|
|
535
|
+
What offset from next trial start end to previous break end in ms.
|
|
536
|
+
|
|
537
|
+
Returns
|
|
538
|
+
-------
|
|
539
|
+
events: 2d-array
|
|
540
|
+
Events with break start and stop markers.
|
|
541
|
+
"""
|
|
542
|
+
min_samples = (None if min_break_length_ms is None
|
|
543
|
+
else ms_to_samples(min_break_length_ms, fs))
|
|
544
|
+
max_samples = (None if max_break_length_ms is None
|
|
545
|
+
else ms_to_samples(max_break_length_ms, fs))
|
|
546
|
+
orig_events = events
|
|
547
|
+
break_starts, break_stops = _extract_break_start_stop_ms(
|
|
548
|
+
events, name_to_start_codes, name_to_stop_codes)
|
|
549
|
+
|
|
550
|
+
break_durations = break_stops - break_starts
|
|
551
|
+
valid_mask = np.array([True] * len(break_starts))
|
|
552
|
+
if min_samples is not None:
|
|
553
|
+
valid_mask[break_durations < min_samples] = False
|
|
554
|
+
if max_samples is not None:
|
|
555
|
+
valid_mask[break_durations > max_samples] = False
|
|
556
|
+
if sum(valid_mask) == 0:
|
|
557
|
+
return deepcopy(events)
|
|
558
|
+
break_starts = break_starts[valid_mask]
|
|
559
|
+
break_stops = break_stops[valid_mask]
|
|
560
|
+
if break_start_offset_ms is not None:
|
|
561
|
+
break_starts += int(round(ms_to_samples(break_start_offset_ms, fs)))
|
|
562
|
+
if break_stop_offset_ms is not None:
|
|
563
|
+
break_stops += int(round(ms_to_samples(break_stop_offset_ms, fs)))
|
|
564
|
+
break_events = np.zeros((len(break_starts) * 2, 2))
|
|
565
|
+
break_events[0::2, 0] = break_starts
|
|
566
|
+
break_events[1::2, 0] = break_stops
|
|
567
|
+
break_events[0::2, 1] = break_start_code
|
|
568
|
+
break_events[1::2, 1] = break_stop_code
|
|
569
|
+
|
|
570
|
+
new_events = np.concatenate((orig_events, break_events))
|
|
571
|
+
# sort events
|
|
572
|
+
sort_order = np.argsort(new_events[:, 0], kind='mergesort')
|
|
573
|
+
new_events = new_events[sort_order]
|
|
574
|
+
return new_events
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def _extract_break_start_stop_ms(events, name_to_start_codes,
|
|
578
|
+
name_to_stop_codes):
|
|
579
|
+
assert len(events[0]) == 2, "expect only 2dimensional event array here"
|
|
580
|
+
start_code_to_name_and_y = _to_mrk_code_to_name_and_y(name_to_start_codes)
|
|
581
|
+
# Ensure all stop marker codes are iterables
|
|
582
|
+
for name in name_to_stop_codes:
|
|
583
|
+
codes = name_to_stop_codes[name]
|
|
584
|
+
if not hasattr(codes, '__len__'):
|
|
585
|
+
name_to_stop_codes[name] = [codes]
|
|
586
|
+
all_stop_codes = np.concatenate(list(name_to_stop_codes.values())).astype(
|
|
587
|
+
np.int32)
|
|
588
|
+
event_samples = events[:, 0]
|
|
589
|
+
event_codes = events[:, 1]
|
|
590
|
+
|
|
591
|
+
break_starts = []
|
|
592
|
+
break_stops = []
|
|
593
|
+
i_event = 0
|
|
594
|
+
while i_event < len(events):
|
|
595
|
+
while (i_event < len(events)) and (
|
|
596
|
+
event_codes[i_event] not in all_stop_codes):
|
|
597
|
+
i_event += 1
|
|
598
|
+
if i_event < len(events):
|
|
599
|
+
# one sample after start
|
|
600
|
+
stop_sample = event_samples[i_event]
|
|
601
|
+
stop_code = event_codes[i_event]
|
|
602
|
+
i_event += 1
|
|
603
|
+
while (i_event < len(events)) and (
|
|
604
|
+
event_codes[i_event] not in start_code_to_name_and_y):
|
|
605
|
+
if event_codes[i_event] in all_stop_codes:
|
|
606
|
+
log.warning(
|
|
607
|
+
"New end marker {:.0f} at {:.0f} samples found, "
|
|
608
|
+
"no start marker for earlier end marker {:.0f} "
|
|
609
|
+
"at {:.0f} samples found.".format(
|
|
610
|
+
event_codes[i_event],
|
|
611
|
+
event_samples[i_event],
|
|
612
|
+
stop_code, stop_sample))
|
|
613
|
+
stop_sample = event_samples[i_event]
|
|
614
|
+
stop_code = event_codes[i_event]
|
|
615
|
+
i_event += 1
|
|
616
|
+
|
|
617
|
+
if i_event == len(events):
|
|
618
|
+
break
|
|
619
|
+
|
|
620
|
+
start_sample = event_samples[i_event]
|
|
621
|
+
start_code = event_codes[i_event]
|
|
622
|
+
assert start_code in start_code_to_name_and_y
|
|
623
|
+
# let's start one after stop of the trial and stop one efore
|
|
624
|
+
# start of the trial to ensure that markers will be
|
|
625
|
+
# in right order
|
|
626
|
+
break_starts.append(stop_sample + 1)
|
|
627
|
+
break_stops.append(start_sample - 1)
|
|
628
|
+
return np.array(break_starts), np.array(break_stops)
|