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