braindecode 0.1.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.1.0/Braindecode.egg-info/PKG-INFO +23 -0
- Braindecode-0.1.0/Braindecode.egg-info/SOURCES.txt +11 -0
- Braindecode-0.1.0/Braindecode.egg-info/dependency_links.txt +1 -0
- Braindecode-0.1.0/Braindecode.egg-info/requires.txt +2 -0
- Braindecode-0.1.0/Braindecode.egg-info/top_level.txt +1 -0
- Braindecode-0.1.0/PKG-INFO +23 -0
- Braindecode-0.1.0/braindecode/__init__.py +0 -0
- Braindecode-0.1.0/braindecode/iterators.py +216 -0
- Braindecode-0.1.0/braindecode/splitters.py +18 -0
- Braindecode-0.1.0/braindecode/trial_segment.py +198 -0
- Braindecode-0.1.0/braindecode/util.py +156 -0
- Braindecode-0.1.0/setup.cfg +4 -0
- Braindecode-0.1.0/setup.py +62 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 1.1
|
|
2
|
+
Name: Braindecode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A deep learning for raw time-domain EEG decoding toolbox.
|
|
5
|
+
Home-page: https://github.com/robintibor/braindecode
|
|
6
|
+
Author: Robin Tibor Schirrmeister
|
|
7
|
+
Author-email: robintibor@gmail.com
|
|
8
|
+
License: BSD 3-Clause
|
|
9
|
+
Description: Note: The old braindecode repository has been moved to https://github.com/robintibor/braindevel.
|
|
10
|
+
|
|
11
|
+
# Braindecode
|
|
12
|
+
A deep learning for raw time-domain EEG decoding toolbox.
|
|
13
|
+
|
|
14
|
+
Keywords: eeg deep-learning brain-state-decoding
|
|
15
|
+
Platform: UNKNOWN
|
|
16
|
+
Classifier: Development Status :: 3 - Alpha
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: Science/Research
|
|
19
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
22
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
setup.py
|
|
2
|
+
Braindecode.egg-info/PKG-INFO
|
|
3
|
+
Braindecode.egg-info/SOURCES.txt
|
|
4
|
+
Braindecode.egg-info/dependency_links.txt
|
|
5
|
+
Braindecode.egg-info/requires.txt
|
|
6
|
+
Braindecode.egg-info/top_level.txt
|
|
7
|
+
braindecode/__init__.py
|
|
8
|
+
braindecode/iterators.py
|
|
9
|
+
braindecode/splitters.py
|
|
10
|
+
braindecode/trial_segment.py
|
|
11
|
+
braindecode/util.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
braindecode
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 1.1
|
|
2
|
+
Name: Braindecode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A deep learning for raw time-domain EEG decoding toolbox.
|
|
5
|
+
Home-page: https://github.com/robintibor/braindecode
|
|
6
|
+
Author: Robin Tibor Schirrmeister
|
|
7
|
+
Author-email: robintibor@gmail.com
|
|
8
|
+
License: BSD 3-Clause
|
|
9
|
+
Description: Note: The old braindecode repository has been moved to https://github.com/robintibor/braindevel.
|
|
10
|
+
|
|
11
|
+
# Braindecode
|
|
12
|
+
A deep learning for raw time-domain EEG decoding toolbox.
|
|
13
|
+
|
|
14
|
+
Keywords: eeg deep-learning brain-state-decoding
|
|
15
|
+
Platform: UNKNOWN
|
|
16
|
+
Classifier: Development Status :: 3 - Alpha
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: Science/Research
|
|
19
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
22
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
File without changes
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy.random import RandomState
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_balanced_batches(n_trials, rng, shuffle, n_batches=None,
|
|
6
|
+
batch_size=None):
|
|
7
|
+
"""Create indices for batches balanced in size (batches will have maximum size difference of 1).
|
|
8
|
+
Supply either batch size or number of batches. Resulting batches
|
|
9
|
+
will not have the given batch size but rather the next largest batch size
|
|
10
|
+
that allows to split the set into balanced batches (maximum size difference 1).
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
n_trials : int
|
|
15
|
+
Size of set.
|
|
16
|
+
rng :
|
|
17
|
+
|
|
18
|
+
shuffle :
|
|
19
|
+
Whether to shuffle indices before splitting set.
|
|
20
|
+
n_batches :
|
|
21
|
+
(Default value = None)
|
|
22
|
+
batch_size :
|
|
23
|
+
(Default value = None)
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
assert batch_size is not None or n_batches is not None
|
|
30
|
+
if n_batches is None:
|
|
31
|
+
n_batches = int(np.round(n_trials / float(batch_size)))
|
|
32
|
+
|
|
33
|
+
if n_batches > 0:
|
|
34
|
+
min_batch_size = n_trials // n_batches
|
|
35
|
+
n_batches_with_extra_trial = n_trials % n_batches
|
|
36
|
+
else:
|
|
37
|
+
n_batches = 1
|
|
38
|
+
min_batch_size = n_trials
|
|
39
|
+
n_batches_with_extra_trial = 0
|
|
40
|
+
assert n_batches_with_extra_trial < n_batches
|
|
41
|
+
all_inds = np.array(range(n_trials))
|
|
42
|
+
if shuffle:
|
|
43
|
+
rng.shuffle(all_inds)
|
|
44
|
+
i_start_trial = 0
|
|
45
|
+
i_stop_trial = 0
|
|
46
|
+
batches = []
|
|
47
|
+
for i_batch in range(n_batches):
|
|
48
|
+
i_stop_trial += min_batch_size
|
|
49
|
+
if i_batch < n_batches_with_extra_trial:
|
|
50
|
+
i_stop_trial += 1
|
|
51
|
+
batch_inds = all_inds[range(i_start_trial, i_stop_trial)]
|
|
52
|
+
batches.append(batch_inds)
|
|
53
|
+
i_start_trial = i_stop_trial
|
|
54
|
+
assert i_start_trial == n_trials
|
|
55
|
+
return batches
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BalancedBatchSizeIterator(object):
|
|
59
|
+
"""
|
|
60
|
+
Create batches of balanced size.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
batch_size: int
|
|
65
|
+
Resulting batches will not necessarily have the given batch size
|
|
66
|
+
but rather the next largest batch size that allows to split the set into
|
|
67
|
+
balanced batches (maximum size difference 1).
|
|
68
|
+
"""
|
|
69
|
+
def __init__(self, batch_size):
|
|
70
|
+
self.batch_size = batch_size
|
|
71
|
+
self.rng = RandomState(328774)
|
|
72
|
+
|
|
73
|
+
def get_batches(self, dataset, shuffle):
|
|
74
|
+
n_trials = dataset.X.shape[0]
|
|
75
|
+
batches = get_balanced_batches(n_trials,
|
|
76
|
+
batch_size=self.batch_size,
|
|
77
|
+
rng=self.rng,
|
|
78
|
+
shuffle=shuffle)
|
|
79
|
+
for batch_inds in batches:
|
|
80
|
+
yield (dataset.X[batch_inds], dataset.y[batch_inds])
|
|
81
|
+
|
|
82
|
+
def reset_rng(self):
|
|
83
|
+
self.rng = RandomState(328774)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class CropsFromTrialsIterator(object):
|
|
87
|
+
def __init__(self, batch_size, input_time_length, n_preds_per_input,
|
|
88
|
+
check_preds_smaller_trial_len=True):
|
|
89
|
+
# TODO: remove this check preds smaller trila len and check that
|
|
90
|
+
# trials are smaller than input length
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
batch_size: int
|
|
96
|
+
input_time_length: int
|
|
97
|
+
Input time length of the ConvNet, determines size of batches in
|
|
98
|
+
3rd dimension.
|
|
99
|
+
n_preds_per_input: int
|
|
100
|
+
Number of predictions ConvNet makes per one input. Can be computed
|
|
101
|
+
by making a forward pass with the given input time length, the
|
|
102
|
+
output length in 3rd dimension is n_preds_per_input.
|
|
103
|
+
"""
|
|
104
|
+
self.batch_size = batch_size
|
|
105
|
+
self.input_time_length = input_time_length
|
|
106
|
+
self.n_preds_per_input = n_preds_per_input
|
|
107
|
+
self.check_preds_smaller_trial_len = check_preds_smaller_trial_len
|
|
108
|
+
self.rng = RandomState((2017, 6, 28))
|
|
109
|
+
|
|
110
|
+
def reset_rng(self):
|
|
111
|
+
self.rng = RandomState((2017, 6, 28))
|
|
112
|
+
|
|
113
|
+
def get_batches(self, dataset, shuffle):
|
|
114
|
+
# start always at first predictable sample, so
|
|
115
|
+
# start at end of receptive field
|
|
116
|
+
n_receptive_field = self.input_time_length - self.n_preds_per_input + 1
|
|
117
|
+
i_trial_starts = [n_receptive_field - 1] * len(dataset.X)
|
|
118
|
+
i_trial_stops = [trial.shape[1] for trial in dataset.X]
|
|
119
|
+
|
|
120
|
+
if self.check_preds_smaller_trial_len:
|
|
121
|
+
check_trial_bounds(i_trial_starts, i_trial_stops,
|
|
122
|
+
self.n_preds_per_input)
|
|
123
|
+
start_stop_blocks_per_trial = compute_start_stop_block_inds(
|
|
124
|
+
i_trial_starts, i_trial_stops, self.input_time_length,
|
|
125
|
+
self.n_preds_per_input, self.check_preds_smaller_trial_len)
|
|
126
|
+
for i_trial, trial_blocks in enumerate(start_stop_blocks_per_trial):
|
|
127
|
+
assert trial_blocks[0][0] == 0
|
|
128
|
+
assert trial_blocks[-1][1] == i_trial_stops[i_trial]
|
|
129
|
+
|
|
130
|
+
return self.yield_block_batches(dataset.X, dataset.y,
|
|
131
|
+
start_stop_blocks_per_trial,
|
|
132
|
+
shuffle=shuffle)
|
|
133
|
+
|
|
134
|
+
def yield_block_batches(self, X, y, start_stop_blocks_per_trial, shuffle):
|
|
135
|
+
# add trial nr to start stop blocks and flatten at same time
|
|
136
|
+
i_trial_start_stop_block = [(i_trial, start, stop)
|
|
137
|
+
for i_trial, block in
|
|
138
|
+
enumerate(start_stop_blocks_per_trial)
|
|
139
|
+
for (start, stop) in block]
|
|
140
|
+
if shuffle:
|
|
141
|
+
self.rng.shuffle(i_trial_start_stop_block)
|
|
142
|
+
|
|
143
|
+
for i_block in range(0, len(i_trial_start_stop_block), self.batch_size):
|
|
144
|
+
i_block_stop = min(i_block + self.batch_size,
|
|
145
|
+
len(i_trial_start_stop_block))
|
|
146
|
+
start_stop_blocks = i_trial_start_stop_block[i_block:i_block_stop]
|
|
147
|
+
batch = create_batch_from_i_trial_start_stop_blocks(
|
|
148
|
+
X, y, start_stop_blocks, self.n_preds_per_input)
|
|
149
|
+
yield batch
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def check_trial_bounds(i_trial_starts, i_trial_stops, n_preds_per_input):
|
|
153
|
+
for start, stop in zip(i_trial_starts, i_trial_stops):
|
|
154
|
+
assert stop - start >= n_preds_per_input, (
|
|
155
|
+
"Trial should be longer or equal than number of sample preds, "
|
|
156
|
+
"Trial length: {:d}, sample preds {:d}...".
|
|
157
|
+
format(stop - start, n_preds_per_input))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def compute_start_stop_block_inds(i_trial_starts, i_trial_stops,
|
|
161
|
+
input_time_length, n_preds_per_input,
|
|
162
|
+
check_preds_smaller_trial_len):
|
|
163
|
+
# create start stop indices for all batches still 2d trial -> start stop
|
|
164
|
+
start_stop_blocks_per_trial = []
|
|
165
|
+
for i_trial in range(len(i_trial_starts)):
|
|
166
|
+
trial_start = i_trial_starts[i_trial]
|
|
167
|
+
trial_stop = i_trial_stops[i_trial]
|
|
168
|
+
start_stop_blocks = get_start_stop_blocks_for_trial(
|
|
169
|
+
trial_start, trial_stop, input_time_length,
|
|
170
|
+
n_preds_per_input)
|
|
171
|
+
|
|
172
|
+
if check_preds_smaller_trial_len:
|
|
173
|
+
# check that block is correct, all predicted samples together
|
|
174
|
+
# should be the trial samples
|
|
175
|
+
all_predicted_samples = [
|
|
176
|
+
range(stop - n_preds_per_input,
|
|
177
|
+
stop) for _,stop in start_stop_blocks]
|
|
178
|
+
# this check takes about 50 ms in performance test
|
|
179
|
+
# whereas loop itself takes only 5 ms.. deactivate it if not necessary
|
|
180
|
+
assert np.array_equal(
|
|
181
|
+
range(i_trial_starts[i_trial], i_trial_stops[i_trial]),
|
|
182
|
+
np.unique(np.concatenate(all_predicted_samples)))
|
|
183
|
+
|
|
184
|
+
start_stop_blocks_per_trial.append(start_stop_blocks)
|
|
185
|
+
return start_stop_blocks_per_trial
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def get_start_stop_blocks_for_trial(trial_start, trial_stop, input_time_length,
|
|
189
|
+
n_preds_per_input):
|
|
190
|
+
start_stop_blocks = []
|
|
191
|
+
i_window_stop = trial_start # now when we add sample preds in loop,
|
|
192
|
+
# first sample of trial corresponds to first prediction
|
|
193
|
+
while i_window_stop < trial_stop:
|
|
194
|
+
i_window_stop += n_preds_per_input
|
|
195
|
+
i_adjusted_stop = min(i_window_stop, trial_stop)
|
|
196
|
+
i_window_start = i_adjusted_stop - input_time_length
|
|
197
|
+
start_stop_blocks.append((i_window_start, i_adjusted_stop))
|
|
198
|
+
|
|
199
|
+
return start_stop_blocks
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def create_batch_from_i_trial_start_stop_blocks(X, y, i_trial_start_stop_block,
|
|
203
|
+
n_preds_per_input):
|
|
204
|
+
Xs = []
|
|
205
|
+
ys = []
|
|
206
|
+
for i_trial, start, stop in i_trial_start_stop_block:
|
|
207
|
+
Xs.append(X[i_trial][:,start:stop])
|
|
208
|
+
# one-hot-encode targets
|
|
209
|
+
#block_y = np.zeros((n_classes, n_preds_per_input), dtype=np.float32)
|
|
210
|
+
#block_y[y[i_trial]] = 1
|
|
211
|
+
#block_y = np.ones((n_preds_per_input), dtype=np.int64) * y[i_trial]
|
|
212
|
+
#ys.append(block_y)
|
|
213
|
+
ys.append(y[i_trial])
|
|
214
|
+
batch_X = np.array(Xs)
|
|
215
|
+
batch_y = np.array(ys)
|
|
216
|
+
return batch_X, batch_y
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from braindecode.datasets.signal_target import apply_to_X_y
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def concatenate_sets(set_a, set_b):
|
|
7
|
+
return apply_to_X_y(lambda a,b: np.concatenate((a,b), axis=0),
|
|
8
|
+
set_a, set_b)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def split_into_two_sets(dataset, first_set_fraction=None, n_boundary=None):
|
|
12
|
+
assert (first_set_fraction is None) != (n_boundary is None), (
|
|
13
|
+
"Supply either first_set_fraction or n_boundary")
|
|
14
|
+
if n_boundary is None:
|
|
15
|
+
n_boundary = int(round(len(dataset.X) * first_set_fraction))
|
|
16
|
+
first_set = apply_to_X_y(lambda a: a[:n_boundary], dataset)
|
|
17
|
+
second_set = apply_to_X_y(lambda a: a[n_boundary:], dataset)
|
|
18
|
+
return first_set, second_set
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import numpy as np
|
|
3
|
+
import xarray as xr
|
|
4
|
+
|
|
5
|
+
log = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_target_series(cnt, marker_def, ival):
|
|
9
|
+
"""
|
|
10
|
+
Compute one-hot encoded target series.
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
cnt : DateArray
|
|
14
|
+
marker_def: OrderedDict (str -> list int)
|
|
15
|
+
Dictionary mapping class names -> list of marker codes. Order of keys
|
|
16
|
+
is used to determine indices of classes in one-hot encoded result.
|
|
17
|
+
ival : (number, number) tuple
|
|
18
|
+
Segmentation interval for each trial.
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
2darray
|
|
22
|
+
One-hot encoded target series.
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
assert 'fs' in cnt.attrs
|
|
26
|
+
assert 'events' in cnt.attrs
|
|
27
|
+
assert ival[0] < ival[1]
|
|
28
|
+
sample_ival = cnt.attrs['fs'] * np.array(ival) / 1000.0
|
|
29
|
+
sample_ival = np.int32(np.round(sample_ival))
|
|
30
|
+
assert sample_ival[0] < sample_ival[1]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class_names = marker_def.keys()
|
|
34
|
+
targets = np.zeros((len(cnt.data), len(class_names)), dtype=np.int32)
|
|
35
|
+
for i_sample, m in cnt.attrs['events']:
|
|
36
|
+
for class_idx, classname in enumerate(class_names):
|
|
37
|
+
if m in marker_def[classname]:
|
|
38
|
+
start_index = i_sample + sample_ival[0]
|
|
39
|
+
stop_index = i_sample + sample_ival[1]
|
|
40
|
+
assert stop_index > start_index
|
|
41
|
+
if start_index >= 0 and stop_index <= len(cnt.coords['time']):
|
|
42
|
+
targets[start_index:stop_index, class_idx] = 1
|
|
43
|
+
elif start_index < 0:
|
|
44
|
+
log.warn("Ignoring trial, start index < 0: {:d}".format(
|
|
45
|
+
start_index))
|
|
46
|
+
else:
|
|
47
|
+
assert stop_index > len(cnt.coords['time'])
|
|
48
|
+
log.warn("Ignoring trial, start index > n_samples:"
|
|
49
|
+
"{:d} > {:d} ".format(
|
|
50
|
+
stop_index, len(cnt.coords['time'])))
|
|
51
|
+
return targets
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def compute_trial_start_stop_samples(y, check_trial_lengths_equal=True,
|
|
55
|
+
input_time_length=None):
|
|
56
|
+
"""Computes trial start and end samples (end is inclusive) from
|
|
57
|
+
one-hot encoded y-matrix.
|
|
58
|
+
Specify input time length to kick out trials that are too short after
|
|
59
|
+
signal start.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
y : 2darray
|
|
64
|
+
check_trial_lengths_equal : bool
|
|
65
|
+
(Default value = True)
|
|
66
|
+
input_time_length : int, optional
|
|
67
|
+
(Default value = None)
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
#TODO: change to start stop
|
|
74
|
+
trial_part = np.sum(y, 1) == 1
|
|
75
|
+
boundaries = np.diff(trial_part.astype(np.int32))
|
|
76
|
+
i_trial_starts = np.flatnonzero(boundaries == 1) + 1
|
|
77
|
+
i_trial_stops = np.flatnonzero(boundaries == -1) + 1
|
|
78
|
+
# it can happen that a trial is only partially there since the
|
|
79
|
+
# cnt signal was split in the middle of a trial
|
|
80
|
+
# for now just remove these
|
|
81
|
+
# use that start marker should always be before or equal to end marker
|
|
82
|
+
if i_trial_starts[0] >= i_trial_stops[0]:
|
|
83
|
+
# cut out first trial which only has end marker
|
|
84
|
+
i_trial_stops = i_trial_stops[1:]
|
|
85
|
+
if i_trial_starts[-1] >= i_trial_stops[-1]:
|
|
86
|
+
# cut out last trial which only has start marker
|
|
87
|
+
i_trial_starts = i_trial_starts[:-1]
|
|
88
|
+
|
|
89
|
+
assert (len(i_trial_starts) == len(i_trial_stops))
|
|
90
|
+
assert (np.all(i_trial_starts < i_trial_stops))
|
|
91
|
+
# possibly remove first trials if they are too early
|
|
92
|
+
if input_time_length is not None:
|
|
93
|
+
while i_trial_starts[0] < (input_time_length - 1):
|
|
94
|
+
i_trial_starts = i_trial_starts[1:]
|
|
95
|
+
i_trial_stops = i_trial_stops[1:]
|
|
96
|
+
if check_trial_lengths_equal:
|
|
97
|
+
# just checking that all trial lengths are equal
|
|
98
|
+
all_trial_lens = np.array(i_trial_stops) - np.array(i_trial_starts)
|
|
99
|
+
assert all(all_trial_lens == all_trial_lens[0]), (
|
|
100
|
+
"All trial lengths should be equal...")
|
|
101
|
+
return i_trial_starts, i_trial_stops
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def segment_dat(cnt, marker_def, ival):
|
|
105
|
+
"""Convert a continuous data object to an epoched one.
|
|
106
|
+
|
|
107
|
+
Given a continuous data object, a definition of classes, and an
|
|
108
|
+
interval, this method looks for markers as defined in ``marker_def``
|
|
109
|
+
and slices the dat according to the time interval given with
|
|
110
|
+
``ival`` along the ``timeaxis``. The returned ``dat`` object stores
|
|
111
|
+
those slices and the class each slice belongs to.
|
|
112
|
+
|
|
113
|
+
Epochs that are too close to the borders and thus too short are
|
|
114
|
+
ignored.
|
|
115
|
+
|
|
116
|
+
If the segmentation does not result in any epochs (i.e. the markers
|
|
117
|
+
in ``marker_def`` could not be found in ``dat``, the resulting
|
|
118
|
+
epo.data will be an empty array.
|
|
119
|
+
|
|
120
|
+
This method is also suitable for **online processing**, please read
|
|
121
|
+
the documentation for the ``newsamples`` parameter and have a look
|
|
122
|
+
at the Examples below.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
dat : Data
|
|
127
|
+
the data object to be segmented
|
|
128
|
+
marker_def : dict
|
|
129
|
+
The keys are class names, the values are lists of markers
|
|
130
|
+
ival : [int, int]
|
|
131
|
+
The interval in milliseconds to cut around the markers. I.e. to
|
|
132
|
+
get the interval starting with the marker plus the remaining
|
|
133
|
+
100ms define the interval like [0, 100]. The start point is
|
|
134
|
+
included, the endpoint is not (like: ``[start, end)``). To get
|
|
135
|
+
200ms before the marker until 100ms after the marker do:
|
|
136
|
+
``[-200, 100]`` Only negative or positive values are possible
|
|
137
|
+
(i.e. ``[-500, -100]``)
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
epo : Data
|
|
142
|
+
a copy of the resulting epoched data.
|
|
143
|
+
|
|
144
|
+
Raises
|
|
145
|
+
------
|
|
146
|
+
AssertionError
|
|
147
|
+
* if ``dat`` has not ``.fs`` or ``.markers`` attribute or if
|
|
148
|
+
``ival[0] > ival[1]``.
|
|
149
|
+
|
|
150
|
+
Examples
|
|
151
|
+
--------
|
|
152
|
+
|
|
153
|
+
Offline Experiment
|
|
154
|
+
|
|
155
|
+
>>> # Define the markers belonging to class 1 and 2
|
|
156
|
+
>>> md = {'class 1': ['S1', 'S2'],
|
|
157
|
+
... 'class 2': ['S3', 'S4']
|
|
158
|
+
... }
|
|
159
|
+
>>> # Epoch the data -500ms and +700ms around the markers defined in
|
|
160
|
+
>>> # md
|
|
161
|
+
>>> epo = segment_dat(cnt, md, [-500, 700])
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
assert 'fs' in cnt.attrs
|
|
166
|
+
assert 'events' in cnt.attrs
|
|
167
|
+
y = create_target_series(cnt, marker_def, ival=ival)
|
|
168
|
+
# Create classes per trial
|
|
169
|
+
# and sample inds per trial from the target series
|
|
170
|
+
starts, stops = compute_trial_start_stop_samples(y)
|
|
171
|
+
classes = [np.argmax(y[i_s]) for i_s in starts]
|
|
172
|
+
sample_inds_per_trial = [list(range(i_start, i_stop))
|
|
173
|
+
for (i_start, i_stop) in zip(starts, stops)]
|
|
174
|
+
|
|
175
|
+
if len(sample_inds_per_trial) == 0:
|
|
176
|
+
assert "No epochs not tested yet"
|
|
177
|
+
data = np.array([])
|
|
178
|
+
else:
|
|
179
|
+
timeaxis = list(cnt.dims).index('time')
|
|
180
|
+
# np.take inserts a new dimension at `axis`...
|
|
181
|
+
data = cnt.data.take(sample_inds_per_trial, axis=timeaxis)
|
|
182
|
+
# we want that new dimension at axis 0 so we have to swap it.
|
|
183
|
+
# before that we have to convert the negative axis indices to
|
|
184
|
+
# their equivalent positive one, otherwise swapaxis will be one
|
|
185
|
+
# off.
|
|
186
|
+
data = data.swapaxes(0, timeaxis)
|
|
187
|
+
time = np.linspace(ival[0], ival[1],
|
|
188
|
+
data.shape[1], # assume timeaxis now on 1
|
|
189
|
+
endpoint=False)
|
|
190
|
+
epo = xr.DataArray(data,
|
|
191
|
+
coords={'trials': classes, 'time': time,
|
|
192
|
+
'channels': cnt.channels,},
|
|
193
|
+
dims=('trials', 'time','channels',))
|
|
194
|
+
epo.attrs = cnt.attrs.copy()
|
|
195
|
+
# remove events part
|
|
196
|
+
epo.attrs.pop('events')
|
|
197
|
+
return epo
|
|
198
|
+
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import errno
|
|
3
|
+
import numpy as np
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def deepcopy_xarr(xarr):
|
|
8
|
+
"""
|
|
9
|
+
Deepcopy for xarray that makes sure coords and attrs
|
|
10
|
+
are properly deepcopied.
|
|
11
|
+
With normal copy method from xarray, when i mutated
|
|
12
|
+
xarr.coords[coord].data it would also mutate in the copy
|
|
13
|
+
and vice versa.
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
xarr: DateArray
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
xcopy: DateArray
|
|
21
|
+
Deep copy of xarr
|
|
22
|
+
"""
|
|
23
|
+
xcopy = xarr.copy(deep=True)
|
|
24
|
+
|
|
25
|
+
for dim in xcopy.coords:
|
|
26
|
+
xcopy.coords[dim].data = np.copy(xcopy.coords[dim].data)
|
|
27
|
+
xcopy.attrs = deepcopy(xcopy.attrs)
|
|
28
|
+
for attr in xcopy.attrs:
|
|
29
|
+
xcopy.attrs[attr] = deepcopy(xcopy.attrs[attr])
|
|
30
|
+
return xcopy
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FuncAndArgs(object):
|
|
34
|
+
"""Container for a function and its arguments.
|
|
35
|
+
Useful in case you want to pass a function and its arguments
|
|
36
|
+
to another function without creating a new class.
|
|
37
|
+
You can call the new instance either with the apply method or
|
|
38
|
+
the ()-call operator:
|
|
39
|
+
|
|
40
|
+
>>> FuncAndArgs(max, 2,3).apply(4)
|
|
41
|
+
4
|
|
42
|
+
>>> FuncAndArgs(max, 2,3)(4)
|
|
43
|
+
4
|
|
44
|
+
>>> FuncAndArgs(sum, [3,4])(8)
|
|
45
|
+
15
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
def __init__(self, func, *args, **kwargs):
|
|
49
|
+
self.func = func
|
|
50
|
+
self.args = args
|
|
51
|
+
self.kwargs = kwargs
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def apply(self, *other_args, **other_kwargs):
|
|
55
|
+
all_args = self.args + other_args
|
|
56
|
+
all_kwargs = self.kwargs.copy()
|
|
57
|
+
all_kwargs.update(other_kwargs)
|
|
58
|
+
return self.func(*all_args, **all_kwargs)
|
|
59
|
+
|
|
60
|
+
def __call__(self, *other_args, **other_kwargs):
|
|
61
|
+
return self.apply(*other_args, **other_kwargs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class GetAttr(object):
|
|
65
|
+
""" Hacky class for yaml to return attr of something.
|
|
66
|
+
Uses new to actually return completely different object... """
|
|
67
|
+
def __new__(cls, obj, attr):
|
|
68
|
+
return getattr(obj, attr)
|
|
69
|
+
|
|
70
|
+
class ApplyFunc(object):
|
|
71
|
+
""" Hacky class to wrap function call in object for yaml loading... """
|
|
72
|
+
def __new__(cls, func,args, kwargs):
|
|
73
|
+
return func(*args, **kwargs)
|
|
74
|
+
|
|
75
|
+
def add_message_to_exception(exc, additional_message):
|
|
76
|
+
# give some more info...
|
|
77
|
+
# see http://www.ianbicking.org/blog/2007/09/re-raising-exceptions.html
|
|
78
|
+
args = exc.args
|
|
79
|
+
if not args:
|
|
80
|
+
arg0 = ''
|
|
81
|
+
else:
|
|
82
|
+
|
|
83
|
+
arg0 = args[0]
|
|
84
|
+
arg0 += additional_message
|
|
85
|
+
exc.args = (arg0, ) + args[1:]
|
|
86
|
+
|
|
87
|
+
def unzip(l):
|
|
88
|
+
return zip(*l)
|
|
89
|
+
|
|
90
|
+
def dict_compare(d1, d2):
|
|
91
|
+
"""From http://stackoverflow.com/a/18860653/1469195"""
|
|
92
|
+
d1_keys = set(d1.keys())
|
|
93
|
+
d2_keys = set(d2.keys())
|
|
94
|
+
intersect_keys = d1_keys.intersection(d2_keys)
|
|
95
|
+
added = d1_keys - d2_keys
|
|
96
|
+
removed = d2_keys - d1_keys
|
|
97
|
+
modified = {o : (d1[o], d2[o]) for o in intersect_keys if d1[o] != d2[o]}
|
|
98
|
+
same = set(o for o in intersect_keys if d1[o] == d2[o])
|
|
99
|
+
return added, removed, modified, same
|
|
100
|
+
|
|
101
|
+
def dict_equal(d1,d2):
|
|
102
|
+
d1_keys = set(d1.keys())
|
|
103
|
+
d2_keys = set(d2.keys())
|
|
104
|
+
intersect_keys = d1_keys.intersection(d2_keys)
|
|
105
|
+
modified = {o : (d1[o], d2[o]) for o in intersect_keys if d1[o] != d2[o]}
|
|
106
|
+
return (intersect_keys == d2_keys and intersect_keys == d1_keys and
|
|
107
|
+
len(modified) == 0)
|
|
108
|
+
|
|
109
|
+
def dict_is_subset(d1,d2):
|
|
110
|
+
added, removed, modified, same = dict_compare(d1, d2)
|
|
111
|
+
return (len(added) == 0 and len(modified) == 0)
|
|
112
|
+
|
|
113
|
+
def merge_dicts(*dict_args):
|
|
114
|
+
'''
|
|
115
|
+
Given any number of dicts, shallow copy and merge into a new dict,
|
|
116
|
+
precedence goes to key value pairs in latter dicts.
|
|
117
|
+
http://stackoverflow.com/a/26853961
|
|
118
|
+
'''
|
|
119
|
+
result = {}
|
|
120
|
+
for dictionary in dict_args:
|
|
121
|
+
result.update(dictionary)
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
def touch_file(path):
|
|
125
|
+
# from http://stackoverflow.com/a/12654798/1469195
|
|
126
|
+
basedir = os.path.dirname(path)
|
|
127
|
+
if not os.path.exists(basedir):
|
|
128
|
+
os.makedirs(basedir)
|
|
129
|
+
with open(path, 'a'):
|
|
130
|
+
os.utime(path, None)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def to_tuple(sequence_or_element, length=None):
|
|
134
|
+
if hasattr(sequence_or_element, '__len__'):
|
|
135
|
+
assert length is None
|
|
136
|
+
return tuple(sequence_or_element)
|
|
137
|
+
else:
|
|
138
|
+
if length is None:
|
|
139
|
+
return (sequence_or_element, )
|
|
140
|
+
else:
|
|
141
|
+
return (sequence_or_element,) * length
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def mkdir_p(path):
|
|
145
|
+
try:
|
|
146
|
+
os.makedirs(path)
|
|
147
|
+
except OSError as exc: # Python >2.5
|
|
148
|
+
if exc.errno == errno.EEXIST and os.path.isdir(path):
|
|
149
|
+
pass
|
|
150
|
+
else:
|
|
151
|
+
raise
|
|
152
|
+
|
|
153
|
+
def select_inverse_inds(arr, inds):
|
|
154
|
+
mask = np.ones(len(arr),dtype=bool)
|
|
155
|
+
mask[inds] = False
|
|
156
|
+
return arr[mask]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from setuptools import setup, find_packages # Always prefer setuptools over distutils
|
|
2
|
+
from codecs import open # To use a consistent encoding
|
|
3
|
+
from os import path
|
|
4
|
+
|
|
5
|
+
here = path.abspath(path.dirname(__file__))
|
|
6
|
+
|
|
7
|
+
# README.md is used as the long description by default
|
|
8
|
+
# Get the long description from the relevant file
|
|
9
|
+
with open(path.join(here, 'README.md'), encoding='utf-8') as f:
|
|
10
|
+
long_description = f.read()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
setup(
|
|
14
|
+
name='Braindecode',
|
|
15
|
+
|
|
16
|
+
# Versions should comply with PEP440. For a discussion on single-sourcing
|
|
17
|
+
# the version across setup.py and the project code, see
|
|
18
|
+
# http://packaging.python.org/en/latest/tutorial.html#version
|
|
19
|
+
version='0.1.0',
|
|
20
|
+
|
|
21
|
+
description='A deep learning for raw time-domain EEG decoding toolbox.',
|
|
22
|
+
long_description=long_description, #this is the
|
|
23
|
+
|
|
24
|
+
# The project's main homepage.
|
|
25
|
+
url='https://github.com/robintibor/braindecode',
|
|
26
|
+
|
|
27
|
+
# Author details
|
|
28
|
+
author='Robin Tibor Schirrmeister',
|
|
29
|
+
author_email='robintibor@gmail.com',
|
|
30
|
+
|
|
31
|
+
# Choose your license
|
|
32
|
+
license='BSD 3-Clause',
|
|
33
|
+
|
|
34
|
+
install_requires=['numpy','mne'],
|
|
35
|
+
#tests_require = [...]
|
|
36
|
+
|
|
37
|
+
# See https://PyPI.python.org/PyPI?%3Aaction=list_classifiers
|
|
38
|
+
classifiers=[
|
|
39
|
+
'Development Status :: 3 - Alpha',
|
|
40
|
+
|
|
41
|
+
# Indicate who your project is intended for
|
|
42
|
+
"Intended Audience :: Developers",
|
|
43
|
+
"Intended Audience :: Science/Research",
|
|
44
|
+
'Topic :: Software Development :: Build Tools',
|
|
45
|
+
|
|
46
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
47
|
+
|
|
48
|
+
# Pick your license as you wish (should match "license" above)
|
|
49
|
+
'License :: OSI Approved :: BSD License',
|
|
50
|
+
|
|
51
|
+
# Specify the Python versions you support here. In particular, ensure
|
|
52
|
+
# that you indicate whether you support Python 2, Python 3 or both.
|
|
53
|
+
'Programming Language :: Python :: 2.7',
|
|
54
|
+
'Programming Language :: Python :: 3.5',
|
|
55
|
+
],
|
|
56
|
+
|
|
57
|
+
# What does your project relate to?
|
|
58
|
+
keywords='eeg deep-learning brain-state-decoding',
|
|
59
|
+
|
|
60
|
+
packages=['braindecode'],
|
|
61
|
+
|
|
62
|
+
)
|