py-neuromodulation 0.0.4__py3-none-any.whl → 0.0.6__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 (109) hide show
  1. py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -34
  2. py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +95 -106
  3. py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +107 -119
  4. py_neuromodulation/__init__.py +80 -13
  5. py_neuromodulation/{nm_RMAP.py → analysis/RMAP.py} +496 -531
  6. py_neuromodulation/analysis/__init__.py +4 -0
  7. py_neuromodulation/{nm_decode.py → analysis/decode.py} +918 -992
  8. py_neuromodulation/{nm_analysis.py → analysis/feature_reader.py} +994 -1074
  9. py_neuromodulation/{nm_plots.py → analysis/plots.py} +627 -612
  10. py_neuromodulation/{nm_stats.py → analysis/stats.py} +458 -480
  11. py_neuromodulation/data/README +6 -6
  12. py_neuromodulation/data/dataset_description.json +8 -8
  13. py_neuromodulation/data/participants.json +32 -32
  14. py_neuromodulation/data/participants.tsv +2 -2
  15. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -5
  16. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -11
  17. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -11
  18. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -18
  19. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -35
  20. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -13
  21. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -2
  22. py_neuromodulation/default_settings.yaml +241 -0
  23. py_neuromodulation/features/__init__.py +31 -0
  24. py_neuromodulation/features/bandpower.py +165 -0
  25. py_neuromodulation/features/bispectra.py +157 -0
  26. py_neuromodulation/features/bursts.py +297 -0
  27. py_neuromodulation/features/coherence.py +255 -0
  28. py_neuromodulation/features/feature_processor.py +121 -0
  29. py_neuromodulation/features/fooof.py +142 -0
  30. py_neuromodulation/features/hjorth_raw.py +57 -0
  31. py_neuromodulation/features/linelength.py +21 -0
  32. py_neuromodulation/features/mne_connectivity.py +148 -0
  33. py_neuromodulation/features/nolds.py +94 -0
  34. py_neuromodulation/features/oscillatory.py +249 -0
  35. py_neuromodulation/features/sharpwaves.py +432 -0
  36. py_neuromodulation/filter/__init__.py +3 -0
  37. py_neuromodulation/filter/kalman_filter.py +67 -0
  38. py_neuromodulation/filter/kalman_filter_external.py +1890 -0
  39. py_neuromodulation/filter/mne_filter.py +128 -0
  40. py_neuromodulation/filter/notch_filter.py +93 -0
  41. py_neuromodulation/grid_cortex.tsv +40 -40
  42. py_neuromodulation/liblsl/libpugixml.so.1.12 +0 -0
  43. py_neuromodulation/liblsl/linux/bionic_amd64/liblsl.1.16.2.so +0 -0
  44. py_neuromodulation/liblsl/linux/bookworm_amd64/liblsl.1.16.2.so +0 -0
  45. py_neuromodulation/liblsl/linux/focal_amd46/liblsl.1.16.2.so +0 -0
  46. py_neuromodulation/liblsl/linux/jammy_amd64/liblsl.1.16.2.so +0 -0
  47. py_neuromodulation/liblsl/linux/jammy_x86/liblsl.1.16.2.so +0 -0
  48. py_neuromodulation/liblsl/linux/noble_amd64/liblsl.1.16.2.so +0 -0
  49. py_neuromodulation/liblsl/macos/amd64/liblsl.1.16.2.dylib +0 -0
  50. py_neuromodulation/liblsl/macos/arm64/liblsl.1.16.0.dylib +0 -0
  51. py_neuromodulation/liblsl/windows/amd64/liblsl.1.16.2.dll +0 -0
  52. py_neuromodulation/liblsl/windows/x86/liblsl.1.16.2.dll +0 -0
  53. py_neuromodulation/processing/__init__.py +10 -0
  54. py_neuromodulation/{nm_artifacts.py → processing/artifacts.py} +29 -25
  55. py_neuromodulation/processing/data_preprocessor.py +77 -0
  56. py_neuromodulation/processing/filter_preprocessing.py +78 -0
  57. py_neuromodulation/processing/normalization.py +175 -0
  58. py_neuromodulation/{nm_projection.py → processing/projection.py} +370 -394
  59. py_neuromodulation/{nm_rereference.py → processing/rereference.py} +97 -95
  60. py_neuromodulation/{nm_resample.py → processing/resample.py} +56 -50
  61. py_neuromodulation/stream/__init__.py +3 -0
  62. py_neuromodulation/stream/data_processor.py +325 -0
  63. py_neuromodulation/stream/generator.py +53 -0
  64. py_neuromodulation/stream/mnelsl_player.py +94 -0
  65. py_neuromodulation/stream/mnelsl_stream.py +120 -0
  66. py_neuromodulation/stream/settings.py +292 -0
  67. py_neuromodulation/stream/stream.py +427 -0
  68. py_neuromodulation/utils/__init__.py +2 -0
  69. py_neuromodulation/{nm_define_nmchannels.py → utils/channels.py} +305 -302
  70. py_neuromodulation/utils/database.py +149 -0
  71. py_neuromodulation/utils/io.py +378 -0
  72. py_neuromodulation/utils/keyboard.py +52 -0
  73. py_neuromodulation/utils/logging.py +66 -0
  74. py_neuromodulation/utils/types.py +251 -0
  75. {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/METADATA +28 -33
  76. py_neuromodulation-0.0.6.dist-info/RECORD +89 -0
  77. {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/WHEEL +1 -1
  78. {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/licenses/LICENSE +21 -21
  79. py_neuromodulation/FieldTrip.py +0 -589
  80. py_neuromodulation/_write_example_dataset_helper.py +0 -65
  81. py_neuromodulation/nm_EpochStream.py +0 -92
  82. py_neuromodulation/nm_IO.py +0 -417
  83. py_neuromodulation/nm_across_patient_decoding.py +0 -927
  84. py_neuromodulation/nm_bispectra.py +0 -168
  85. py_neuromodulation/nm_bursts.py +0 -198
  86. py_neuromodulation/nm_coherence.py +0 -205
  87. py_neuromodulation/nm_cohortwrapper.py +0 -435
  88. py_neuromodulation/nm_eval_timing.py +0 -239
  89. py_neuromodulation/nm_features.py +0 -116
  90. py_neuromodulation/nm_features_abc.py +0 -39
  91. py_neuromodulation/nm_filter.py +0 -219
  92. py_neuromodulation/nm_filter_preprocessing.py +0 -91
  93. py_neuromodulation/nm_fooof.py +0 -159
  94. py_neuromodulation/nm_generator.py +0 -37
  95. py_neuromodulation/nm_hjorth_raw.py +0 -73
  96. py_neuromodulation/nm_kalmanfilter.py +0 -58
  97. py_neuromodulation/nm_linelength.py +0 -33
  98. py_neuromodulation/nm_mne_connectivity.py +0 -112
  99. py_neuromodulation/nm_nolds.py +0 -93
  100. py_neuromodulation/nm_normalization.py +0 -214
  101. py_neuromodulation/nm_oscillatory.py +0 -448
  102. py_neuromodulation/nm_run_analysis.py +0 -435
  103. py_neuromodulation/nm_settings.json +0 -338
  104. py_neuromodulation/nm_settings.py +0 -68
  105. py_neuromodulation/nm_sharpwaves.py +0 -401
  106. py_neuromodulation/nm_stream_abc.py +0 -218
  107. py_neuromodulation/nm_stream_offline.py +0 -359
  108. py_neuromodulation/utils/_logging.py +0 -24
  109. py_neuromodulation-0.0.4.dist-info/RECORD +0 -72
@@ -1,116 +0,0 @@
1
- import numpy as np
2
-
3
- from py_neuromodulation import (
4
- nm_hjorth_raw,
5
- nm_mne_connectivity,
6
- nm_sharpwaves,
7
- nm_coherence,
8
- nm_fooof,
9
- nm_nolds,
10
- nm_features_abc,
11
- nm_oscillatory,
12
- nm_bursts,
13
- nm_linelength,
14
- nm_bispectra,
15
- )
16
-
17
-
18
- class Features:
19
- """Class for calculating features."""
20
-
21
- features: list[nm_features_abc.Feature] = []
22
-
23
- def __init__(
24
- self, s: dict, ch_names: list[str], sfreq: int | float
25
- ) -> None:
26
- """_summary_
27
-
28
- Parameters
29
- ----------
30
- s : dict
31
- _description_
32
- ch_names : list[str]
33
- _description_
34
- sfreq : int | float
35
- _description_
36
-
37
- Raises
38
- ------
39
- ValueError
40
- _description_
41
- """
42
-
43
- self.features = []
44
-
45
- for feature in s["features"]:
46
- if s["features"][feature] is False:
47
- continue
48
- match feature:
49
- case "raw_hjorth":
50
- FeatureClass = nm_hjorth_raw.Hjorth
51
- case "return_raw":
52
- FeatureClass = nm_hjorth_raw.Raw
53
- case "bandpass_filter":
54
- FeatureClass = nm_oscillatory.BandPower
55
- case "stft":
56
- FeatureClass = nm_oscillatory.STFT
57
- case "fft":
58
- FeatureClass = nm_oscillatory.FFT
59
- case "welch":
60
- FeatureClass = nm_oscillatory.Welch
61
- case "sharpwave_analysis":
62
- FeatureClass = nm_sharpwaves.SharpwaveAnalyzer
63
- case "fooof":
64
- FeatureClass = nm_fooof.FooofAnalyzer
65
- case "nolds":
66
- FeatureClass = nm_nolds.Nolds
67
- case "coherence":
68
- FeatureClass = nm_coherence.NM_Coherence
69
- case "bursts":
70
- FeatureClass = nm_bursts.Burst
71
- case "linelength":
72
- FeatureClass = nm_linelength.LineLength
73
- case "mne_connectivity":
74
- FeatureClass = nm_mne_connectivity.MNEConnectivity
75
- case "bispectrum":
76
- FeatureClass = nm_bispectra.Bispectra
77
- case _:
78
- raise ValueError(f"Unknown feature found. Got: {feature}.")
79
-
80
- FeatureClass.test_settings(s, ch_names, sfreq)
81
- f_obj = FeatureClass(s, ch_names, sfreq)
82
- self.features.append(f_obj)
83
-
84
- def register_new_feature(self, feature: nm_features_abc.Feature) -> None:
85
- """Register new feature.
86
-
87
- Parameters
88
- ----------
89
- feature : nm_features_abc.Feature
90
- New feature to add to feature list
91
- """
92
- self.features.append(feature)
93
-
94
- def estimate_features(self, data: np.ndarray) -> dict:
95
- """Calculate features, as defined in settings.json
96
- Features are based on bandpower, raw Hjorth parameters and sharp wave
97
- characteristics.
98
-
99
- Parameters
100
- ----------
101
- data (np array) : (channels, time)
102
-
103
- Returns
104
- -------
105
- dat (dict): naming convention : channel_method_feature_(f_band)
106
- """
107
-
108
- features_compute = {}
109
-
110
- for feature in self.features:
111
- features_compute = feature.calc_feature(
112
- data,
113
- features_compute,
114
- )
115
-
116
- return features_compute
@@ -1,39 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- import numpy as np
3
- from typing import Iterable
4
-
5
-
6
- class Feature(ABC):
7
- @abstractmethod
8
- def __init__(
9
- self, settings: dict, ch_names: Iterable[str], sfreq: int | float
10
- ) -> None:
11
- pass
12
-
13
- @staticmethod
14
- @abstractmethod
15
- def test_settings(
16
- settings: dict,
17
- ch_names: Iterable[str],
18
- sfreq: int | float,
19
- ):
20
- """Method to check passed settings"""
21
- pass
22
-
23
- @abstractmethod
24
- def calc_feature(self, data: np.array, features_compute: dict) -> dict:
25
- """
26
- Feature calculation method. Each method needs to loop through all channels
27
-
28
- Parameters
29
- ----------
30
- data : np.array
31
- (channels, time)
32
- features_compute : dict
33
- ch_names : Iterable[str]
34
-
35
- Returns
36
- -------
37
- dict
38
- """
39
- pass
@@ -1,219 +0,0 @@
1
- """Module for filter functionality."""
2
-
3
- import logging
4
-
5
- logger = logging.getLogger("PynmLogger")
6
-
7
- import mne
8
- from mne.filter import _overlap_add_filter
9
- import numpy as np
10
-
11
-
12
- class MNEFilter:
13
- """mne.filter wrapper
14
-
15
- This class stores for given frequency band ranges the filter
16
- coefficients with length "filter_len".
17
- The filters can then be used sequentially for band power estimation with
18
- apply_filter().
19
- Note that this filter can be a bandpass, bandstop, lowpass, or highpass filter
20
- depending on the frequency ranges given (see further details in mne.filter.create_filter).
21
-
22
- Parameters
23
- ----------
24
- f_ranges : list of lists
25
- Frequency ranges. Inner lists must be of length 2.
26
- sfreq : int | float
27
- Sampling frequency.
28
- filter_length : str, optional
29
- Filter length. Human readable (e.g. "1000ms", "1s"), by default "999ms"
30
- l_trans_bandwidth : int | float | str, optional
31
- Length of the lower transition band or "auto", by default 4
32
- h_trans_bandwidth : int | float | str, optional
33
- Length of the higher transition band or "auto", by default 4
34
- verbose : bool | None, optional
35
- Verbosity level, by default None
36
-
37
- Attributes
38
- ----------
39
- filter_bank: np.ndarray shape (n,)
40
- Factor to upsample by.
41
- """
42
-
43
- def __init__(
44
- self,
45
- f_ranges: list[list[int | float | None]] | list[int | float | None],
46
- sfreq: int | float,
47
- filter_length: str | float = "999ms",
48
- l_trans_bandwidth: int | float | str = 4,
49
- h_trans_bandwidth: int | float | str = 4,
50
- verbose: bool | int | str | None = None,
51
- ) -> None:
52
- filter_bank = []
53
- # mne create_filter function only accepts str and int for filter_length
54
- if isinstance(filter_length, float):
55
- filter_length = int(filter_length)
56
-
57
- if not isinstance(f_ranges[0], list):
58
- f_ranges = [f_ranges]
59
-
60
- for f_range in f_ranges:
61
- try:
62
- filt = mne.filter.create_filter(
63
- None,
64
- sfreq,
65
- l_freq=f_range[0],
66
- h_freq=f_range[1],
67
- fir_design="firwin",
68
- l_trans_bandwidth=l_trans_bandwidth, # type: ignore
69
- h_trans_bandwidth=h_trans_bandwidth, # type: ignore
70
- filter_length=filter_length, # type: ignore
71
- verbose=verbose,
72
- )
73
- except:
74
- filt = mne.filter.create_filter(
75
- None,
76
- sfreq,
77
- l_freq=f_range[0],
78
- h_freq=f_range[1],
79
- fir_design="firwin",
80
- verbose=verbose,
81
- # filter_length=filter_length,
82
- )
83
- filter_bank.append(filt)
84
- self.filter_bank = np.vstack(filter_bank)
85
-
86
- def filter_data(self, data: np.ndarray) -> np.ndarray:
87
- """Apply previously calculated (bandpass) filters to data.
88
-
89
- Parameters
90
- ----------
91
- data : np.ndarray (n_samples, ) or (n_channels, n_samples)
92
- Data to be filtered
93
- filter_bank : np.ndarray, shape (n_fbands, filter_len)
94
- Output of calc_bandpass_filters.
95
-
96
- Returns
97
- -------
98
- np.ndarray, shape (n_channels, n_fbands, n_samples)
99
- Filtered data.
100
-
101
- """
102
- if data.ndim > 2:
103
- raise ValueError(
104
- f"Data must have one or two dimensions. Got:"
105
- f" {data.ndim} dimensions."
106
- )
107
- if data.ndim == 1:
108
- data = np.expand_dims(data, axis=0)
109
-
110
- filtered = np.array(
111
- [
112
- [
113
- np.convolve(filt, chan, mode="same")
114
- for filt in self.filter_bank
115
- ]
116
- for chan in data
117
- ]
118
- )
119
-
120
- # ensure here that the output dimension matches the input dimension
121
- if data.shape[1] != filtered.shape[-1]:
122
- # select the middle part of the filtered data
123
- middle_index = filtered.shape[-1] // 2
124
- filtered = filtered[
125
- :,
126
- :,
127
- middle_index
128
- - data.shape[1] // 2 : middle_index
129
- + data.shape[1] // 2,
130
- ]
131
-
132
- return filtered
133
-
134
-
135
- class NotchFilter:
136
- def __init__(
137
- self,
138
- sfreq: int | float,
139
- line_noise: int | float | None = None,
140
- freqs: np.ndarray | None = None,
141
- notch_widths: int | np.ndarray | None = 3,
142
- trans_bandwidth: int = 6.8,
143
- ) -> None:
144
- if line_noise is None and freqs is None:
145
- raise ValueError(
146
- "Either line_noise or freqs must be defined if notch_filter is"
147
- "activated."
148
- )
149
- if freqs is None:
150
- freqs = np.arange(line_noise, sfreq / 2, line_noise, dtype=int)
151
-
152
- if freqs.size > 0:
153
- if freqs[-1] >= sfreq / 2:
154
- freqs = freqs[:-1]
155
-
156
- # Code is copied from filter.py notch_filter
157
- if freqs.size == 0:
158
- self.filter_bank = None
159
- logger.warning(
160
- "WARNING: notch_filter is activated but data is not being"
161
- f" filtered. This may be due to a low sampling frequency or"
162
- f" incorrect specifications. Make sure your settings are"
163
- f" correct. Got: {sfreq = }, {line_noise = }, {freqs = }."
164
- )
165
- return
166
-
167
- filter_length = int(sfreq - 1)
168
- if notch_widths is None:
169
- notch_widths = freqs / 200.0
170
- elif np.any(notch_widths < 0):
171
- raise ValueError("notch_widths must be >= 0")
172
- else:
173
- notch_widths = np.atleast_1d(notch_widths)
174
- if len(notch_widths) == 1:
175
- notch_widths = notch_widths[0] * np.ones_like(freqs)
176
- elif len(notch_widths) != len(freqs):
177
- raise ValueError(
178
- "notch_widths must be None, scalar, or the "
179
- "same length as freqs"
180
- )
181
-
182
- # Speed this up by computing the fourier coefficients once
183
- tb_half = trans_bandwidth / 2.0
184
- lows = [
185
- freq - nw / 2.0 - tb_half for freq, nw in zip(freqs, notch_widths)
186
- ]
187
- highs = [
188
- freq + nw / 2.0 + tb_half for freq, nw in zip(freqs, notch_widths)
189
- ]
190
-
191
- self.filter_bank = mne.filter.create_filter(
192
- data=None,
193
- sfreq=sfreq,
194
- l_freq=highs,
195
- h_freq=lows,
196
- filter_length=filter_length, # type: ignore
197
- l_trans_bandwidth=tb_half, # type: ignore
198
- h_trans_bandwidth=tb_half, # type: ignore
199
- method="fir",
200
- iir_params=None,
201
- phase="zero",
202
- fir_window="hamming",
203
- fir_design="firwin",
204
- verbose=False,
205
- )
206
-
207
- def process(self, data: np.ndarray) -> np.ndarray:
208
- if self.filter_bank is None:
209
- return data
210
- return _overlap_add_filter(
211
- x=data,
212
- h=self.filter_bank,
213
- n_fft=None,
214
- phase="zero",
215
- picks=None,
216
- n_jobs=1,
217
- copy=True,
218
- pad="reflect_limited",
219
- )
@@ -1,91 +0,0 @@
1
- import numpy as np
2
-
3
- from py_neuromodulation import nm_filter
4
-
5
-
6
- class PreprocessingFilter:
7
-
8
- def __init__(self, settings: dict, sfreq: int | float) -> None:
9
- self.s = settings
10
- self.sfreq = sfreq
11
- self.filters = []
12
-
13
- if self.s["preprocessing_filter"]["bandstop_filter"] is True:
14
- self.filters.append(
15
- nm_filter.MNEFilter(
16
- f_ranges=[
17
- self.s["preprocessing_filter"][
18
- "bandstop_filter_settings"
19
- ]["frequency_high_hz"],
20
- self.s["preprocessing_filter"][
21
- "bandstop_filter_settings"
22
- ]["frequency_low_hz"],
23
- ],
24
- sfreq=self.sfreq,
25
- filter_length=self.sfreq - 1,
26
- verbose=False,
27
- )
28
- )
29
-
30
- if self.s["preprocessing_filter"]["bandpass_filter"] is True:
31
- self.filters.append(
32
- nm_filter.MNEFilter(
33
- f_ranges=[
34
- self.s["preprocessing_filter"][
35
- "bandpass_filter_settings"
36
- ]["frequency_low_hz"],
37
- self.s["preprocessing_filter"][
38
- "bandpass_filter_settings"
39
- ]["frequency_high_hz"],
40
- ],
41
- sfreq=self.sfreq,
42
- filter_length=self.sfreq - 1,
43
- verbose=False,
44
- )
45
- )
46
- if self.s["preprocessing_filter"]["lowpass_filter"] is True:
47
- self.filters.append(
48
- nm_filter.MNEFilter(
49
- f_ranges=[
50
- None,
51
- self.s["preprocessing_filter"][
52
- "lowpass_filter_settings"
53
- ]["frequency_cutoff_hz"],
54
- ],
55
- sfreq=self.sfreq,
56
- filter_length=self.sfreq - 1,
57
- verbose=False,
58
- )
59
- )
60
- if self.s["preprocessing_filter"]["highpass_filter"] is True:
61
- self.filters.append(
62
- nm_filter.MNEFilter(
63
- f_ranges=[
64
- self.s["preprocessing_filter"][
65
- "highpass_filter_settings"
66
- ]["frequency_cutoff_hz"],
67
- None,
68
- ],
69
- sfreq=self.sfreq,
70
- filter_length=self.sfreq - 1,
71
- verbose=False,
72
- )
73
- )
74
-
75
- def process(self, data: np.ndarray) -> np.ndarray:
76
- """Preprocess data according to the initialized list of PreprocessingFilter objects
77
-
78
- Args:
79
- data (numpy ndarray) :
80
- shape(n_channels, n_samples) - data to be preprocessed.
81
-
82
- Returns:
83
- preprocessed_data (numpy ndarray):
84
- shape(n_channels, n_samples) - preprocessed data
85
- """
86
-
87
- for filter in self.filters:
88
- data = filter.filter_data(
89
- data if len(data.shape) == 2 else data[:, 0, :]
90
- )
91
- return data if len(data.shape) == 2 else data[:, 0, :]
@@ -1,159 +0,0 @@
1
- import logging
2
- from typing import Iterable
3
-
4
- import numpy as np
5
- from fooof import FOOOF
6
- from scipy import fft
7
-
8
- from py_neuromodulation import nm_features_abc
9
-
10
-
11
- class FooofAnalyzer(nm_features_abc.Feature):
12
- def __init__(
13
- self, settings: dict, ch_names: Iterable[str], sfreq: float
14
- ) -> None:
15
- self.settings_fooof = settings["fooof"]
16
- self.sfreq = sfreq
17
- self.ch_names = ch_names
18
-
19
- self.freq_range = self.settings_fooof["freq_range_hz"]
20
- self.ap_mode = "knee" if self.settings_fooof["knee"] else "fixed"
21
- self.max_n_peaks = self.settings_fooof["max_n_peaks"]
22
-
23
- self.num_samples = int(
24
- self.settings_fooof["windowlength_ms"] * sfreq / 1000
25
- )
26
-
27
- self.f_vec = np.arange(0, int(self.num_samples / 2) + 1, 1)
28
-
29
- def test_settings(
30
- s: dict,
31
- ch_names: Iterable[str],
32
- sfreq: int | float,
33
- ):
34
- assert isinstance(s["fooof"]["aperiodic"]["exponent"], bool)
35
- assert isinstance(s["fooof"]["aperiodic"]["offset"], bool)
36
- assert isinstance(s["fooof"]["aperiodic"]["knee"], bool)
37
- assert isinstance(s["fooof"]["periodic"]["center_frequency"], bool)
38
- assert isinstance(s["fooof"]["periodic"]["band_width"], bool)
39
- assert isinstance(s["fooof"]["periodic"]["height_over_ap"], bool)
40
- assert isinstance(s["fooof"]["knee"], bool)
41
- assert isinstance(s["fooof"]["windowlength_ms"], (int, float))
42
- assert (
43
- s["fooof"]["windowlength_ms"] <= s["segment_length_features_ms"]
44
- ), (
45
- "fooof windowlength_ms needs to be smaller equal than segment_length_features_ms "
46
- f"got windowlength_ms: {s['fooof']['windowlength_ms']} and {s['segment_length_features_ms']}"
47
- )
48
-
49
- assert (
50
- s["fooof"]["freq_range_hz"][0] < sfreq
51
- and s["fooof"]["freq_range_hz"][1] < sfreq
52
- ), f"fooof frequency range needs to be below sfreq, got {s['fooof']['freq_range_hz']}"
53
-
54
- def _get_spectrum(self, data: np.array):
55
- """return absolute value fft spectrum"""
56
-
57
- data = data[-self.num_samples :]
58
- Z = np.abs(fft.rfft(data))
59
-
60
- return Z
61
-
62
- def calc_feature(
63
- self,
64
- data: np.array,
65
- features_compute: dict,
66
- ) -> dict:
67
- for ch_idx, ch_name in enumerate(self.ch_names):
68
- spectrum = self._get_spectrum(data[ch_idx, :])
69
-
70
- try:
71
- fm = FOOOF(
72
- aperiodic_mode=self.ap_mode,
73
- peak_width_limits=self.settings_fooof["peak_width_limits"],
74
- max_n_peaks=self.settings_fooof["max_n_peaks"],
75
- min_peak_height=self.settings_fooof["min_peak_height"],
76
- peak_threshold=self.settings_fooof["peak_threshold"],
77
- verbose=False,
78
- )
79
- fm.fit(self.f_vec, spectrum, self.freq_range)
80
- except Exception as e:
81
- logging.critical(e, exc_info=True)
82
-
83
- if fm.fooofed_spectrum_ is None:
84
- FIT_PASSED = False
85
- else:
86
- FIT_PASSED = True
87
-
88
- if self.settings_fooof["aperiodic"]["exponent"]:
89
- features_compute[f"{ch_name}_fooof_a_exp"] = (
90
- np.nan_to_num(fm.get_params("aperiodic_params", "exponent"))
91
- if FIT_PASSED is True
92
- else None
93
- )
94
-
95
- if self.settings_fooof["aperiodic"]["offset"]:
96
- features_compute[f"{ch_name}_fooof_a_offset"] = (
97
- np.nan_to_num(fm.get_params("aperiodic_params", "offset"))
98
- if FIT_PASSED is True
99
- else None
100
- )
101
-
102
- if self.settings_fooof["aperiodic"]["knee"]:
103
- if FIT_PASSED is False:
104
- knee_freq = None
105
- else:
106
- if fm.get_params("aperiodic_params", "exponent") != 0:
107
- knee_fooof = fm.get_params("aperiodic_params", "knee")
108
- knee_freq = np.nan_to_num(
109
- knee_fooof
110
- ** (
111
- 1
112
- / fm.get_params("aperiodic_params", "exponent")
113
- )
114
- )
115
- else:
116
- knee_freq = None
117
-
118
- features_compute[f"{ch_name}_fooof_a_knee_frequency"] = (
119
- knee_freq
120
- )
121
-
122
- peaks_bw = (
123
- fm.get_params("peak_params", "BW")
124
- if FIT_PASSED is True
125
- else None
126
- )
127
- peaks_cf = (
128
- fm.get_params("peak_params", "CF")
129
- if FIT_PASSED is True
130
- else None
131
- )
132
- peaks_pw = (
133
- fm.get_params("peak_params", "PW")
134
- if FIT_PASSED is True
135
- else None
136
- )
137
-
138
- if type(peaks_bw) is np.float64 or peaks_bw is None:
139
- peaks_bw = [peaks_bw]
140
- peaks_cf = [peaks_cf]
141
- peaks_pw = [peaks_pw]
142
-
143
- for peak_idx in range(self.max_n_peaks):
144
- if self.settings_fooof["periodic"]["band_width"]:
145
- features_compute[f"{ch_name}_fooof_p_{peak_idx}_bw"] = (
146
- peaks_bw[peak_idx] if peak_idx < len(peaks_bw) else None
147
- )
148
-
149
- if self.settings_fooof["periodic"]["center_frequency"]:
150
- features_compute[f"{ch_name}_fooof_p_{peak_idx}_cf"] = (
151
- peaks_cf[peak_idx] if peak_idx < len(peaks_bw) else None
152
- )
153
-
154
- if self.settings_fooof["periodic"]["height_over_ap"]:
155
- features_compute[f"{ch_name}_fooof_p_{peak_idx}_pw"] = (
156
- peaks_pw[peak_idx] if peak_idx < len(peaks_bw) else None
157
- )
158
-
159
- return features_compute
@@ -1,37 +0,0 @@
1
- from typing import Iterator
2
-
3
- import numpy as np
4
-
5
-
6
- def raw_data_generator(
7
- data: np.ndarray,
8
- settings: dict,
9
- sfreq: int,
10
- ) -> Iterator[np.ndarray]:
11
- """
12
- This generator function mimics online data acquisition.
13
- The data are iteratively sampled with sfreq_new.
14
- Arguments
15
- ---------
16
- ieeg_raw (np array): shape (channels, time)
17
- sfreq: int
18
- sfreq_new: int
19
- offset_time: int | float
20
- Returns
21
- -------
22
- np.array: new batch for run function of full segment length shape
23
- """
24
- sfreq_new = settings["sampling_rate_features_hz"]
25
- offset_time = settings["segment_length_features_ms"]
26
- offset_start = offset_time / 1000 * sfreq
27
-
28
- ratio_samples_features = sfreq / sfreq_new
29
-
30
- ratio_counter = 0
31
- for cnt in range(data.shape[1]+1): # shape + 1 guarantees that the last sample is also included
32
-
33
- if (cnt - offset_start) >= ratio_samples_features * ratio_counter:
34
-
35
- ratio_counter += 1
36
-
37
- yield data[:, np.floor(cnt-offset_start).astype(int) : cnt]