eegdash 0.0.9__py3-none-any.whl → 0.2.0__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.
Potentially problematic release.
This version of eegdash might be problematic. Click here for more details.
- eegdash/__init__.py +8 -1
- eegdash/api.py +690 -0
- eegdash/data_config.py +33 -27
- eegdash/data_utils.py +365 -222
- eegdash/dataset.py +60 -0
- eegdash/features/__init__.py +46 -18
- eegdash/features/datasets.py +62 -23
- eegdash/features/decorators.py +14 -6
- eegdash/features/extractors.py +22 -22
- eegdash/features/feature_bank/__init__.py +3 -3
- eegdash/features/feature_bank/complexity.py +6 -3
- eegdash/features/feature_bank/connectivity.py +16 -56
- eegdash/features/feature_bank/csp.py +3 -4
- eegdash/features/feature_bank/dimensionality.py +8 -5
- eegdash/features/feature_bank/signal.py +30 -4
- eegdash/features/feature_bank/spectral.py +10 -28
- eegdash/features/feature_bank/utils.py +48 -0
- eegdash/features/inspect.py +48 -0
- eegdash/features/serialization.py +4 -5
- eegdash/features/utils.py +9 -7
- eegdash/preprocessing.py +65 -0
- eegdash/utils.py +11 -0
- {eegdash-0.0.9.dist-info → eegdash-0.2.0.dist-info}/METADATA +67 -20
- eegdash-0.2.0.dist-info/RECORD +27 -0
- {eegdash-0.0.9.dist-info → eegdash-0.2.0.dist-info}/WHEEL +1 -1
- {eegdash-0.0.9.dist-info → eegdash-0.2.0.dist-info}/licenses/LICENSE +1 -0
- eegdash/main.py +0 -359
- eegdash-0.0.9.dist-info/RECORD +0 -22
- {eegdash-0.0.9.dist-info → eegdash-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import numpy as np
|
|
2
1
|
import numba as nb
|
|
2
|
+
import numpy as np
|
|
3
3
|
from scipy import special
|
|
4
4
|
|
|
5
|
-
from ..decorators import univariate_feature
|
|
6
|
-
from .signal import signal_zero_crossings
|
|
7
|
-
|
|
5
|
+
from ..decorators import FeaturePredecessor, univariate_feature
|
|
6
|
+
from .signal import SIGNAL_PREDECESSORS, signal_zero_crossings
|
|
8
7
|
|
|
9
8
|
__all__ = [
|
|
10
9
|
"dimensionality_higuchi_fractal_dim",
|
|
@@ -15,6 +14,7 @@ __all__ = [
|
|
|
15
14
|
]
|
|
16
15
|
|
|
17
16
|
|
|
17
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
18
18
|
@univariate_feature
|
|
19
19
|
@nb.njit(cache=True, fastmath=True)
|
|
20
20
|
def dimensionality_higuchi_fractal_dim(x, k_max=10, eps=1e-7):
|
|
@@ -33,6 +33,7 @@ def dimensionality_higuchi_fractal_dim(x, k_max=10, eps=1e-7):
|
|
|
33
33
|
return hfd
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
36
37
|
@univariate_feature
|
|
37
38
|
def dimensionality_petrosian_fractal_dim(x):
|
|
38
39
|
nd = signal_zero_crossings(np.diff(x, axis=-1))
|
|
@@ -40,6 +41,7 @@ def dimensionality_petrosian_fractal_dim(x):
|
|
|
40
41
|
return log_n / (np.log(nd) + log_n)
|
|
41
42
|
|
|
42
43
|
|
|
44
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
43
45
|
@univariate_feature
|
|
44
46
|
def dimensionality_katz_fractal_dim(x):
|
|
45
47
|
dists = np.abs(np.diff(x, axis=-1))
|
|
@@ -50,7 +52,6 @@ def dimensionality_katz_fractal_dim(x):
|
|
|
50
52
|
return log_n / (np.log(d / L) + log_n)
|
|
51
53
|
|
|
52
54
|
|
|
53
|
-
@univariate_feature
|
|
54
55
|
@nb.njit(cache=True, fastmath=True)
|
|
55
56
|
def _hurst_exp(x, ns, a, gamma_ratios, log_n):
|
|
56
57
|
h = np.empty(x.shape[:-1])
|
|
@@ -76,6 +77,7 @@ def _hurst_exp(x, ns, a, gamma_ratios, log_n):
|
|
|
76
77
|
return h
|
|
77
78
|
|
|
78
79
|
|
|
80
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
79
81
|
@univariate_feature
|
|
80
82
|
def dimensionality_hurst_exp(x):
|
|
81
83
|
ns = np.unique(np.power(2, np.arange(2, np.log2(x.shape[-1]) - 1)).astype(int))
|
|
@@ -89,6 +91,7 @@ def dimensionality_hurst_exp(x):
|
|
|
89
91
|
return _hurst_exp(x, ns, a, gamma_ratios, log_n)
|
|
90
92
|
|
|
91
93
|
|
|
94
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
92
95
|
@univariate_feature
|
|
93
96
|
@nb.njit(cache=True, fastmath=True)
|
|
94
97
|
def dimensionality_detrended_fluctuation_analysis(x):
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import numbers
|
|
2
|
-
import numpy as np
|
|
3
|
-
from scipy import stats
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy import signal, stats
|
|
6
5
|
|
|
6
|
+
from ..decorators import FeaturePredecessor, univariate_feature
|
|
7
|
+
from ..extractors import FeatureExtractor
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
10
|
+
"HilbertFeatureExtractor",
|
|
9
11
|
"signal_mean",
|
|
10
12
|
"signal_variance",
|
|
11
13
|
"signal_skewness",
|
|
@@ -23,51 +25,70 @@ __all__ = [
|
|
|
23
25
|
]
|
|
24
26
|
|
|
25
27
|
|
|
28
|
+
@FeaturePredecessor(FeatureExtractor)
|
|
29
|
+
class HilbertFeatureExtractor(FeatureExtractor):
|
|
30
|
+
def preprocess(self, x):
|
|
31
|
+
return np.abs(signal.hilbert(x - x.mean(axis=-1, keepdims=True), axis=-1))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
SIGNAL_PREDECESSORS = [FeatureExtractor, HilbertFeatureExtractor]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
26
38
|
@univariate_feature
|
|
27
39
|
def signal_mean(x):
|
|
28
40
|
return x.mean(axis=-1)
|
|
29
41
|
|
|
30
42
|
|
|
43
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
31
44
|
@univariate_feature
|
|
32
45
|
def signal_variance(x, **kwargs):
|
|
33
46
|
return x.var(axis=-1, **kwargs)
|
|
34
47
|
|
|
35
48
|
|
|
49
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
36
50
|
@univariate_feature
|
|
37
51
|
def signal_std(x, **kwargs):
|
|
38
52
|
return x.std(axis=-1, **kwargs)
|
|
39
53
|
|
|
40
54
|
|
|
55
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
41
56
|
@univariate_feature
|
|
42
57
|
def signal_skewness(x, **kwargs):
|
|
43
58
|
return stats.skew(x, axis=x.ndim - 1, **kwargs)
|
|
44
59
|
|
|
45
60
|
|
|
61
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
46
62
|
@univariate_feature
|
|
47
63
|
def signal_kurtosis(x, **kwargs):
|
|
48
64
|
return stats.kurtosis(x, axis=x.ndim - 1, **kwargs)
|
|
49
65
|
|
|
50
66
|
|
|
67
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
51
68
|
@univariate_feature
|
|
52
69
|
def signal_root_mean_square(x):
|
|
53
70
|
return np.sqrt(np.power(x, 2).mean(axis=-1))
|
|
54
71
|
|
|
55
72
|
|
|
73
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
56
74
|
@univariate_feature
|
|
57
75
|
def signal_peak_to_peak(x, **kwargs):
|
|
58
76
|
return np.ptp(x, axis=-1, **kwargs)
|
|
59
77
|
|
|
60
78
|
|
|
79
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
61
80
|
@univariate_feature
|
|
62
81
|
def signal_quantile(x, q: numbers.Number = 0.5, **kwargs):
|
|
63
82
|
return np.quantile(x, q=q, axis=-1, **kwargs)
|
|
64
83
|
|
|
65
84
|
|
|
85
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
66
86
|
@univariate_feature
|
|
67
87
|
def signal_line_length(x):
|
|
68
88
|
return np.abs(np.diff(x, axis=-1)).mean(axis=-1)
|
|
69
89
|
|
|
70
90
|
|
|
91
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
71
92
|
@univariate_feature
|
|
72
93
|
def signal_zero_crossings(x, threshold=1e-15):
|
|
73
94
|
zero_ind = np.logical_and(x > -threshold, x < threshold)
|
|
@@ -78,16 +99,21 @@ def signal_zero_crossings(x, threshold=1e-15):
|
|
|
78
99
|
return zero_cross
|
|
79
100
|
|
|
80
101
|
|
|
102
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
81
103
|
@univariate_feature
|
|
82
104
|
def signal_hjorth_mobility(x):
|
|
83
105
|
return np.diff(x, axis=-1).std(axis=-1) / x.std(axis=-1)
|
|
84
106
|
|
|
85
107
|
|
|
108
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
86
109
|
@univariate_feature
|
|
87
110
|
def signal_hjorth_complexity(x):
|
|
88
|
-
return np.diff(x, 2, axis=-1).std(axis=-1)
|
|
111
|
+
return (np.diff(x, 2, axis=-1).std(axis=-1) * x.std(axis=-1)) / np.diff(
|
|
112
|
+
x, axis=-1
|
|
113
|
+
).var(axis=-1)
|
|
89
114
|
|
|
90
115
|
|
|
116
|
+
@FeaturePredecessor(*SIGNAL_PREDECESSORS)
|
|
91
117
|
@univariate_feature
|
|
92
118
|
def signal_decorrelation_time(x, fs=1):
|
|
93
119
|
f = np.fft.fft(x - x.mean(axis=-1, keepdims=True), axis=-1)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import numpy as np
|
|
2
1
|
import numba as nb
|
|
2
|
+
import numpy as np
|
|
3
3
|
from scipy.signal import welch
|
|
4
4
|
|
|
5
|
-
from ..extractors import FeatureExtractor
|
|
6
5
|
from ..decorators import FeaturePredecessor, univariate_feature
|
|
7
|
-
|
|
6
|
+
from ..extractors import FeatureExtractor
|
|
7
|
+
from . import utils
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
10
|
"SpectralFeatureExtractor",
|
|
@@ -26,14 +26,13 @@ class SpectralFeatureExtractor(FeatureExtractor):
|
|
|
26
26
|
def preprocess(self, x, **kwargs):
|
|
27
27
|
f_min = kwargs.pop("f_min") if "f_min" in kwargs else None
|
|
28
28
|
f_max = kwargs.pop("f_max") if "f_max" in kwargs else None
|
|
29
|
+
assert "fs" in kwargs
|
|
29
30
|
kwargs["axis"] = -1
|
|
30
31
|
f, p = welch(x, **kwargs)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
f = f[idx]
|
|
36
|
-
p = p[..., idx]
|
|
32
|
+
f_min, f_max = utils.get_valid_freq_band(
|
|
33
|
+
kwargs["fs"], x.shape[-1], f_min, f_max
|
|
34
|
+
)
|
|
35
|
+
f, p = utils.slice_freq_band(f, p, f_min=f_min, f_max=f_max)
|
|
37
36
|
return f, p
|
|
38
37
|
|
|
39
38
|
|
|
@@ -113,22 +112,5 @@ def spectral_slope(f, p):
|
|
|
113
112
|
DBSpectralFeatureExtractor,
|
|
114
113
|
)
|
|
115
114
|
@univariate_feature
|
|
116
|
-
def spectral_bands_power(
|
|
117
|
-
f,
|
|
118
|
-
p,
|
|
119
|
-
bands={
|
|
120
|
-
"delta": (1, 4.5),
|
|
121
|
-
"theta": (4.5, 8),
|
|
122
|
-
"alpha": (8, 12),
|
|
123
|
-
"beta": (12, 30),
|
|
124
|
-
},
|
|
125
|
-
):
|
|
126
|
-
bands_power = dict()
|
|
127
|
-
for k, v in bands.items():
|
|
128
|
-
assert isinstance(k, str)
|
|
129
|
-
assert isinstance(v, tuple)
|
|
130
|
-
assert len(v) == 2
|
|
131
|
-
mask = np.logical_and(f > v[0], f < v[1])
|
|
132
|
-
power = p[..., mask].sum(axis=-1)
|
|
133
|
-
bands_power[k] = power
|
|
134
|
-
return bands_power
|
|
115
|
+
def spectral_bands_power(f, p, bands=utils.DEFAULT_FREQ_BANDS):
|
|
116
|
+
return utils.reduce_freq_bands(f, p, bands, np.sum)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
DEFAULT_FREQ_BANDS = {
|
|
4
|
+
"delta": (1, 4.5),
|
|
5
|
+
"theta": (4.5, 8),
|
|
6
|
+
"alpha": (8, 12),
|
|
7
|
+
"beta": (12, 30),
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_valid_freq_band(fs, n, f_min=None, f_max=None):
|
|
12
|
+
f0 = 2 * fs / n
|
|
13
|
+
f1 = fs / 2
|
|
14
|
+
if f_min is None:
|
|
15
|
+
f_min = f0
|
|
16
|
+
else:
|
|
17
|
+
assert f_min >= f0
|
|
18
|
+
if f_max is None:
|
|
19
|
+
f_max = f1
|
|
20
|
+
else:
|
|
21
|
+
assert f_max <= f1
|
|
22
|
+
return f_min, f_max
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def slice_freq_band(f, *x, f_min=None, f_max=None):
|
|
26
|
+
if f_min is None and f_max is None:
|
|
27
|
+
return f, *x
|
|
28
|
+
else:
|
|
29
|
+
f_min_idx = f >= f_min if f_min is not None else True
|
|
30
|
+
f_max_idx = f <= f_max if f_max is not None else True
|
|
31
|
+
idx = np.logical_and(f_min_idx, f_max_idx)
|
|
32
|
+
f = f[idx]
|
|
33
|
+
xl = [*x]
|
|
34
|
+
for i, xi in enumerate(xl):
|
|
35
|
+
xl[i] = xi[..., idx]
|
|
36
|
+
return f, *xl
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def reduce_freq_bands(f, x, bands, reduce_func=np.sum):
|
|
40
|
+
x_bands = dict()
|
|
41
|
+
for k, lims in bands.items():
|
|
42
|
+
assert isinstance(k, str)
|
|
43
|
+
assert len(lims) == 2 and lims[0] <= lims[1]
|
|
44
|
+
assert lims[0] >= f[0] and lims[1] <= f[-1]
|
|
45
|
+
mask = np.logical_and(f >= lims[0], f < lims[1])
|
|
46
|
+
xf = x[..., mask]
|
|
47
|
+
x_bands[k] = reduce_func(xf, axis=-1)
|
|
48
|
+
return x_bands
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
|
|
4
|
+
from . import extractors, feature_bank
|
|
5
|
+
from .extractors import FeatureExtractor, MultivariateFeature, _get_underlying_func
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_feature_predecessors(feature_or_extractor: Callable):
|
|
9
|
+
current = _get_underlying_func(feature_or_extractor)
|
|
10
|
+
if current is FeatureExtractor:
|
|
11
|
+
return [current]
|
|
12
|
+
predecessor = getattr(current, "parent_extractor_type", [FeatureExtractor])
|
|
13
|
+
if len(predecessor) == 1:
|
|
14
|
+
return [current, *get_feature_predecessors(predecessor[0])]
|
|
15
|
+
else:
|
|
16
|
+
predecessors = [get_feature_predecessors(pred) for pred in predecessor]
|
|
17
|
+
for i in range(len(predecessors)):
|
|
18
|
+
if isinstance(predecessors[i], list) and len(predecessors[i]) == 1:
|
|
19
|
+
predecessors[i] = predecessors[i][0]
|
|
20
|
+
return [current, tuple(predecessors)]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_feature_kind(feature: Callable):
|
|
24
|
+
return _get_underlying_func(feature).feature_kind
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_all_features():
|
|
28
|
+
def isfeature(x):
|
|
29
|
+
return hasattr(_get_underlying_func(x), "feature_kind")
|
|
30
|
+
|
|
31
|
+
return inspect.getmembers(feature_bank, isfeature)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_all_feature_extractors():
|
|
35
|
+
def isfeatureextractor(x):
|
|
36
|
+
return inspect.isclass(x) and issubclass(x, FeatureExtractor)
|
|
37
|
+
|
|
38
|
+
return [
|
|
39
|
+
("FeatureExtractor", FeatureExtractor),
|
|
40
|
+
*inspect.getmembers(feature_bank, isfeatureextractor),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_all_feature_kinds():
|
|
45
|
+
def isfeaturekind(x):
|
|
46
|
+
return inspect.isclass(x) and issubclass(x, MultivariateFeature)
|
|
47
|
+
|
|
48
|
+
return inspect.getmembers(extractors, isfeaturekind)
|
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Convenience functions for storing and loading of features datasets.
|
|
1
|
+
"""Convenience functions for storing and loading of features datasets.
|
|
3
2
|
|
|
4
3
|
see also: https://github.com/braindecode/braindecode//blob/master/braindecode/datautil/serialization.py#L165-L229
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
|
-
import json
|
|
8
6
|
from pathlib import Path
|
|
9
7
|
|
|
10
8
|
import pandas as pd
|
|
11
9
|
from joblib import Parallel, delayed
|
|
12
|
-
|
|
13
10
|
from mne.io import read_info
|
|
11
|
+
|
|
14
12
|
from braindecode.datautil.serialization import _load_kwargs_json
|
|
15
13
|
|
|
16
14
|
from .datasets import (
|
|
17
|
-
FeaturesDataset,
|
|
18
15
|
FeaturesConcatDataset,
|
|
16
|
+
FeaturesDataset,
|
|
19
17
|
)
|
|
20
18
|
|
|
21
19
|
|
|
@@ -34,6 +32,7 @@ def load_features_concat_dataset(path, ids_to_load=None, n_jobs=1):
|
|
|
34
32
|
Returns
|
|
35
33
|
-------
|
|
36
34
|
concat_dataset: FeaturesConcatDataset of FeaturesDatasets
|
|
35
|
+
|
|
37
36
|
"""
|
|
38
37
|
# Make sure we always work with a pathlib.Path
|
|
39
38
|
path = Path(path)
|
eegdash/features/utils.py
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
from typing import Dict, List
|
|
2
|
-
from collections.abc import Callable
|
|
3
1
|
import copy
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
4
5
|
import numpy as np
|
|
5
6
|
import pandas as pd
|
|
6
7
|
from joblib import Parallel, delayed
|
|
7
|
-
from tqdm import tqdm
|
|
8
8
|
from torch.utils.data import DataLoader
|
|
9
|
+
from tqdm import tqdm
|
|
10
|
+
|
|
9
11
|
from braindecode.datasets.base import (
|
|
12
|
+
BaseConcatDataset,
|
|
10
13
|
EEGWindowsDataset,
|
|
11
14
|
WindowsDataset,
|
|
12
|
-
BaseConcatDataset,
|
|
13
15
|
)
|
|
14
16
|
|
|
15
|
-
from .datasets import
|
|
17
|
+
from .datasets import FeaturesConcatDataset, FeaturesDataset
|
|
16
18
|
from .extractors import FeatureExtractor
|
|
17
19
|
|
|
18
20
|
|
|
@@ -53,7 +55,7 @@ def _extract_features_from_windowsdataset(
|
|
|
53
55
|
metadata.reset_index(drop=True, inplace=True)
|
|
54
56
|
metadata.drop("orig_index", axis=1, inplace=True)
|
|
55
57
|
|
|
56
|
-
# FUTURE:
|
|
58
|
+
# FUTURE: truly support WindowsDataset objects
|
|
57
59
|
return FeaturesDataset(
|
|
58
60
|
features_df,
|
|
59
61
|
metadata=metadata,
|
|
@@ -100,7 +102,7 @@ def fit_feature_extractors(
|
|
|
100
102
|
features = dict(enumerate(features))
|
|
101
103
|
if not isinstance(features, FeatureExtractor):
|
|
102
104
|
features = FeatureExtractor(features)
|
|
103
|
-
if not features.
|
|
105
|
+
if not features._is_trainable:
|
|
104
106
|
return features
|
|
105
107
|
features.clear()
|
|
106
108
|
concat_dl = DataLoader(
|
eegdash/preprocessing.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import mne
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from braindecode.preprocessing import (
|
|
7
|
+
Preprocessor,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("eegdash")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class hbn_ec_ec_reannotation(Preprocessor):
|
|
14
|
+
"""Preprocessor to reannotate the raw data for eyes open and eyes closed events.
|
|
15
|
+
|
|
16
|
+
This processor is designed for HBN datasets.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__(fn=self.transform, apply_on_array=False)
|
|
22
|
+
|
|
23
|
+
def transform(self, raw):
|
|
24
|
+
"""Reannotate the raw data to create new events for eyes open and eyes closed
|
|
25
|
+
|
|
26
|
+
This function modifies the raw MNE object by creating new events based on
|
|
27
|
+
the existing annotations for "instructed_toCloseEyes" and "instructed_toOpenEyes".
|
|
28
|
+
It generates new events every 2 seconds within specified time ranges after
|
|
29
|
+
the original events, and replaces the existing annotations with these new events.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
raw : mne.io.Raw
|
|
34
|
+
The raw MNE object containing EEG data and annotations.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
events, event_id = mne.events_from_annotations(raw)
|
|
38
|
+
|
|
39
|
+
logger.info("Original events found with ids: %s", event_id)
|
|
40
|
+
|
|
41
|
+
# Create new events array for 2-second segments
|
|
42
|
+
new_events = []
|
|
43
|
+
sfreq = raw.info["sfreq"]
|
|
44
|
+
for event in events[events[:, 2] == event_id["instructed_toCloseEyes"]]:
|
|
45
|
+
# For each original event, create events every 2 seconds from 15s to 29s after
|
|
46
|
+
start_times = event[0] + np.arange(15, 29, 2) * sfreq
|
|
47
|
+
new_events.extend([[int(t), 0, 1] for t in start_times])
|
|
48
|
+
|
|
49
|
+
for event in events[events[:, 2] == event_id["instructed_toOpenEyes"]]:
|
|
50
|
+
# For each original event, create events every 2 seconds from 5s to 19s after
|
|
51
|
+
start_times = event[0] + np.arange(5, 19, 2) * sfreq
|
|
52
|
+
new_events.extend([[int(t), 0, 2] for t in start_times])
|
|
53
|
+
|
|
54
|
+
# replace events in raw
|
|
55
|
+
new_events = np.array(new_events)
|
|
56
|
+
|
|
57
|
+
annot_from_events = mne.annotations_from_events(
|
|
58
|
+
events=new_events,
|
|
59
|
+
event_desc={1: "eyes_closed", 2: "eyes_open"},
|
|
60
|
+
sfreq=raw.info["sfreq"],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
raw.set_annotations(annot_from_events)
|
|
64
|
+
|
|
65
|
+
return raw
|
eegdash/utils.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
def __init__mongo_client():
|
|
2
|
+
from mne.utils import get_config, set_config
|
|
3
|
+
|
|
4
|
+
if get_config("EEGDASH_DB_URI") is None:
|
|
5
|
+
# Set the default MongoDB URI for EEGDash
|
|
6
|
+
# This is a placeholder and should be replaced with your actual MongoDB URI
|
|
7
|
+
|
|
8
|
+
set_config(
|
|
9
|
+
"EEGDASH_DB_URI",
|
|
10
|
+
"mongodb+srv://eegdash-user:mdzoMjQcHWTVnKDq@cluster0.vz35p.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0",
|
|
11
|
+
)
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eegdash
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: EEG data for machine learning
|
|
5
|
-
Author-email: Young Truong <dt.young112@gmail.com>, Arnaud Delorme <adelorme@gmail.com>
|
|
5
|
+
Author-email: Young Truong <dt.young112@gmail.com>, Arnaud Delorme <adelorme@gmail.com>, Bruno Aristimunha <b.aristimunha@gmail.com>
|
|
6
6
|
License: GNU General Public License
|
|
7
7
|
|
|
8
8
|
Copyright (C) 2024-2025
|
|
9
9
|
|
|
10
10
|
Young Truong, UCSD, dt.young112@gmail.com
|
|
11
11
|
Arnaud Delorme, UCSD, adelorme@ucsd.edu
|
|
12
|
+
Bruno Aristimunha, b.aristimunha@gmail.com
|
|
12
13
|
|
|
13
14
|
This program is free software; you can redistribute it and/or modify
|
|
14
15
|
it under the terms of the GNU General Public License as published by
|
|
@@ -24,35 +25,69 @@ License: GNU General Public License
|
|
|
24
25
|
along with this program; if not, write to the Free Software
|
|
25
26
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1.07 USA
|
|
26
27
|
|
|
27
|
-
Project-URL: Homepage, https://
|
|
28
|
-
Project-URL: Issues, https://github.com/sccn/
|
|
29
|
-
Classifier: Programming Language :: Python :: 3
|
|
28
|
+
Project-URL: Homepage, https://github.com/sccn/EEG-Dash-Data
|
|
29
|
+
Project-URL: Issues, https://github.com/sccn/EEG-Dash-Data/issues
|
|
30
30
|
Classifier: License :: OSI Approved :: MIT License
|
|
31
31
|
Classifier: Operating System :: OS Independent
|
|
32
|
-
|
|
32
|
+
Classifier: Intended Audience :: Science/Research
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: Programming Language :: Python
|
|
35
|
+
Classifier: Topic :: Software Development
|
|
36
|
+
Classifier: Topic :: Scientific/Engineering
|
|
37
|
+
Classifier: Development Status :: 3 - Alpha
|
|
38
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
39
|
+
Classifier: Operating System :: POSIX
|
|
40
|
+
Classifier: Operating System :: Unix
|
|
41
|
+
Classifier: Operating System :: MacOS
|
|
42
|
+
Classifier: Programming Language :: Python :: 3
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
46
|
+
Requires-Python: >3.10
|
|
33
47
|
Description-Content-Type: text/markdown
|
|
34
48
|
License-File: LICENSE
|
|
35
|
-
Requires-Dist:
|
|
49
|
+
Requires-Dist: braindecode>=1.0
|
|
50
|
+
Requires-Dist: mne_bids>=0.16.0
|
|
51
|
+
Requires-Dist: numba
|
|
52
|
+
Requires-Dist: numpy
|
|
53
|
+
Requires-Dist: pandas
|
|
54
|
+
Requires-Dist: pybids
|
|
55
|
+
Requires-Dist: pymongo
|
|
36
56
|
Requires-Dist: python-dotenv
|
|
37
57
|
Requires-Dist: s3fs
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist: pynwb
|
|
40
|
-
Requires-Dist: h5py
|
|
41
|
-
Requires-Dist: pymongo
|
|
42
|
-
Requires-Dist: joblib
|
|
43
|
-
Requires-Dist: braindecode
|
|
44
|
-
Requires-Dist: mne-bids
|
|
45
|
-
Requires-Dist: pybids
|
|
46
|
-
Requires-Dist: pymatreader
|
|
47
|
-
Requires-Dist: pyarrow
|
|
58
|
+
Requires-Dist: scipy
|
|
48
59
|
Requires-Dist: tqdm
|
|
49
|
-
Requires-Dist:
|
|
60
|
+
Requires-Dist: xarray
|
|
61
|
+
Provides-Extra: tests
|
|
62
|
+
Requires-Dist: pytest; extra == "tests"
|
|
63
|
+
Requires-Dist: pytest-cov; extra == "tests"
|
|
64
|
+
Requires-Dist: codecov; extra == "tests"
|
|
65
|
+
Requires-Dist: pytest_cases; extra == "tests"
|
|
66
|
+
Provides-Extra: dev
|
|
67
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
68
|
+
Provides-Extra: docs
|
|
69
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
70
|
+
Requires-Dist: sphinx_gallery; extra == "docs"
|
|
71
|
+
Requires-Dist: sphinx_rtd_theme; extra == "docs"
|
|
72
|
+
Requires-Dist: numpydoc; extra == "docs"
|
|
73
|
+
Provides-Extra: all
|
|
74
|
+
Requires-Dist: pytest; extra == "all"
|
|
75
|
+
Requires-Dist: pytest-cov; extra == "all"
|
|
76
|
+
Requires-Dist: codecov; extra == "all"
|
|
77
|
+
Requires-Dist: pytest_cases; extra == "all"
|
|
78
|
+
Requires-Dist: pre-commit; extra == "all"
|
|
79
|
+
Requires-Dist: sphinx; extra == "all"
|
|
80
|
+
Requires-Dist: sphinx_gallery; extra == "all"
|
|
81
|
+
Requires-Dist: sphinx_rtd_theme; extra == "all"
|
|
82
|
+
Requires-Dist: numpydoc; extra == "all"
|
|
50
83
|
Dynamic: license-file
|
|
51
84
|
|
|
52
85
|
# EEG-Dash
|
|
86
|
+
|
|
53
87
|
To leverage recent and ongoing advancements in large-scale computational methods and to ensure the preservation of scientific data generated from publicly funded research, the EEG-DaSh data archive will create a data-sharing resource for MEEG (EEG, MEG) data contributed by collaborators for machine learning (ML) and deep learning (DL) applications.
|
|
54
88
|
|
|
55
89
|
## Data source
|
|
90
|
+
|
|
56
91
|
The data in EEG-DaSh originates from a collaboration involving 25 laboratories, encompassing 27,053 participants. This extensive collection includes MEEG data, which is a combination of EEG and MEG signals. The data is sourced from various studies conducted by these labs, involving both healthy subjects and clinical populations with conditions such as ADHD, depression, schizophrenia, dementia, autism, and psychosis. Additionally, data spans different mental states like sleep, meditation, and cognitive tasks. In addition, EEG-DaSh will incorporate a subset of the data converted from NEMAR, which includes 330 MEEG BIDS-formatted datasets, further expanding the archive with well-curated, standardized neuroelectromagnetic data.
|
|
57
92
|
|
|
58
93
|
## Featured data
|
|
@@ -72,9 +107,11 @@ The following HBN datasets are currently featured on EEGDash. Documentation abou
|
|
|
72
107
|
A total of [246 other datasets](datasets.md) are also available through EEGDash.
|
|
73
108
|
|
|
74
109
|
## Data format
|
|
110
|
+
|
|
75
111
|
EEGDash queries return a **Pytorch Dataset** formatted to facilitate machine learning (ML) and deep learning (DL) applications. PyTorch Datasets are the best format for EEGDash queries because they provide an efficient, scalable, and flexible structure for machine learning (ML) and deep learning (DL) applications. They allow seamless integration with PyTorch’s DataLoader, enabling efficient batching, shuffling, and parallel data loading, which is essential for training deep learning models on large EEG datasets.
|
|
76
112
|
|
|
77
113
|
## Data preprocessing
|
|
114
|
+
|
|
78
115
|
EEGDash datasets are processed using the popular [BrainDecode](https://braindecode.org/stable/index.html) library. In fact, EEGDash datasets are BrainDecode datasets, which are themselves PyTorch datasets. This means that any preprocessing possible on BrainDecode datasets is also possible on EEGDash datasets. Refer to [BrainDecode](https://braindecode.org/stable/index.html) tutorials for guidance on preprocessing EEG data.
|
|
79
116
|
|
|
80
117
|
## EEG-Dash usage
|
|
@@ -90,7 +127,10 @@ To use the data from a single subject, enter:
|
|
|
90
127
|
|
|
91
128
|
```python
|
|
92
129
|
from eegdash import EEGDashDataset
|
|
93
|
-
|
|
130
|
+
|
|
131
|
+
ds_NDARDB033FW5 = EEGDashDataset(
|
|
132
|
+
{"dataset": "ds005514", "task": "RestingState", "subject": "NDARDB033FW5"}
|
|
133
|
+
)
|
|
94
134
|
```
|
|
95
135
|
|
|
96
136
|
This will search and download the metadata for the task **RestingState** for subject **NDARDB033FW5** in BIDS dataset **ds005514**. The actual data will not be downloaded at this stage. Following standard practice, data is only downloaded once it is processed. The **ds_NDARDB033FW5** object is a fully functional BrainDecode dataset, which is itself a PyTorch dataset. This [tutorial](https://github.com/sccn/EEGDash/blob/develop/notebooks/tutorial_eoec.ipynb) shows how to preprocess the EEG data, extracting portions of the data containing eyes-open and eyes-closed segments, then perform eyes-open vs. eyes-closed classification using a (shallow) deep-learning model.
|
|
@@ -99,7 +139,10 @@ To use the data from multiple subjects, enter:
|
|
|
99
139
|
|
|
100
140
|
```python
|
|
101
141
|
from eegdash import EEGDashDataset
|
|
102
|
-
|
|
142
|
+
|
|
143
|
+
ds_ds005505rest = EEGDashDataset(
|
|
144
|
+
{"dataset": "ds005505", "task": "RestingState"}, target_name="sex"
|
|
145
|
+
)
|
|
103
146
|
```
|
|
104
147
|
|
|
105
148
|
This will search and download the metadata for the task 'RestingState' for all subjects in BIDS dataset 'ds005505' (a total of 136). As above, the actual data will not be downloaded at this stage so this command is quick to execute. Also, the target class for each subject is assigned using the target_name parameter. This means that this object is ready to be directly fed to a deep learning model, although the [tutorial script](https://github.com/sccn/EEGDash/blob/develop/notebooks/tutorial_sex_classification.ipynb) performs minimal processing on it, prior to training a deep-learning model. Because 14 gigabytes of data are downloaded, this tutorial takes about 10 minutes to execute.
|
|
@@ -121,3 +164,7 @@ EEG-DaSh is a collaborative initiative between the United States and Israel, sup
|
|
|
121
164
|
|
|
122
165
|
|
|
123
166
|
|
|
167
|
+
python3 -m pip install --upgrade build
|
|
168
|
+
python3 -m build
|
|
169
|
+
python3 -m pip install --upgrade twine
|
|
170
|
+
python3 -m twine upload --repository eegdash dist/*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
eegdash/__init__.py,sha256=nMBZrB4bJs79rl9TZ_x-IVPNTHYJS6V55Pus9fErB5E,232
|
|
2
|
+
eegdash/api.py,sha256=1mbufz6qgujrY_9V2GWUP4Eqel_q4ns9XVrmbGcSJG0,25776
|
|
3
|
+
eegdash/data_config.py,sha256=OS6ERO-jHrnEOfMJUehY7ieABdsRw_qWzOKJ4pzSfqw,1323
|
|
4
|
+
eegdash/data_utils.py,sha256=mR0TtERYIefakGQ98jwAeeRVKSNDU9eBlUoH1AY9tnc,23663
|
|
5
|
+
eegdash/dataset.py,sha256=qXcE4JxxYj89VQ84sKmq7kGcunZqt1pp5wz7a62j_OQ,2460
|
|
6
|
+
eegdash/preprocessing.py,sha256=wvqAO8UgDoQQz7xjVykrl4V8AawS4tpKR4Vrr_9BovY,2230
|
|
7
|
+
eegdash/utils.py,sha256=ZxVW4ll5MaSZ_ht1L5p7YJxOtYi3b0547oa5W_jbH4A,450
|
|
8
|
+
eegdash/features/__init__.py,sha256=484CLxpPifc8ZQfeM8jWZLvtVKljCxn3qqlUCaq-Yxk,1284
|
|
9
|
+
eegdash/features/datasets.py,sha256=kU1DO70ArSIy-LF1hHD2NN4iT-kJrI0mVpSkyV_OSeI,18301
|
|
10
|
+
eegdash/features/decorators.py,sha256=v0qaJz_dcX703p1fvFYbAIXmwK3d8naYGlq7fRVKn_w,1313
|
|
11
|
+
eegdash/features/extractors.py,sha256=H7h6tP3dKoRcjDJpWWAo0ppmokCq5QlhqMcehYwYV9s,6845
|
|
12
|
+
eegdash/features/inspect.py,sha256=PmbWhx5H_WqpnorUpWONUSkUtaIHkZblRa_Xyk7Szyc,1569
|
|
13
|
+
eegdash/features/serialization.py,sha256=pNsTz0EeRPPYE-A61XK7UoMShI9YBEHQqC5STbzUU6A,2861
|
|
14
|
+
eegdash/features/utils.py,sha256=eM6DdyOpdVfNh7dSPykJ0WaTDtaGvkCQWAmW0G8v60Y,3784
|
|
15
|
+
eegdash/features/feature_bank/__init__.py,sha256=BKrM3aaggXrfey1yEjEBYaxOV5e3UK-o8oGeB30epOg,149
|
|
16
|
+
eegdash/features/feature_bank/complexity.py,sha256=Ds1GAXZ0LGM32xB4EZC2jbMljUBv0yicf2SkuyLvN5I,3183
|
|
17
|
+
eegdash/features/feature_bank/connectivity.py,sha256=bQ6KlxWm5GNpCS9ypLqBUr2L171Yq7wpBQT2tRQKTZ4,2159
|
|
18
|
+
eegdash/features/feature_bank/csp.py,sha256=YOzieLnOcqjvfrcjvg8R3S4SWuC1BqK5J5WXVNCCTc0,3304
|
|
19
|
+
eegdash/features/feature_bank/dimensionality.py,sha256=j_Ds71Y1AbV2uLFQj8EuXQ4kzofLBlQtPV5snMkF7i4,3965
|
|
20
|
+
eegdash/features/feature_bank/signal.py,sha256=3Tb8z9gX7iZipxQJ9DSyy30JfdmW58kgvimSyZX74p8,3404
|
|
21
|
+
eegdash/features/feature_bank/spectral.py,sha256=bNB7skusePs1gX7NOU6yRlw_Gr4UOCkO_ylkCgybzug,3319
|
|
22
|
+
eegdash/features/feature_bank/utils.py,sha256=DGh-Q7-XFIittP7iBBxvsJaZrlVvuY5mw-G7q6C-PCI,1237
|
|
23
|
+
eegdash-0.2.0.dist-info/licenses/LICENSE,sha256=KykUD4H3kw3HLz5bZ0kxMWwZotnk8rhkfCCerGyX2sk,855
|
|
24
|
+
eegdash-0.2.0.dist-info/METADATA,sha256=GhxMc7p2HvTZo9lZFjBX1tJ70VeMlMnaYBhvpqw0iG8,10220
|
|
25
|
+
eegdash-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
+
eegdash-0.2.0.dist-info/top_level.txt,sha256=zavO69HQ6MyZM0aQMR2zUS6TAFc7bnN5GEpDpOpFZzU,8
|
|
27
|
+
eegdash-0.2.0.dist-info/RECORD,,
|
|
@@ -4,6 +4,7 @@ Copyright (C) 2024-2025
|
|
|
4
4
|
|
|
5
5
|
Young Truong, UCSD, dt.young112@gmail.com
|
|
6
6
|
Arnaud Delorme, UCSD, adelorme@ucsd.edu
|
|
7
|
+
Bruno Aristimunha, b.aristimunha@gmail.com
|
|
7
8
|
|
|
8
9
|
This program is free software; you can redistribute it and/or modify
|
|
9
10
|
it under the terms of the GNU General Public License as published by
|