eegdash 0.3.3.dev61__py3-none-any.whl → 0.5.0.dev180784713__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 (45) hide show
  1. eegdash/__init__.py +19 -6
  2. eegdash/api.py +336 -539
  3. eegdash/bids_eeg_metadata.py +495 -0
  4. eegdash/const.py +349 -0
  5. eegdash/dataset/__init__.py +28 -0
  6. eegdash/dataset/base.py +311 -0
  7. eegdash/dataset/bids_dataset.py +641 -0
  8. eegdash/dataset/dataset.py +692 -0
  9. eegdash/dataset/dataset_summary.csv +255 -0
  10. eegdash/dataset/registry.py +287 -0
  11. eegdash/downloader.py +197 -0
  12. eegdash/features/__init__.py +15 -13
  13. eegdash/features/datasets.py +329 -138
  14. eegdash/features/decorators.py +105 -13
  15. eegdash/features/extractors.py +233 -63
  16. eegdash/features/feature_bank/__init__.py +12 -12
  17. eegdash/features/feature_bank/complexity.py +22 -20
  18. eegdash/features/feature_bank/connectivity.py +27 -28
  19. eegdash/features/feature_bank/csp.py +3 -1
  20. eegdash/features/feature_bank/dimensionality.py +6 -6
  21. eegdash/features/feature_bank/signal.py +29 -30
  22. eegdash/features/feature_bank/spectral.py +40 -44
  23. eegdash/features/feature_bank/utils.py +8 -0
  24. eegdash/features/inspect.py +126 -15
  25. eegdash/features/serialization.py +58 -17
  26. eegdash/features/utils.py +90 -16
  27. eegdash/hbn/__init__.py +28 -0
  28. eegdash/hbn/preprocessing.py +105 -0
  29. eegdash/hbn/windows.py +428 -0
  30. eegdash/logging.py +54 -0
  31. eegdash/mongodb.py +55 -24
  32. eegdash/paths.py +52 -0
  33. eegdash/utils.py +29 -1
  34. eegdash-0.5.0.dev180784713.dist-info/METADATA +121 -0
  35. eegdash-0.5.0.dev180784713.dist-info/RECORD +38 -0
  36. eegdash-0.5.0.dev180784713.dist-info/licenses/LICENSE +29 -0
  37. eegdash/data_config.py +0 -34
  38. eegdash/data_utils.py +0 -687
  39. eegdash/dataset.py +0 -69
  40. eegdash/preprocessing.py +0 -63
  41. eegdash-0.3.3.dev61.dist-info/METADATA +0 -192
  42. eegdash-0.3.3.dev61.dist-info/RECORD +0 -28
  43. eegdash-0.3.3.dev61.dist-info/licenses/LICENSE +0 -23
  44. {eegdash-0.3.3.dev61.dist-info → eegdash-0.5.0.dev180784713.dist-info}/WHEEL +0 -0
  45. {eegdash-0.3.3.dev61.dist-info → eegdash-0.5.0.dev180784713.dist-info}/top_level.txt +0 -0
@@ -3,11 +3,10 @@ import numpy as np
3
3
  from sklearn.neighbors import KDTree
4
4
 
5
5
  from ..decorators import FeaturePredecessor, univariate_feature
6
- from ..extractors import FeatureExtractor
7
6
  from .signal import SIGNAL_PREDECESSORS
8
7
 
