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.
Files changed (69) hide show
  1. Braindecode-0.5.1.dist-info/LICENSE.txt +31 -0
  2. Braindecode-0.5.1.dist-info/METADATA +133 -0
  3. Braindecode-0.5.1.dist-info/RECORD +69 -0
  4. Braindecode-0.5.1.dist-info/WHEEL +5 -0
  5. Braindecode-0.5.1.dist-info/top_level.txt +2 -0
  6. braindecode/__init__.py +4 -0
  7. braindecode/classifier.py +258 -0
  8. braindecode/datasets/__init__.py +7 -0
  9. braindecode/datasets/base.py +293 -0
  10. braindecode/datasets/bbci.py +679 -0
  11. braindecode/datasets/moabb.py +110 -0
  12. braindecode/datasets/sleep_physionet.py +103 -0
  13. braindecode/datasets/tuh.py +230 -0
  14. braindecode/datautil/__init__.py +11 -0
  15. braindecode/datautil/mne.py +136 -0
  16. braindecode/datautil/preprocess.py +364 -0
  17. braindecode/datautil/serialization.py +111 -0
  18. braindecode/datautil/util.py +36 -0
  19. braindecode/datautil/windowers.py +435 -0
  20. braindecode/datautil/xy.py +77 -0
  21. braindecode/models/__init__.py +12 -0
  22. braindecode/models/deep4.py +252 -0
  23. braindecode/models/eegnet.py +339 -0
  24. braindecode/models/eegresnet.py +232 -0
  25. braindecode/models/functions.py +43 -0
  26. braindecode/models/hybrid.py +103 -0
  27. braindecode/models/modules.py +150 -0
  28. braindecode/models/shallow_fbcsp.py +158 -0
  29. braindecode/models/sleep_stager_chambon_2018.py +113 -0
  30. braindecode/models/tcn.py +143 -0
  31. braindecode/models/util.py +66 -0
  32. braindecode/regressor.py +240 -0
  33. braindecode/samplers/__init__.py +5 -0
  34. braindecode/samplers/base.py +78 -0
  35. braindecode/samplers/ssl.py +118 -0
  36. braindecode/training/__init__.py +8 -0
  37. braindecode/training/callbacks.py +21 -0
  38. braindecode/training/losses.py +17 -0
  39. braindecode/training/scoring.py +296 -0
  40. braindecode/util.py +362 -0
  41. braindecode/version.py +1 -0
  42. braindecode/visualization/__init__.py +5 -0
  43. braindecode/visualization/gradients.py +53 -0
  44. test/__init__.py +0 -0
  45. test/acceptance_tests/__init__.py +0 -0
  46. test/acceptance_tests/test_cropped_decoding.py +171 -0
  47. test/acceptance_tests/test_eeg_classifier.py +255 -0
  48. test/acceptance_tests/test_model_architectures.py +54 -0
  49. test/acceptance_tests/test_trialwise_decoding.py +168 -0
  50. test/unit_tests/__init__.py +0 -0
  51. test/unit_tests/datasets/__init__.py +0 -0
  52. test/unit_tests/datasets/test_dataset.py +227 -0
  53. test/unit_tests/datasets/test_mne.py +145 -0
  54. test/unit_tests/datasets/test_sleep_physionet.py +23 -0
  55. test/unit_tests/datasets/test_tuh.py +97 -0
  56. test/unit_tests/datasets/test_xy.py +31 -0
  57. test/unit_tests/datautil/__init__.py +0 -0
  58. test/unit_tests/datautil/test_preprocess.py +270 -0
  59. test/unit_tests/datautil/test_serialization.py +127 -0
  60. test/unit_tests/datautil/test_windowers.py +519 -0
  61. test/unit_tests/models/__init__.py +0 -0
  62. test/unit_tests/models/test_models.py +136 -0
  63. test/unit_tests/models/test_util.py +22 -0
  64. test/unit_tests/test_eeg_classifier.py +73 -0
  65. test/unit_tests/test_eeg_regressor.py +72 -0
  66. test/unit_tests/test_scoring.py +282 -0
  67. test/unit_tests/test_util.py +30 -0
  68. test/unit_tests/visualization/__init__.py +0 -0
  69. test/unit_tests/visualization/test_gradients.py +38 -0