9
8
  __all__ = [
10
- "EntropyFeatureExtractor",
9
+ "complexity_entropy_preprocessor",
11
10
  "complexity_approx_entropy",
12
11
  "complexity_sample_entropy",
13
12
  "complexity_svd_entropy",
@@ -30,28 +29,31 @@ def _channel_app_samp_entropy_counts(x, m, r, l):
30
29
 
31
30
 
32
31
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
33
- class EntropyFeatureExtractor(FeatureExtractor):
34
- def preprocess(self, x, m=2, r=0.2, l=1):
35
- rr = r * x.std(axis=-1)
36
- counts_m = np.empty((*x.shape[:-1], (x.shape[-1] - m + 1) // l))
37
- counts_mp1 = np.empty((*x.shape[:-1], (x.shape[-1] - m) // l))
38
- for i in np.ndindex(x.shape[:-1]):
39
- counts_m[*i, :] = _channel_app_samp_entropy_counts(x[i], m, rr[i], l)
40
- counts_mp1[*i, :] = _channel_app_samp_entropy_counts(x[i], m + 1, rr[i], l)
41
- return counts_m, counts_mp1
42
-
43
-
44
- @FeaturePredecessor(EntropyFeatureExtractor)
32
+ def complexity_entropy_preprocessor(x, /, m=2, r=0.2, l=1):
33
+ rr = r * x.std(axis=-1)
34
+ counts_m = np.empty((*x.shape[:-1], (x.shape[-1] - m + 1) // l))
35
+ counts_mp1 = np.empty((*x.shape[:-1], (x.shape[-1] - m) // l))
36
+ for i in np.ndindex(x.shape[:-1]):
37
+ counts_m[i + (slice(None),)] = _channel_app_samp_entropy_counts(
38
+ x[i], m, rr[i], l
39
+ )
40
+ counts_mp1[i + (slice(None),)] = _channel_app_samp_entropy_counts(
41
+ x[i], m + 1, rr[i], l
42
+ )
43
+ return counts_m, counts_mp1
44
+
45
+
46
+ @FeaturePredecessor(complexity_entropy_preprocessor)
45
47
  @univariate_feature
46
- def complexity_approx_entropy(counts_m, counts_mp1):
48
+ def complexity_approx_entropy(counts_m, counts_mp1, /):
47
49
  phi_m = np.log(counts_m / counts_m.shape[-1]).mean(axis=-1)
48
50
  phi_mp1 = np.log(counts_mp1 / counts_mp1.shape[-1]).mean(axis=-1)
49
51
  return phi_m - phi_mp1
50
52
 
51
53
 
52
- @FeaturePredecessor(EntropyFeatureExtractor)
54
+ @FeaturePredecessor(complexity_entropy_preprocessor)
53
55
  @univariate_feature
54
- def complexity_sample_entropy(counts_m, counts_mp1):
56
+ def complexity_sample_entropy(counts_m, counts_mp1, /):
55
57
  A = np.sum(counts_mp1 - 1, axis=-1)
56
58
  B = np.sum(counts_m - 1, axis=-1)
57
59
  return -np.log(A / B)
@@ -59,10 +61,10 @@ def complexity_sample_entropy(counts_m, counts_mp1):
59
61
 
60
62
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
61
63
  @univariate_feature
62
- def complexity_svd_entropy(x, m=10, tau=1):
64
+ def complexity_svd_entropy(x, /, m=10, tau=1):
63
65
  x_emb = np.empty((*x.shape[:-1], (x.shape[-1] - m + 1) // tau, m))
64
66
  for i in np.ndindex(x.shape[:-1]):
65
- x_emb[*i, :, :] = _create_embedding(x[i], m, tau)
67
+ x_emb[i + (slice(None), slice(None))] = _create_embedding(x[i], m, tau)
66
68
  s = np.linalg.svdvals(x_emb)
67
69
  s /= s.sum(axis=-1, keepdims=True)
68
70
  return -np.sum(s * np.log(s), axis=-1)
@@ -71,7 +73,7 @@ def complexity_svd_entropy(x, m=10, tau=1):
71
73
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
72
74
  @univariate_feature
73
75
  @nb.njit(cache=True, fastmath=True)
74
- def complexity_lempel_ziv(x, threshold=None, normalize=True):
76
+ def complexity_lempel_ziv(x, /, threshold=None, normalize=True):
75
77
  lzc = np.empty(x.shape[:-1])
76
78
  for i in np.ndindex(x.shape[:-1]):
77
79
  t = np.median(x[i]) if threshold is None else threshold
@@ -4,56 +4,55 @@ import numpy as np
4
4
  from scipy.signal import csd
5
5
 
6
6
  from ..decorators import FeaturePredecessor, bivariate_feature
7
- from ..extractors import BivariateFeature, FeatureExtractor
7
+ from ..extractors import BivariateFeature
8
8
  from . import utils
9
+ from .signal import SIGNAL_PREDECESSORS
9
10
 
10
11
  __all__ = [
11
- "CoherenceFeatureExtractor",
12
+ "connectivity_coherency_preprocessor",
12
13
  "connectivity_magnitude_square_coherence",
13
14
  "connectivity_imaginary_coherence",
14
15
  "connectivity_lagged_coherence",
15
16
  ]
16
17
 
17
18
 
18
- class CoherenceFeatureExtractor(FeatureExtractor):
19
- def preprocess(self, x, **kwargs):
20
- f_min = kwargs.pop("f_min") if "f_min" in kwargs else None
21
- f_max = kwargs.pop("f_max") if "f_max" in kwargs else None
22
- assert "fs" in kwargs and "nperseg" in kwargs
23
- kwargs["axis"] = -1
24
- n = x.shape[1]
25
- idx_x, idx_y = BivariateFeature.get_pair_iterators(n)
26
- ix, iy = list(chain(range(n), idx_x)), list(chain(range(n), idx_y))
27
- f, s = csd(x[:, ix], x[:, iy], **kwargs)
28
- f_min, f_max = utils.get_valid_freq_band(
29
- kwargs["fs"], x.shape[-1], f_min, f_max
30
- )
31
- f, s = utils.slice_freq_band(f, s, f_min=f_min, f_max=f_max)
32
- p, sxy = np.split(s, [n], axis=1)
33
- sxx, syy = p[:, idx_x].real, p[:, idx_y].real
34
- c = sxy / np.sqrt(sxx * syy)
35
- return f, c
36
-
37
-
38
- @FeaturePredecessor(CoherenceFeatureExtractor)
19
+ @FeaturePredecessor(*SIGNAL_PREDECESSORS)
20
+ def connectivity_coherency_preprocessor(x, /, **kwargs):
21
+ f_min = kwargs.pop("f_min") if "f_min" in kwargs else None
22
+ f_max = kwargs.pop("f_max") if "f_max" in kwargs else None
23
+ assert "fs" in kwargs and "nperseg" in kwargs
24
+ kwargs["axis"] = -1
25
+ n = x.shape[1]
26
+ idx_x, idx_y = BivariateFeature.get_pair_iterators(n)
27
+ ix, iy = list(chain(range(n), idx_x)), list(chain(range(n), idx_y))
28
+ f, s = csd(x[:, ix], x[:, iy], **kwargs)
29
+ f_min, f_max = utils.get_valid_freq_band(kwargs["fs"], x.shape[-1], f_min, f_max)
30
+ f, s = utils.slice_freq_band(f, s, f_min=f_min, f_max=f_max)
31
+ p, sxy = np.split(s, [n], axis=1)
32
+ sxx, syy = p[:, idx_x].real, p[:, idx_y].real
33
+ c = sxy / np.sqrt(sxx * syy)
34
+ return f, c
35
+
36
+
37
+ @FeaturePredecessor(connectivity_coherency_preprocessor)
39
38
  @bivariate_feature
40
- def connectivity_magnitude_square_coherence(f, c, bands=utils.DEFAULT_FREQ_BANDS):
39
+ def connectivity_magnitude_square_coherence(f, c, /, bands=utils.DEFAULT_FREQ_BANDS):
41
40
  # https://neuroimage.usc.edu/brainstorm/Tutorials/Connectivity
42
41
  coher = c.real**2 + c.imag**2
43
42
  return utils.reduce_freq_bands(f, coher, bands, np.mean)
44
43
 
45
44
 
46
- @FeaturePredecessor(CoherenceFeatureExtractor)
45
+ @FeaturePredecessor(connectivity_coherency_preprocessor)
47
46
  @bivariate_feature
48
- def connectivity_imaginary_coherence(f, c, bands=utils.DEFAULT_FREQ_BANDS):
47
+ def connectivity_imaginary_coherence(f, c, /, bands=utils.DEFAULT_FREQ_BANDS):
49
48
  # https://neuroimage.usc.edu/brainstorm/Tutorials/Connectivity
50
49
  coher = c.imag
51
50
  return utils.reduce_freq_bands(f, coher, bands, np.mean)
52
51
 
53
52
 
54
- @FeaturePredecessor(CoherenceFeatureExtractor)
53
+ @FeaturePredecessor(connectivity_coherency_preprocessor)
55
54
  @bivariate_feature
56
- def connectivity_lagged_coherence(f, c, bands=utils.DEFAULT_FREQ_BANDS):
55
+ def connectivity_lagged_coherence(f, c, /, bands=utils.DEFAULT_FREQ_BANDS):
57
56
  # https://neuroimage.usc.edu/brainstorm/Tutorials/Connectivity
58
57
  coher = c.imag / np.sqrt(1 - c.real)
59
58
  return utils.reduce_freq_bands(f, coher, bands, np.mean)
@@ -3,8 +3,9 @@ import numpy as np
3
3
  import scipy
4
4
  import scipy.linalg
5
5
 
6
- from ..decorators import multivariate_feature
6
+ from ..decorators import FeaturePredecessor, multivariate_feature
7
7
  from ..extractors import TrainableFeature
8
+ from .signal import SIGNAL_PREDECESSORS
8
9
 
9
10
  __all__ = [
10
11
  "CommonSpatialPattern",
@@ -21,6 +22,7 @@ def _update_mean_cov(count, mean, cov, x_count, x_mean, x_cov):
21
22
  cov[:] -= np.outer(mean, mean)
22
23
 
23
24
 
25
+ @FeaturePredecessor(*SIGNAL_PREDECESSORS)
24
26
  @multivariate_feature
25
27
  class CommonSpatialPattern(TrainableFeature):
26
28
  def __init__(self):
@@ -17,7 +17,7 @@ __all__ = [
17
17
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
18
18
  @univariate_feature
19
19
  @nb.njit(cache=True, fastmath=True)
20
- def dimensionality_higuchi_fractal_dim(x, k_max=10, eps=1e-7):
20
+ def dimensionality_higuchi_fractal_dim(x, /, k_max=10, eps=1e-7):
21
21
  N = x.shape[-1]
22
22
  hfd = np.empty(x.shape[:-1])
23
23
  log_k = np.vstack((-np.log(np.arange(1, k_max + 1)), np.ones(k_max))).T
@@ -26,7 +26,7 @@ def dimensionality_higuchi_fractal_dim(x, k_max=10, eps=1e-7):
26
26
  for i in np.ndindex(x.shape[:-1]):
27
27
  for k in range(1, k_max + 1):
28
28
  for m in range(k):
29
- L_km[m] = np.mean(np.abs(np.diff(x[*i, m:], n=k)))
29
+ L_km[m] = np.mean(np.abs(np.diff(x[i + (slice(m, None),)], n=k)))
30
30
  L_k[k - 1] = (N - 1) * np.sum(L_km[:k]) / (k**3)
31
31
  L_k = np.maximum(L_k, eps)
32
32
  hfd[i] = np.linalg.lstsq(log_k, np.log(L_k))[0][0]
@@ -35,7 +35,7 @@ def dimensionality_higuchi_fractal_dim(x, k_max=10, eps=1e-7):
35
35
 
36
36
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
37
37
  @univariate_feature
38
- def dimensionality_petrosian_fractal_dim(x):
38
+ def dimensionality_petrosian_fractal_dim(x, /):
39
39
  nd = signal_zero_crossings(np.diff(x, axis=-1))
40
40
  log_n = np.log(x.shape[-1])
41
41
  return log_n / (np.log(nd) + log_n)
@@ -43,7 +43,7 @@ def dimensionality_petrosian_fractal_dim(x):
43
43
 
44
44
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
45
45
  @univariate_feature
46
- def dimensionality_katz_fractal_dim(x):
46
+ def dimensionality_katz_fractal_dim(x, /):
47
47
  dists = np.abs(np.diff(x, axis=-1))
48
48
  L = dists.sum(axis=-1)
49
49
  a = dists.mean(axis=-1)
@@ -79,7 +79,7 @@ def _hurst_exp(x, ns, a, gamma_ratios, log_n):
79
79
 
80
80
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
81
81
  @univariate_feature
82
- def dimensionality_hurst_exp(x):
82
+ def dimensionality_hurst_exp(x, /):
83
83
  ns = np.unique(np.power(2, np.arange(2, np.log2(x.shape[-1]) - 1)).astype(int))
84
84
  idx = ns > 340
85
85
  gamma_ratios = np.empty(ns.shape[0])
@@ -94,7 +94,7 @@ def dimensionality_hurst_exp(x):
94
94
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
95
95
  @univariate_feature
96
96
  @nb.njit(cache=True, fastmath=True)
97
- def dimensionality_detrended_fluctuation_analysis(x):
97
+ def dimensionality_detrended_fluctuation_analysis(x, /):
98
98
  ns = np.unique(np.floor(np.power(2, np.arange(2, np.log2(x.shape[-1]) - 1))))
99
99
  a = np.vstack((np.arange(ns[-1]), np.ones(int(ns[-1])))).T
100
100
  log_n = np.vstack((np.log(ns), np.ones(ns.shape[0]))).T
@@ -4,93 +4,92 @@ import numpy as np
4
4
  from scipy import signal, stats
5
5
 
6
6
  from ..decorators import FeaturePredecessor, univariate_feature
7
- from ..extractors import FeatureExtractor
8
7
 
9
8
  __all__ = [
10
- "HilbertFeatureExtractor",
11
- "signal_mean",
12
- "signal_variance",
13
- "signal_skewness",
9
+ "signal_hilbert_preprocessor",
10
+ "SIGNAL_PREDECESSORS",
11
+ "signal_decorrelation_time",
12
+ "signal_hjorth_activity",
13
+ "signal_hjorth_complexity",
14
+ "signal_hjorth_mobility",
14
15
  "signal_kurtosis",
15
- "signal_std",
16
- "signal_root_mean_square",
16
+ "signal_line_length",
17
+ "signal_mean",
17
18
  "signal_peak_to_peak",
18
19
  "signal_quantile",
20
+ "signal_root_mean_square",
21
+ "signal_skewness",
22
+ "signal_std",
23
+ "signal_variance",
19
24
  "signal_zero_crossings",
20
- "signal_line_length",
21
- "signal_hjorth_activity",
22
- "signal_hjorth_mobility",
23
- "signal_hjorth_complexity",
24
- "signal_decorrelation_time",
25
25
  ]
26
26
 
27
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))
28
+ @FeaturePredecessor()
29
+ def signal_hilbert_preprocessor(x, /):
30
+ return np.abs(signal.hilbert(x - x.mean(axis=-1, keepdims=True), axis=-1))
32
31
 
33
32
 
34
- SIGNAL_PREDECESSORS = [FeatureExtractor, HilbertFeatureExtractor]
33
+ SIGNAL_PREDECESSORS = [None, signal_hilbert_preprocessor]
35
34
 
36
35
 
37
36
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
38
37
  @univariate_feature
39
- def signal_mean(x):
38
+ def signal_mean(x, /):
40
39
  return x.mean(axis=-1)
41
40
 
42
41
 
43
42
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
44
43
  @univariate_feature
45
- def signal_variance(x, **kwargs):
44
+ def signal_variance(x, /, **kwargs):
46
45
  return x.var(axis=-1, **kwargs)
47
46
 
48
47
 
49
48
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
50
49
  @univariate_feature
51
- def signal_std(x, **kwargs):
50
+ def signal_std(x, /, **kwargs):
52
51
  return x.std(axis=-1, **kwargs)
53
52
 
54
53
 
55
54
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
56
55
  @univariate_feature
57
- def signal_skewness(x, **kwargs):
56
+ def signal_skewness(x, /, **kwargs):
58
57
  return stats.skew(x, axis=x.ndim - 1, **kwargs)
59
58
 
60
59
 
61
60
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
62
61
  @univariate_feature
63
- def signal_kurtosis(x, **kwargs):
62
+ def signal_kurtosis(x, /, **kwargs):
64
63
  return stats.kurtosis(x, axis=x.ndim - 1, **kwargs)
65
64
 
66
65
 
67
66
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
68
67
  @univariate_feature
69
- def signal_root_mean_square(x):
68
+ def signal_root_mean_square(x, /):
70
69
  return np.sqrt(np.power(x, 2).mean(axis=-1))
71
70
 
72
71
 
73
72
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
74
73
  @univariate_feature
75
- def signal_peak_to_peak(x, **kwargs):
74
+ def signal_peak_to_peak(x, /, **kwargs):
76
75
  return np.ptp(x, axis=-1, **kwargs)
77
76
 
78
77
 
79
78
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
80
79
  @univariate_feature
81
- def signal_quantile(x, q: numbers.Number = 0.5, **kwargs):
80
+ def signal_quantile(x, /, q: numbers.Number = 0.5, **kwargs):
82
81
  return np.quantile(x, q=q, axis=-1, **kwargs)
83
82
 
84
83
 
85
84
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
86
85
  @univariate_feature
87
- def signal_line_length(x):
86
+ def signal_line_length(x, /):
88
87
  return np.abs(np.diff(x, axis=-1)).mean(axis=-1)
89
88
 
90
89
 
91
90
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
92
91
  @univariate_feature
93
- def signal_zero_crossings(x, threshold=1e-15):
92
+ def signal_zero_crossings(x, /, threshold=1e-15):
94
93
  zero_ind = np.logical_and(x > -threshold, x < threshold)
95
94
  zero_cross = np.diff(zero_ind, axis=-1).astype(int).sum(axis=-1)
96
95
  y = x.copy()
@@ -101,13 +100,13 @@ def signal_zero_crossings(x, threshold=1e-15):
101
100
 
102
101
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
103
102
  @univariate_feature
104
- def signal_hjorth_mobility(x):
103
+ def signal_hjorth_mobility(x, /):
105
104
  return np.diff(x, axis=-1).std(axis=-1) / x.std(axis=-1)
106
105
 
107
106
 
108
107
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
109
108
  @univariate_feature
110
- def signal_hjorth_complexity(x):
109
+ def signal_hjorth_complexity(x, /):
111
110
  return (np.diff(x, 2, axis=-1).std(axis=-1) * x.std(axis=-1)) / np.diff(
112
111
  x, axis=-1
113
112
  ).var(axis=-1)
@@ -115,7 +114,7 @@ def signal_hjorth_complexity(x):
115
114
 
116
115
  @FeaturePredecessor(*SIGNAL_PREDECESSORS)
117
116
  @univariate_feature
118
- def signal_decorrelation_time(x, fs=1):
117
+ def signal_decorrelation_time(x, /, fs=1):
119
118
  f = np.fft.fft(x - x.mean(axis=-1, keepdims=True), axis=-1)
120
119
  ac = np.fft.ifft(f.real**2 + f.imag**2, axis=-1)[..., : x.shape[-1] // 2]
121
120
  dct = np.empty(x.shape[:-1])
@@ -3,13 +3,13 @@ import numpy as np
3
3
  from scipy.signal import welch
4
4
 
5
5
  from ..decorators import FeaturePredecessor, univariate_feature
6
- from ..extractors import FeatureExtractor
7
6
  from . import utils
7
+ from .signal import SIGNAL_PREDECESSORS
8
8
 
9
9
  __all__ = [
10
- "SpectralFeatureExtractor",
11
- "NormalizedSpectralFeatureExtractor",
12
- "DBSpectralFeatureExtractor",
10
+ "spectral_preprocessor",
11
+ "spectral_normalized_preprocessor",
12
+ "spectral_db_preprocessor",
13
13
  "spectral_root_total_power",
14
14
  "spectral_moment",
15
15
  "spectral_entropy",
@@ -22,84 +22,80 @@ __all__ = [
22
22
  ]
23
23
 
24
24
 
25
- class SpectralFeatureExtractor(FeatureExtractor):
26
- def preprocess(self, x, **kwargs):
27
- f_min = kwargs.pop("f_min") if "f_min" in kwargs else None
28
- f_max = kwargs.pop("f_max") if "f_max" in kwargs else None
29
- assert "fs" in kwargs
30
- kwargs["axis"] = -1
31
- f, p = welch(x, **kwargs)
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)
36
- return f, p
25
+ @FeaturePredecessor(*SIGNAL_PREDECESSORS)
26
+ def spectral_preprocessor(x, /, **kwargs):
27
+ f_min = kwargs.pop("f_min") if "f_min" in kwargs else None
28
+ f_max = kwargs.pop("f_max") if "f_max" in kwargs else None
29
+ assert "fs" in kwargs
30
+ kwargs["axis"] = -1
31
+ f, p = welch(x, **kwargs)
32
+ f_min, f_max = utils.get_valid_freq_band(kwargs["fs"], x.shape[-1], f_min, f_max)
33
+ f, p = utils.slice_freq_band(f, p, f_min=f_min, f_max=f_max)
34
+ return f, p
37
35
 
38
36
 
39
- @FeaturePredecessor(SpectralFeatureExtractor)
40
- class NormalizedSpectralFeatureExtractor(FeatureExtractor):
41
- def preprocess(self, *x):
42
- return (*x[:-1], x[-1] / x[-1].sum(axis=-1, keepdims=True))
37
+ @FeaturePredecessor(spectral_preprocessor)
38
+ def spectral_normalized_preprocessor(f, p, /):
39
+ return f, p / p.sum(axis=-1, keepdims=True)
43
40
 
44
41
 
45
- @FeaturePredecessor(SpectralFeatureExtractor)
46
- class DBSpectralFeatureExtractor(FeatureExtractor):
47
- def preprocess(self, *x, eps=1e-15):
48
- return (*x[:-1], 10 * np.log10(x[-1] + eps))
42
+ @FeaturePredecessor(spectral_preprocessor)
43
+ def spectral_db_preprocessor(f, p, /, eps=1e-15):
44
+ return f, 10 * np.log10(p + eps)
49
45
 
50
46
 
51
- @FeaturePredecessor(SpectralFeatureExtractor)
47
+ @FeaturePredecessor(spectral_preprocessor)
52
48
  @univariate_feature
53
- def spectral_root_total_power(f, p):
49
+ def spectral_root_total_power(f, p, /):
54
50
  return np.sqrt(p.sum(axis=-1))
55
51
 
56
52
 
57
- @FeaturePredecessor(NormalizedSpectralFeatureExtractor)
53
+ @FeaturePredecessor(spectral_normalized_preprocessor)
58
54
  @univariate_feature
59
- def spectral_moment(f, p):
55
+ def spectral_moment(f, p, /):
60
56
  return np.sum(f * p, axis=-1)
61
57
 
62
58
 
63
- @FeaturePredecessor(SpectralFeatureExtractor)
59
+ @FeaturePredecessor(spectral_preprocessor)
64
60
  @univariate_feature
65
- def spectral_hjorth_activity(f, p):
61
+ def spectral_hjorth_activity(f, p, /):
66
62
  return np.sum(p, axis=-1)
67
63
 
68
64
 
69
- @FeaturePredecessor(NormalizedSpectralFeatureExtractor)
65
+ @FeaturePredecessor(spectral_normalized_preprocessor)
70
66
  @univariate_feature
71
- def spectral_hjorth_mobility(f, p):
67
+ def spectral_hjorth_mobility(f, p, /):
72
68
  return np.sqrt(np.sum(np.power(f, 2) * p, axis=-1))
73
69
 
74
70
 
75
- @FeaturePredecessor(NormalizedSpectralFeatureExtractor)
71
+ @FeaturePredecessor(spectral_normalized_preprocessor)
76
72
  @univariate_feature
77
- def spectral_hjorth_complexity(f, p):
73
+ def spectral_hjorth_complexity(f, p, /):
78
74
  return np.sqrt(np.sum(np.power(f, 4) * p, axis=-1))
79
75
 
80
76
 
81
- @FeaturePredecessor(NormalizedSpectralFeatureExtractor)
77
+ @FeaturePredecessor(spectral_normalized_preprocessor)
82
78
  @univariate_feature
83
- def spectral_entropy(f, p):
79
+ def spectral_entropy(f, p, /):
84
80
  idx = p > 0
85
81
  plogp = np.zeros_like(p)
86
82
  plogp[idx] = p[idx] * np.log(p[idx])
87
83
  return -np.sum(plogp, axis=-1)
88
84
 
89
85
 
90
- @FeaturePredecessor(NormalizedSpectralFeatureExtractor)
86
+ @FeaturePredecessor(spectral_normalized_preprocessor)
91
87
  @univariate_feature
92
88
  @nb.njit(cache=True, fastmath=True)
93
- def spectral_edge(f, p, edge=0.9):
89
+ def spectral_edge(f, p, /, edge=0.9):
94
90
  se = np.empty(p.shape[:-1])
95
91
  for i in np.ndindex(p.shape[:-1]):
96
92
  se[i] = f[np.searchsorted(np.cumsum(p[i]), edge)]
97
93
  return se
98
94
 
99
95
 
100
- @FeaturePredecessor(DBSpectralFeatureExtractor)
96
+ @FeaturePredecessor(spectral_db_preprocessor)
101
97
  @univariate_feature
102
- def spectral_slope(f, p):
98
+ def spectral_slope(f, p, /):
103
99
  log_f = np.vstack((np.log(f), np.ones(f.shape[0]))).T
104
100
  r = np.linalg.lstsq(log_f, p.reshape(-1, p.shape[-1]).T)[0]
105
101
  r = r.reshape(2, *p.shape[:-1])
@@ -107,10 +103,10 @@ def spectral_slope(f, p):
107
103
 
108
104
 
109
105
  @FeaturePredecessor(
110
- SpectralFeatureExtractor,
111
- NormalizedSpectralFeatureExtractor,
112
- DBSpectralFeatureExtractor,
106
+ spectral_preprocessor,
107
+ spectral_normalized_preprocessor,
108
+ spectral_db_preprocessor,
113
109
  )
114
110
  @univariate_feature
115
- def spectral_bands_power(f, p, bands=utils.DEFAULT_FREQ_BANDS):
111
+ def spectral_bands_power(f, p, /, bands=utils.DEFAULT_FREQ_BANDS):
116
112
  return utils.reduce_freq_bands(f, p, bands, np.sum)
@@ -1,5 +1,13 @@
1
1
  import numpy as np
2
2
 
3
+ __all__ = [
4
+ "DEFAULT_FREQ_BANDS",
5
+ "get_valid_freq_band",
6
+ "reduce_freq_bands",
7
+ "slice_freq_band",
8
+ ]
9
+
10
+
3
11
  DEFAULT_FREQ_BANDS = {
4
12
  "delta": (1, 4.5),
5
13
  "theta": (4.5, 8),