@@ -0,0 +1,255 @@
1
+ # Authors: Maciej Sliwowski
2
+ # Robin Tibor Schirrmeister
3
+ # Lukas Gemein
4
+ #
5
+ # License: BSD-3
6
+
7
+ import mne
8
+ import numpy as np
9
+ from mne.io import concatenate_raws
10
+ from skorch.helper import predefined_split
11
+ from torch import optim
12
+ from torch.nn.functional import nll_loss
13
+
14
+ from braindecode.classifier import EEGClassifier
15
+ from braindecode.datautil.xy import create_from_X_y
16
+ from braindecode.training.losses import CroppedLoss
17
+ from braindecode.models import ShallowFBCSPNet
18
+ from braindecode.models.util import to_dense_prediction_model
19
+ from braindecode.training.scoring import CroppedTrialEpochScoring
20
+ from braindecode.util import set_random_seeds, np_to_var
21
+
22
+
23
+ def assert_deep_allclose(expected, actual, *args, **kwargs):
24
+ """
25
+ Assert that two complex structures have almost equal contents.
26
+
27
+ Compares lists, dicts and tuples recursively. Checks numeric values
28
+ using test_case's :py:meth:`unittest.TestCase.assertAlmostEqual` and
29
+ checks all other values with :py:meth:`unittest.TestCase.assertEqual`.
30
+ Accepts additional positional and keyword arguments and pass those
31
+ intact to assertAlmostEqual() (that's how you specify comparison
32
+ precision).
33
+ """
34
+ is_root = not "__trace" in kwargs
35
+ trace = kwargs.pop("__trace", "ROOT")
36
+ try:
37
+ if isinstance(expected, (int, float, complex)):
38
+ np.testing.assert_allclose(expected, actual, *args, **kwargs)
39
+ elif isinstance(expected, (list, tuple, np.ndarray)):
40
+ assert len(expected) == len(actual)
41
+ for index in range(len(expected)):
42
+ v1, v2 = expected[index], actual[index]
43
+ assert_deep_allclose(
44
+ v1, v2, __trace=repr(index), *args, **kwargs
45
+ )
46
+ elif isinstance(expected, dict):
47
+ assert set(expected) == set(actual)
48
+ for key in expected:
49
+ assert_deep_allclose(
50
+ expected[key],
51
+ actual[key],
52
+ __trace=repr(key),
53
+ *args,
54
+ **kwargs
55
+ )
56
+ else:
57
+ assert expected == actual
58
+ except AssertionError as exc:
59
+ exc.__dict__.setdefault("traces", []).append(trace)
60
+ msg = (
61
+ exc.message
62
+ if hasattr(exc, "message")
63
+ else exc.args[0]
64
+ if exc.args
65
+ else ""
66
+ )
67
+ if is_root:
68
+ trace = " -> ".join(reversed(exc.traces))
69
+ exc = AssertionError("%s\nTRACE: %s" % (msg, trace))
70
+ raise exc
71
+
72
+
73
+ def test_eeg_classifier():
74
+ # 5,6,7,10,13,14 are codes for executed and imagined hands/feet
75
+ subject_id = 1
76
+ event_codes = [5, 6, 9, 10, 13, 14]
77
+
78
+ # This will download the files if you don't have them yet,
79
+ # and then return the paths to the files.
80
+ physionet_paths = mne.datasets.eegbci.load_data(
81
+ subject_id, event_codes, update_path=False
82
+ )
83
+
84
+ # Load each of the files
85
+ parts = [
86
+ mne.io.read_raw_edf(
87
+ path, preload=True, stim_channel="auto", verbose="WARNING"
88
+ )
89
+ for path in physionet_paths
90
+ ]
91
+
92
+ # Concatenate them
93
+ raw = concatenate_raws(parts)
94
+
95
+ # Find the events in this dataset
96
+ events, _ = mne.events_from_annotations(raw)
97
+
98
+ # Use only EEG channels
99
+ eeg_channel_inds = mne.pick_types(
100
+ raw.info, meg=False, eeg=True, stim=False, eog=False, exclude="bads"
101
+ )
102
+
103
+ # Extract trials, only using EEG channels
104
+ epoched = mne.Epochs(
105
+ raw,
106
+ events,
107
+ dict(hands=2, feet=3),
108
+ tmin=1,
109
+ tmax=4.1,
110
+ proj=False,
111
+ picks=eeg_channel_inds,
112
+ baseline=None,
113
+ preload=True,
114
+ )
115
+
116
+ # Convert data from volt to millivolt
117
+ # Pytorch expects float32 for input and int64 for labels.
118
+ X = (epoched.get_data() * 1e6).astype(np.float32)
119
+ y = (epoched.events[:, 2] - 2).astype(np.int64) # 2,3 -> 0,1
120
+
121
+ # Set if you want to use GPU
122
+ # You can also use torch.cuda.is_available() to determine if cuda is available on your machine.
123
+ cuda = False
124
+ set_random_seeds(seed=20170629, cuda=cuda)
125
+
126
+ # This will determine how many crops are processed in parallel
127
+ input_window_samples = 450
128
+ n_classes = 2
129
+ in_chans = X.shape[1]
130
+ # final_conv_length determines the size of the receptive field of the ConvNet
131
+ model = ShallowFBCSPNet(
132
+ in_chans=in_chans,
133
+ n_classes=n_classes,
134
+ input_window_samples=input_window_samples,
135
+ final_conv_length=12,
136
+ )
137
+ to_dense_prediction_model(model)
138
+
139
+ if cuda:
140
+ model.cuda()
141
+
142
+ # determine output size
143
+ test_input = np_to_var(
144
+ np.ones((2, in_chans, input_window_samples, 1), dtype=np.float32)
145
+ )
146
+ if cuda:
147
+ test_input = test_input.cuda()
148
+ out = model(test_input)
149
+ n_preds_per_input = out.cpu().data.numpy().shape[2]
150
+
151
+ train_set = create_from_X_y(X[:48], y[:48],
152
+ drop_last_window=False,
153
+ window_size_samples=input_window_samples,
154
+ window_stride_samples=n_preds_per_input)
155
+
156
+ valid_set = create_from_X_y(X[48:60], y[48:60],
157
+ drop_last_window=False,
158
+ window_size_samples=input_window_samples,
159
+ window_stride_samples=n_preds_per_input)
160
+
161
+ cropped_cb_train = CroppedTrialEpochScoring(
162
+ "accuracy",
163
+ name="train_trial_accuracy",
164
+ lower_is_better=False,
165
+ on_train=True,
166
+ )
167
+
168
+ cropped_cb_valid = CroppedTrialEpochScoring(
169
+ "accuracy",
170
+ on_train=False,
171
+ name="valid_trial_accuracy",
172
+ lower_is_better=False,
173
+ )
174
+
175
+ clf = EEGClassifier(
176
+ model,
177
+ cropped=True,
178
+ criterion=CroppedLoss,
179
+ criterion__loss_function=nll_loss,
180
+ optimizer=optim.Adam,
181
+ train_split=predefined_split(valid_set),
182
+ batch_size=32,
183
+ callbacks=[
184
+ ("train_trial_accuracy", cropped_cb_train),
185
+ ("valid_trial_accuracy", cropped_cb_valid),
186
+ ],
187
+ )
188
+
189
+ clf.fit(train_set, y=None, epochs=4)
190
+
191
+ expected = [{'batches': [{'train_batch_size': 32, 'train_loss': 1.6639312505722046},
192
+ {'train_batch_size': 32, 'train_loss': 2.6161606311798096},
193
+ {'train_batch_size': 32, 'train_loss': 1.627132773399353},
194
+ {'valid_batch_size': 24, 'valid_loss': 0.9677614569664001}],
195
+ 'epoch': 1,
196
+ 'train_batch_count': 3,
197
+ 'train_loss': 1.9690748850504558,
198
+ 'train_loss_best': True,
199
+ 'train_trial_accuracy': 0.4791666666666667,
200
+ 'train_trial_accuracy_best': True,
201
+ 'valid_batch_count': 1,
202
+ 'valid_loss': 0.9677614569664001,
203
+ 'valid_loss_best': True,
204
+ 'valid_trial_accuracy': 0.5,
205
+ 'valid_trial_accuracy_best': True},
206
+ {'batches': [{'train_batch_size': 32, 'train_loss': 1.3829222917556763},
207
+ {'train_batch_size': 32, 'train_loss': 1.3123714923858643},
208
+ {'train_batch_size': 32, 'train_loss': 1.0109959840774536},
209
+ {'valid_batch_size': 24, 'valid_loss': 1.9435862302780151}],
210
+ 'epoch': 2,
211
+ 'train_batch_count': 3,
212
+ 'train_loss': 1.2354299227396648,
213
+ 'train_loss_best': True,
214
+ 'train_trial_accuracy': 0.5,
215
+ 'train_trial_accuracy_best': True,
216
+ 'valid_batch_count': 1,
217
+ 'valid_loss': 1.9435862302780151,
218
+ 'valid_loss_best': False,
219
+ 'valid_trial_accuracy': 0.5,
220
+ 'valid_trial_accuracy_best': False},
221
+ {'batches': [{'train_batch_size': 32, 'train_loss': 1.172208547592163},
222
+ {'train_batch_size': 32, 'train_loss': 0.8899562954902649},
223
+ {'train_batch_size': 32, 'train_loss': 1.0232216119766235},
224
+ {'valid_batch_size': 24, 'valid_loss': 0.9585554599761963}],
225
+ 'epoch': 3,
226
+ 'train_batch_count': 3,
227
+ 'train_loss': 1.0284621516863506,
228
+ 'train_loss_best': True,
229
+ 'train_trial_accuracy': 0.5,
230
+ 'train_trial_accuracy_best': False,
231
+ 'valid_batch_count': 1,
232
+ 'valid_loss': 0.9585554599761963,
233
+ 'valid_loss_best': True,
234
+ 'valid_trial_accuracy': 0.5,
235
+ 'valid_trial_accuracy_best': False},
236
+ {'batches': [{'train_batch_size': 32, 'train_loss': 0.9693693518638611},
237
+ {'train_batch_size': 32, 'train_loss': 0.900641918182373},
238
+ {'train_batch_size': 32, 'train_loss': 0.8839665651321411},
239
+ {'valid_batch_size': 24, 'valid_loss': 0.873468816280365}],
240
+ 'epoch': 4,
241
+ 'train_batch_count': 3,
242
+ 'train_loss': 0.9179926117261251,
243
+ 'train_loss_best': True,
244
+ 'train_trial_accuracy': 0.625,
245
+ 'train_trial_accuracy_best': True,
246
+ 'valid_batch_count': 1,
247
+ 'valid_loss': 0.873468816280365,
248
+ 'valid_loss_best': True,
249
+ 'valid_trial_accuracy': 0.4166666666666667,
250
+ 'valid_trial_accuracy_best': False}]
251
+
252
+ history_without_dur = [
253
+ {k: v for k, v in h.items() if k != "dur"} for h in clf.history
254
+ ]
255
+ assert_deep_allclose(expected, history_without_dur, atol=1e-3, rtol=1e-3)
@@ -0,0 +1,54 @@
1
+ # Authors: Lukas Gemein <l.gemein@gmail.com>
2
+ #
3
+ #
4
+ # License: BSD-3
5
+
6
+ import torch
7
+ import numpy as np
8
+
9
+ from braindecode.models.tcn import TCN
10
+ from braindecode.util import set_random_seeds
11
+
12
+
13
+ def test_tcn():
14
+ set_random_seeds(0, False)
15
+ tcn = TCN(
16
+ n_in_chans=21,
17
+ n_outputs=2,
18
+ n_filters=55,
19
+ n_blocks=5,
20
+ kernel_size=16,
21
+ drop_prob=0.05270154233150525,
22
+ add_log_softmax=True
23
+ )
24
+ # braindecode models are always in eval mode after initialization
25
+ # original model implementation was not
26
+ tcn.train()
27
+ x = torch.rand(1, 21, 1000, 1)
28
+ out = tcn(x)
29
+ # this is the output of the original model implementation using the same
30
+ # initialization arguments as above
31
+ expected = np.array(
32
+ [[[-0.5504, -0.5304, -0.6023, -0.5231, -0.5387, -0.5522, -0.5323,
33
+ -0.5540, -0.5297, -0.5333, -0.5743, -0.5330, -0.5117, -0.5051,
34
+ -0.5523, -0.5507, -0.5724, -0.5380, -0.5697, -0.4871, -0.5400,
35
+ -0.4986, -0.5502, -0.5524, -0.5263, -0.5440, -0.5464, -0.5005,
36
+ -0.5404, -0.5098, -0.5197, -0.5578, -0.5419, -0.5601, -0.5031,
37
+ -0.5616, -0.5205, -0.5378, -0.5472, -0.4897, -0.5216, -0.5560,
38
+ -0.5480, -0.5488, -0.5258, -0.5637, -0.5318, -0.5134, -0.5460,
39
+ -0.5294, -0.5513, -0.5310, -0.5307, -0.5326, -0.5270, -0.5156,
40
+ -0.5569, -0.5416, -0.5279, -0.5553, -0.5589, -0.5166, -0.5108,
41
+ -0.5076, -0.5279, -0.5208, -0.5367, -0.5557, -0.5690, -0.5494],
42
+ [-0.8597, -0.8877, -0.7931, -0.8982, -0.8758, -0.8573, -0.8849,
43
+ -0.8549, -0.8887, -0.8834, -0.8280, -0.8839, -0.9150, -0.9250,
44
+ -0.8572, -0.8593, -0.8305, -0.8769, -0.8340, -0.9530, -0.8741,
45
+ -0.9350, -0.8600, -0.8570, -0.8935, -0.8685, -0.8652, -0.9319,
46
+ -0.8735, -0.9179, -0.9031, -0.8497, -0.8714, -0.8466, -0.9280,
47
+ -0.8447, -0.9019, -0.8771, -0.8640, -0.9489, -0.9003, -0.8521,
48
+ -0.8630, -0.8619, -0.8942, -0.8419, -0.8856, -0.9124, -0.8658,
49
+ -0.8890, -0.8585, -0.8867, -0.8872, -0.8845, -0.8924, -0.9092,
50
+ -0.8509, -0.8718, -0.8912, -0.8531, -0.8482, -0.9077, -0.9163,
51
+ -0.9212, -0.8912, -0.9016, -0.8787, -0.8525, -0.8349,
52
+ -0.8611]]])
53
+ np.testing.assert_allclose(
54
+ out.detach().numpy(), expected, rtol=1e-3, atol=1e-3)
@@ -0,0 +1,168 @@
1
+ # Authors: Maciej Sliwowski
2
+ # Robin Tibor Schirrmeister
3
+ #
4
+ # License: BSD-3
5
+
6
+ import mne
7
+ import numpy as np
8
+ import torch
9
+ from mne.io import concatenate_raws
10
+ from skorch.helper import predefined_split
11
+ from torch.utils.data import Dataset, Subset
12
+
13
+ from braindecode.classifier import EEGClassifier
14
+ from braindecode.models import ShallowFBCSPNet
15
+ from braindecode.util import set_random_seeds
16
+
17
+
18
+ class EpochsDataset(Dataset):
19
+ def __init__(self, windows):
20
+ self.windows = windows
21
+ self.y = np.array(self.windows.events[:, -1])
22
+ self.y = self.y - self.y.min()
23
+
24
+ def __getitem__(self, index):
25
+ X = self.windows.get_data(item=index)[0].astype('float32')[:, :, None]
26
+ y = self.y[index]
27
+ return X, y
28
+
29
+ def __len__(self):
30
+ return len(self.windows.events)
31
+
32
+
33
+ def test_trialwise_decoding():
34
+ # 5,6,7,10,13,14 are codes for executed and imagined hands/feet
35
+ subject_id = 1
36
+ event_codes = [5, 6, 9, 10, 13, 14]
37
+
38
+ # This will download the files if you don't have them yet,
39
+ # and then return the paths to the files.
40
+ physionet_paths = mne.datasets.eegbci.load_data(
41
+ subject_id, event_codes, update_path=False
42
+ )
43
+
44
+ # Load each of the files
45
+ parts = [
46
+ mne.io.read_raw_edf(
47
+ path, preload=True, stim_channel="auto", verbose="WARNING"
48
+ )
49
+ for path in physionet_paths
50
+ ]
51
+
52
+ # Concatenate them
53
+ raw = concatenate_raws(parts)
54
+ raw.apply_function(lambda x: x * 1000000)
55
+
56
+ # Find the events in this dataset
57
+ events, _ = mne.events_from_annotations(raw)
58
+ # Use only EEG channels
59
+ eeg_channel_inds = mne.pick_types(
60
+ raw.info, meg=False, eeg=True, stim=False, eog=False, exclude="bads"
61
+ )
62
+
63
+ # Extract trials, only using EEG channels
64
+ epoched = mne.Epochs(
65
+ raw,
66
+ events,
67
+ dict(hands=2, feet=3),
68
+ tmin=1,
69
+ tmax=4.1,
70
+ proj=False,
71
+ picks=eeg_channel_inds,
72
+ baseline=None,
73
+ preload=True,
74
+ )
75
+
76
+ ds = EpochsDataset(epoched)
77
+
78
+ train_set = Subset(ds, np.arange(60))
79
+ valid_set = Subset(ds, np.arange(60, len(ds)))
80
+
81
+ train_valid_split = predefined_split(valid_set)
82
+
83
+ cuda = False
84
+ if cuda:
85
+ device = 'cuda'
86
+ else:
87
+ device = 'cpu'
88
+ set_random_seeds(seed=20170629, cuda=cuda)
89
+ n_classes = 2
90
+ in_chans = train_set[0][0].shape[0]
91
+ input_window_samples = train_set[0][0].shape[1]
92
+ model = ShallowFBCSPNet(
93
+ in_chans=in_chans,
94
+ n_classes=n_classes,
95
+ input_window_samples=input_window_samples,
96
+ final_conv_length="auto",
97
+ )
98
+ if cuda:
99
+ model.cuda()
100
+
101
+ clf = EEGClassifier(
102
+ model,
103
+ cropped=False,
104
+ criterion=torch.nn.NLLLoss,
105
+ optimizer=torch.optim.Adam,
106
+ train_split=train_valid_split,
107
+ optimizer__lr=0.001,
108
+ batch_size=30,
109
+ callbacks=["accuracy"],
110
+ device=device,
111
+ )
112
+ clf.fit(train_set, y=None, epochs=6)
113
+
114
+ np.testing.assert_allclose(
115
+ clf.history[:, 'train_loss'],
116
+ np.array([
117
+ 1.623811,
118
+ 1.141197,
119
+ 0.730361,
120
+ 0.5994,
121
+ 0.738884,
122
+ 0.419241
123
+ ]),
124
+ rtol=1e-4,
125
+ atol=1e-5,
126
+ )
127
+
128
+ np.testing.assert_allclose(
129
+ clf.history[:, 'valid_loss'],
130
+ np.array([
131
+ 1.958356261253357,
132
+ 0.8494758009910583,
133
+ 0.9321595430374146,
134
+ 0.8126973509788513,
135
+ 0.7426613569259644,
136
+ 0.7547585368156433
137
+ ]),
138
+ rtol=1e-4,
139
+ atol=1e-5,
140
+ )
141
+
142
+ np.testing.assert_allclose(
143
+ clf.history[:, 'train_accuracy'],
144
+ np.array([
145
+ 0.5,
146
+ 0.8166666666666667,
147
+ 0.7,
148
+ 0.8,
149
+ 0.8666666666666667,
150
+ 0.9
151
+ ]),
152
+ rtol=1e-4,
153
+ atol=1e-5,
154
+ )
155
+
156
+ np.testing.assert_allclose(
157
+ clf.history[:, 'valid_accuracy'],
158
+ np.array([
159
+ 0.4666666666666667,
160
+ 0.6,
161
+ 0.5,
162
+ 0.5666666666666667,
163
+ 0.6,
164
+ 0.6,
165
+ ]),
166
+ rtol=1e-4,
167
+ atol=1e-5,
168
+ )
File without changes
File without changes