py-neuromodulation 0.0.5__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.
- py_neuromodulation/__init__.py +16 -10
- py_neuromodulation/{nm_RMAP.py → analysis/RMAP.py} +2 -2
- py_neuromodulation/analysis/__init__.py +4 -0
- py_neuromodulation/{nm_decode.py → analysis/decode.py} +4 -4
- py_neuromodulation/{nm_analysis.py → analysis/feature_reader.py} +21 -20
- py_neuromodulation/{nm_plots.py → analysis/plots.py} +54 -12
- py_neuromodulation/{nm_stats.py → analysis/stats.py} +2 -8
- py_neuromodulation/{nm_settings.yaml → default_settings.yaml} +6 -9
- py_neuromodulation/features/__init__.py +31 -0
- py_neuromodulation/features/bandpower.py +165 -0
- py_neuromodulation/{nm_bispectra.py → features/bispectra.py} +8 -5
- py_neuromodulation/{nm_bursts.py → features/bursts.py} +14 -9
- py_neuromodulation/{nm_coherence.py → features/coherence.py} +17 -13
- py_neuromodulation/{nm_features.py → features/feature_processor.py} +30 -53
- py_neuromodulation/{nm_fooof.py → features/fooof.py} +11 -8
- py_neuromodulation/{nm_hjorth_raw.py → features/hjorth_raw.py} +10 -5
- py_neuromodulation/{nm_linelength.py → features/linelength.py} +1 -1
- py_neuromodulation/{nm_mne_connectivity.py → features/mne_connectivity.py} +5 -6
- py_neuromodulation/{nm_nolds.py → features/nolds.py} +5 -7
- py_neuromodulation/{nm_oscillatory.py → features/oscillatory.py} +7 -181
- py_neuromodulation/{nm_sharpwaves.py → features/sharpwaves.py} +13 -4
- py_neuromodulation/filter/__init__.py +3 -0
- py_neuromodulation/{nm_kalmanfilter.py → filter/kalman_filter.py} +67 -71
- py_neuromodulation/filter/kalman_filter_external.py +1890 -0
- py_neuromodulation/{nm_filter.py → filter/mne_filter.py} +128 -219
- py_neuromodulation/filter/notch_filter.py +93 -0
- py_neuromodulation/processing/__init__.py +10 -0
- py_neuromodulation/{nm_artifacts.py → processing/artifacts.py} +2 -3
- py_neuromodulation/{nm_preprocessing.py → processing/data_preprocessor.py} +19 -25
- py_neuromodulation/{nm_filter_preprocessing.py → processing/filter_preprocessing.py} +3 -4
- py_neuromodulation/{nm_normalization.py → processing/normalization.py} +9 -7
- py_neuromodulation/{nm_projection.py → processing/projection.py} +14 -14
- py_neuromodulation/{nm_rereference.py → processing/rereference.py} +13 -13
- py_neuromodulation/{nm_resample.py → processing/resample.py} +1 -4
- py_neuromodulation/stream/__init__.py +3 -0
- py_neuromodulation/{nm_run_analysis.py → stream/data_processor.py} +42 -42
- py_neuromodulation/stream/generator.py +53 -0
- py_neuromodulation/{nm_mnelsl_generator.py → stream/mnelsl_player.py} +10 -6
- py_neuromodulation/{nm_mnelsl_stream.py → stream/mnelsl_stream.py} +13 -9
- py_neuromodulation/{nm_settings.py → stream/settings.py} +27 -24
- py_neuromodulation/{nm_stream.py → stream/stream.py} +217 -188
- py_neuromodulation/utils/__init__.py +2 -0
- py_neuromodulation/{nm_define_nmchannels.py → utils/channels.py} +14 -9
- py_neuromodulation/{nm_database.py → utils/database.py} +2 -2
- py_neuromodulation/{nm_IO.py → utils/io.py} +42 -77
- py_neuromodulation/utils/keyboard.py +52 -0
- py_neuromodulation/{nm_logger.py → utils/logging.py} +3 -3
- py_neuromodulation/{nm_types.py → utils/types.py} +72 -14
- {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.6.dist-info}/METADATA +3 -11
- py_neuromodulation-0.0.6.dist-info/RECORD +89 -0
- py_neuromodulation/FieldTrip.py +0 -589
- py_neuromodulation/_write_example_dataset_helper.py +0 -83
- py_neuromodulation/nm_generator.py +0 -45
- py_neuromodulation/nm_stream_abc.py +0 -166
- py_neuromodulation-0.0.5.dist-info/RECORD +0 -83
- {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.6.dist-info}/WHEEL +0 -0
- {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
import pandas as pd
|
|
3
2
|
from pydantic import Field
|
|
4
|
-
from py_neuromodulation.
|
|
3
|
+
from py_neuromodulation.utils.types import NMBaseModel
|
|
5
4
|
from typing import TYPE_CHECKING
|
|
6
5
|
|
|
7
6
|
if TYPE_CHECKING:
|
|
8
|
-
from py_neuromodulation
|
|
7
|
+
from py_neuromodulation import NMSettings
|
|
8
|
+
import pandas as pd
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class ProjectionSettings(NMBaseModel):
|
|
@@ -16,16 +16,16 @@ class Projection:
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
18
|
settings: "NMSettings",
|
|
19
|
-
grid_cortex: pd.DataFrame,
|
|
20
|
-
grid_subcortex: pd.DataFrame,
|
|
19
|
+
grid_cortex: "pd.DataFrame",
|
|
20
|
+
grid_subcortex: "pd.DataFrame",
|
|
21
21
|
coords: dict,
|
|
22
|
-
|
|
22
|
+
channels: "pd.DataFrame",
|
|
23
23
|
plot_projection: bool = False,
|
|
24
24
|
) -> None:
|
|
25
25
|
self.grid_cortex = grid_cortex
|
|
26
26
|
self.grid_subcortex = grid_subcortex
|
|
27
27
|
self.coords = coords
|
|
28
|
-
self.
|
|
28
|
+
self.channels = channels
|
|
29
29
|
self.project_cortex = settings.postprocessing.project_cortex
|
|
30
30
|
self.project_subcortex = settings.postprocessing.project_subcortex
|
|
31
31
|
self.max_dist_cortex = settings.project_cortex_settings.max_dist_mm
|
|
@@ -78,7 +78,7 @@ class Projection:
|
|
|
78
78
|
)[0]
|
|
79
79
|
|
|
80
80
|
if plot_projection:
|
|
81
|
-
from py_neuromodulation.
|
|
81
|
+
from py_neuromodulation.analysis.plots import NM_Plot
|
|
82
82
|
|
|
83
83
|
nmplotter = NM_Plot(
|
|
84
84
|
ecog_strip=self.ecog_strip,
|
|
@@ -90,7 +90,7 @@ class Projection:
|
|
|
90
90
|
nmplotter.plot_cortex()
|
|
91
91
|
|
|
92
92
|
def remove_not_used_ch_from_coords(self):
|
|
93
|
-
ch_not_used = self.
|
|
93
|
+
ch_not_used = self.channels.query('(used==0) or (status=="bad")').name
|
|
94
94
|
if len(ch_not_used) > 0:
|
|
95
95
|
for ch in ch_not_used:
|
|
96
96
|
for key_ in self.coords:
|
|
@@ -196,7 +196,7 @@ class Projection:
|
|
|
196
196
|
"""Initialize channel names via nm_channel new_name column"""
|
|
197
197
|
|
|
198
198
|
if self.project_cortex:
|
|
199
|
-
self.ecog_channels = self.
|
|
199
|
+
self.ecog_channels = self.channels.query(
|
|
200
200
|
'(type=="ecog") and (used == 1) and (status=="good")'
|
|
201
201
|
).name.to_list()
|
|
202
202
|
|
|
@@ -206,11 +206,11 @@ class Projection:
|
|
|
206
206
|
self.ecog_channels.remove(ecog_channel)
|
|
207
207
|
# write ecog_channels to be new_name
|
|
208
208
|
self.ecog_channels = list(
|
|
209
|
-
self.
|
|
209
|
+
self.channels.query("name == @self.ecog_channels").new_name
|
|
210
210
|
)
|
|
211
211
|
|
|
212
212
|
if self.project_subcortex:
|
|
213
|
-
self.lfp_channels = self.
|
|
213
|
+
self.lfp_channels = self.channels.query(
|
|
214
214
|
'(type=="lfp" or type=="seeg" or type=="dbs") \
|
|
215
215
|
and (used == 1) and (status=="good")'
|
|
216
216
|
).name.to_list()
|
|
@@ -222,11 +222,11 @@ class Projection:
|
|
|
222
222
|
self.lfp_channels.remove(lfp_channel)
|
|
223
223
|
# write lfp_channels to be new_name
|
|
224
224
|
self.lfp_channels = list(
|
|
225
|
-
self.
|
|
225
|
+
self.channels.query("name == @self.lfp_channels").new_name
|
|
226
226
|
)
|
|
227
227
|
|
|
228
228
|
def init_projection_run(self, feature_dict: dict) -> None:
|
|
229
|
-
"""Initialize indexes for respective channels in feature series computed by
|
|
229
|
+
"""Initialize indexes for respective channels in feature series computed by features.py"""
|
|
230
230
|
# here it is assumed that only one hemisphere is recorded at a time!
|
|
231
231
|
if self.project_cortex:
|
|
232
232
|
for ecog_channel in self.ecog_channels:
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pandas as pd
|
|
5
5
|
|
|
6
|
-
from py_neuromodulation.
|
|
6
|
+
from py_neuromodulation.utils.types import NMPreprocessor
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class ReReferencer(NMPreprocessor):
|
|
10
10
|
def __init__(
|
|
11
11
|
self,
|
|
12
12
|
sfreq: float,
|
|
13
|
-
|
|
13
|
+
channels: pd.DataFrame,
|
|
14
14
|
) -> None:
|
|
15
15
|
"""Initialize real-time rereference information.
|
|
16
16
|
|
|
@@ -18,9 +18,9 @@ class ReReferencer(NMPreprocessor):
|
|
|
18
18
|
----------
|
|
19
19
|
sfreq : float
|
|
20
20
|
Sampling frequency. Is not used, only kept for compatibility.
|
|
21
|
-
|
|
21
|
+
channels : Pandas DataFrame
|
|
22
22
|
Dataframe containing information about rereferencing, as
|
|
23
|
-
specified in
|
|
23
|
+
specified in channels.csv.
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
Raises:
|
|
@@ -30,27 +30,27 @@ class ReReferencer(NMPreprocessor):
|
|
|
30
30
|
|
|
31
31
|
self.ref_matrix: np.ndarray | None
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
# (channels_used,) = np.where((
|
|
33
|
+
channels = channels[channels["used"] == 1].reset_index(drop=True)
|
|
34
|
+
# (channels_used,) = np.where((channels.used == 1))
|
|
35
35
|
|
|
36
|
-
ch_names =
|
|
36
|
+
ch_names = channels["name"].tolist()
|
|
37
37
|
|
|
38
38
|
# no re-referencing is being performed when there is a single channel present only
|
|
39
|
-
if
|
|
39
|
+
if channels.shape[0] in (0, 1):
|
|
40
40
|
self.ref_matrix = None
|
|
41
41
|
return
|
|
42
42
|
|
|
43
|
-
ch_types =
|
|
44
|
-
refs =
|
|
43
|
+
ch_types = channels["type"]
|
|
44
|
+
refs = channels["rereference"]
|
|
45
45
|
|
|
46
46
|
type_map = {}
|
|
47
47
|
for ch_type in ch_types.unique():
|
|
48
48
|
type_map[ch_type] = np.where(
|
|
49
|
-
(ch_types == ch_type) & (
|
|
49
|
+
(ch_types == ch_type) & (channels["status"] == "good")
|
|
50
50
|
)[0]
|
|
51
51
|
|
|
52
|
-
ref_matrix = np.zeros((len(
|
|
53
|
-
for ind in range(len(
|
|
52
|
+
ref_matrix = np.zeros((len(channels), len(channels)))
|
|
53
|
+
for ind in range(len(channels)):
|
|
54
54
|
ref_matrix[ind, ind] = 1
|
|
55
55
|
# if ind not in channels_used:
|
|
56
56
|
# continue
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"""Module for resampling."""
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
-
from py_neuromodulation.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from py_neuromodulation.nm_preprocessing import NMPreprocessor
|
|
4
|
+
from py_neuromodulation.utils.types import NMBaseModel, Field, NMPreprocessor
|
|
8
5
|
|
|
9
6
|
|
|
10
7
|
class ResamplerSettings(NMBaseModel):
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
"""This module contains the class to process a given batch of data."""
|
|
2
2
|
|
|
3
3
|
from time import time
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
4
5
|
import numpy as np
|
|
5
|
-
import pandas as pd
|
|
6
6
|
|
|
7
|
-
from py_neuromodulation import
|
|
8
|
-
from py_neuromodulation.
|
|
9
|
-
from py_neuromodulation.
|
|
10
|
-
from py_neuromodulation.
|
|
11
|
-
from py_neuromodulation.
|
|
12
|
-
|
|
7
|
+
from py_neuromodulation import logger
|
|
8
|
+
from py_neuromodulation.utils.types import _PathLike
|
|
9
|
+
from py_neuromodulation.features import FeatureProcessors
|
|
10
|
+
from py_neuromodulation.utils import io
|
|
11
|
+
from py_neuromodulation.stream.settings import NMSettings
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from py_neuromodulation.processing.projection import Projection
|
|
15
|
+
import pandas as pd
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class DataProcessor:
|
|
@@ -17,21 +20,23 @@ class DataProcessor:
|
|
|
17
20
|
self,
|
|
18
21
|
sfreq: float,
|
|
19
22
|
settings: NMSettings | _PathLike,
|
|
20
|
-
|
|
23
|
+
channels: "pd.DataFrame | _PathLike",
|
|
21
24
|
coord_names: list | None = None,
|
|
22
25
|
coord_list: list | None = None,
|
|
23
26
|
line_noise: float | None = None,
|
|
24
27
|
path_grids: _PathLike | None = None,
|
|
25
28
|
verbose: bool = True,
|
|
26
29
|
) -> None:
|
|
30
|
+
from py_neuromodulation.processing import DataPreprocessor
|
|
31
|
+
|
|
27
32
|
"""Initialize run class.
|
|
28
33
|
|
|
29
34
|
Parameters
|
|
30
35
|
----------
|
|
31
|
-
settings :
|
|
32
|
-
|
|
36
|
+
settings : settings.NMSettings object
|
|
37
|
+
channels : pd.DataFrame | _PathLike
|
|
33
38
|
Initialized pd.DataFrame with channel specific information.
|
|
34
|
-
The path to a
|
|
39
|
+
The path to a channels.csv can be also passed.
|
|
35
40
|
coord_names : list | None
|
|
36
41
|
list of coordinate names
|
|
37
42
|
coord_list : list | None
|
|
@@ -41,8 +46,9 @@ class DataProcessor:
|
|
|
41
46
|
verbose : boolean
|
|
42
47
|
if True, log signal processed and computation time
|
|
43
48
|
"""
|
|
49
|
+
|
|
44
50
|
self.settings = NMSettings.load(settings)
|
|
45
|
-
self.
|
|
51
|
+
self.channels = io.load_channels(channels)
|
|
46
52
|
|
|
47
53
|
self.sfreq_features: float = self.settings.sampling_rate_features_hz
|
|
48
54
|
self._sfreq_raw_orig: float = sfreq
|
|
@@ -55,15 +61,15 @@ class DataProcessor:
|
|
|
55
61
|
|
|
56
62
|
(self.ch_names_used, _, self.feature_idx, _) = self._get_ch_info()
|
|
57
63
|
|
|
58
|
-
self.preprocessors =
|
|
64
|
+
self.preprocessors = DataPreprocessor(
|
|
59
65
|
settings=self.settings,
|
|
60
|
-
|
|
66
|
+
channels=self.channels,
|
|
61
67
|
sfreq=self.sfreq_raw,
|
|
62
68
|
line_noise=self.line_noise,
|
|
63
69
|
)
|
|
64
70
|
|
|
65
71
|
if self.settings.postprocessing.feature_normalization:
|
|
66
|
-
from py_neuromodulation.
|
|
72
|
+
from py_neuromodulation.processing.normalization import FeatureNormalizer
|
|
67
73
|
|
|
68
74
|
self.feature_normalizer = FeatureNormalizer(self.settings)
|
|
69
75
|
|
|
@@ -78,7 +84,7 @@ class DataProcessor:
|
|
|
78
84
|
coord_names=coord_names, coord_list=coord_list
|
|
79
85
|
)
|
|
80
86
|
|
|
81
|
-
self.projection = self._get_projection(self.settings, self.
|
|
87
|
+
self.projection = self._get_projection(self.settings, self.channels)
|
|
82
88
|
|
|
83
89
|
self.cnt_samples = 0
|
|
84
90
|
|
|
@@ -134,16 +140,16 @@ class DataProcessor:
|
|
|
134
140
|
def _get_ch_info(
|
|
135
141
|
self,
|
|
136
142
|
) -> tuple[list[str], list[str], list[int], np.ndarray]:
|
|
137
|
-
"""Get used feature and label info from
|
|
138
|
-
|
|
139
|
-
ch_names_used =
|
|
140
|
-
ch_types_used =
|
|
143
|
+
"""Get used feature and label info from channels"""
|
|
144
|
+
channels = self.channels
|
|
145
|
+
ch_names_used = channels[channels["used"] == 1]["new_name"].tolist()
|
|
146
|
+
ch_types_used = channels[channels["used"] == 1]["type"].tolist()
|
|
141
147
|
|
|
142
148
|
# used channels for feature estimation
|
|
143
|
-
feature_idx = np.where(
|
|
149
|
+
feature_idx = np.where(channels["used"] & ~channels["target"])[0].tolist()
|
|
144
150
|
|
|
145
151
|
# If multiple targets exist, select only the first
|
|
146
|
-
label_idx = np.where(
|
|
152
|
+
label_idx = np.where(channels["target"] == 1)[0]
|
|
147
153
|
|
|
148
154
|
return ch_names_used, ch_types_used, feature_idx, label_idx
|
|
149
155
|
|
|
@@ -151,12 +157,12 @@ class DataProcessor:
|
|
|
151
157
|
def _get_grids(
|
|
152
158
|
settings: "NMSettings",
|
|
153
159
|
path_grids: _PathLike | None,
|
|
154
|
-
) -> tuple[pd.DataFrame | None, pd.DataFrame | None]:
|
|
160
|
+
) -> "tuple[pd.DataFrame | None, pd.DataFrame | None]":
|
|
155
161
|
"""Read settings specified grids
|
|
156
162
|
|
|
157
163
|
Parameters
|
|
158
164
|
----------
|
|
159
|
-
settings :
|
|
165
|
+
settings : settings.NMSettings object
|
|
160
166
|
path_grids : _PathLike | str
|
|
161
167
|
|
|
162
168
|
Returns
|
|
@@ -166,18 +172,20 @@ class DataProcessor:
|
|
|
166
172
|
might be None if not specified in settings
|
|
167
173
|
"""
|
|
168
174
|
if settings.postprocessing.project_cortex:
|
|
169
|
-
grid_cortex =
|
|
175
|
+
grid_cortex = io.read_grid(path_grids, "cortex")
|
|
170
176
|
else:
|
|
171
177
|
grid_cortex = None
|
|
172
178
|
if settings.postprocessing.project_subcortex:
|
|
173
|
-
grid_subcortex =
|
|
179
|
+
grid_subcortex = io.read_grid(path_grids, "subcortex")
|
|
174
180
|
else:
|
|
175
181
|
grid_subcortex = None
|
|
176
182
|
return grid_cortex, grid_subcortex
|
|
177
183
|
|
|
178
184
|
def _get_projection(
|
|
179
|
-
self, settings: "NMSettings",
|
|
180
|
-
) -> Projection | None:
|
|
185
|
+
self, settings: "NMSettings", channels: "pd.DataFrame"
|
|
186
|
+
) -> "Projection | None":
|
|
187
|
+
from py_neuromodulation.processing.projection import Projection
|
|
188
|
+
|
|
181
189
|
"""Return projection of used coordinated and grids"""
|
|
182
190
|
|
|
183
191
|
if not any(
|
|
@@ -194,19 +202,11 @@ class DataProcessor:
|
|
|
194
202
|
grid_cortex=grid_cortex,
|
|
195
203
|
grid_subcortex=grid_subcortex,
|
|
196
204
|
coords=self.coords,
|
|
197
|
-
|
|
205
|
+
channels=channels,
|
|
198
206
|
plot_projection=False,
|
|
199
207
|
)
|
|
200
208
|
return projection
|
|
201
209
|
|
|
202
|
-
@staticmethod
|
|
203
|
-
def _load_nm_channels(
|
|
204
|
-
nm_channels: pd.DataFrame | _PathLike,
|
|
205
|
-
) -> pd.DataFrame:
|
|
206
|
-
if not isinstance(nm_channels, pd.DataFrame):
|
|
207
|
-
return nm_IO.load_nm_channels(nm_channels)
|
|
208
|
-
return nm_channels
|
|
209
|
-
|
|
210
210
|
def _set_coords(
|
|
211
211
|
self, coord_names: list[str] | None, coord_list: list | None
|
|
212
212
|
) -> dict:
|
|
@@ -308,18 +308,18 @@ class DataProcessor:
|
|
|
308
308
|
if additional_args is not None:
|
|
309
309
|
sidecar = sidecar | additional_args
|
|
310
310
|
|
|
311
|
-
|
|
311
|
+
io.save_sidecar(sidecar, out_dir, prefix)
|
|
312
312
|
|
|
313
313
|
def save_settings(self, out_dir: _PathLike, prefix: str = "") -> None:
|
|
314
314
|
self.settings.save(out_dir, prefix)
|
|
315
315
|
|
|
316
|
-
def
|
|
317
|
-
|
|
316
|
+
def save_channels(self, out_dir: _PathLike, prefix: str) -> None:
|
|
317
|
+
io.save_channels(self.channels, out_dir, prefix)
|
|
318
318
|
|
|
319
319
|
def save_features(
|
|
320
320
|
self,
|
|
321
|
-
feature_arr: pd.DataFrame,
|
|
321
|
+
feature_arr: "pd.DataFrame",
|
|
322
322
|
out_dir: _PathLike = "",
|
|
323
323
|
prefix: str = "",
|
|
324
324
|
) -> None:
|
|
325
|
-
|
|
325
|
+
io.save_features(feature_arr, out_dir, prefix)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RawDataGenerator:
|
|
5
|
+
"""
|
|
6
|
+
This generator function mimics online data acquisition.
|
|
7
|
+
The data are iteratively sampled with settings.sampling_rate_features_hz
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
data: np.ndarray,
|
|
13
|
+
sfreq: float,
|
|
14
|
+
sampling_rate_features_hz: float,
|
|
15
|
+
segment_length_features_ms: float,
|
|
16
|
+
) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Arguments
|
|
19
|
+
---------
|
|
20
|
+
data (np array): shape (channels, time)
|
|
21
|
+
settings (settings.NMSettings): settings object
|
|
22
|
+
sfreq (float): sampling frequency of the data
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
np.array: 1D array of time stamps
|
|
27
|
+
np.array: new batch for run function of full segment length shape
|
|
28
|
+
"""
|
|
29
|
+
self.batch_counter: int = 0 # counter for the batches
|
|
30
|
+
|
|
31
|
+
self.data = data
|
|
32
|
+
self.sfreq = sfreq
|
|
33
|
+
# Width, in data points, of the moving window used to calculate features
|
|
34
|
+
self.segment_length = segment_length_features_ms / 1000 * sfreq
|
|
35
|
+
# Ratio of the sampling frequency of the input data to the sampling frequency
|
|
36
|
+
self.stride = sfreq / sampling_rate_features_hz
|
|
37
|
+
|
|
38
|
+
def __iter__(self):
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def __next__(self):
|
|
42
|
+
start = self.stride * self.batch_counter
|
|
43
|
+
end = start + self.segment_length
|
|
44
|
+
|
|
45
|
+
self.batch_counter += 1
|
|
46
|
+
|
|
47
|
+
start_idx = int(start)
|
|
48
|
+
end_idx = int(end)
|
|
49
|
+
|
|
50
|
+
if end_idx > self.data.shape[1]:
|
|
51
|
+
raise StopIteration
|
|
52
|
+
|
|
53
|
+
return np.arange(start, end) / self.sfreq, self.data[:, start_idx:end_idx]
|
|
@@ -2,14 +2,16 @@ import numpy as np
|
|
|
2
2
|
import mne
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from py_neuromodulation import
|
|
5
|
+
from py_neuromodulation.utils.types import _PathLike
|
|
6
|
+
from py_neuromodulation.utils import io
|
|
7
|
+
from py_neuromodulation import logger
|
|
6
8
|
|
|
7
|
-
class LSLOfflinePlayer:
|
|
8
9
|
|
|
10
|
+
class LSLOfflinePlayer:
|
|
9
11
|
def __init__(
|
|
10
12
|
self,
|
|
11
13
|
stream_name: str | None = "lsl_offline_player",
|
|
12
|
-
f_name: str |
|
|
14
|
+
f_name: str | _PathLike = None,
|
|
13
15
|
raw: mne.io.Raw | None = None,
|
|
14
16
|
sfreq: int | float | None = None,
|
|
15
17
|
data: np.ndarray | None = None,
|
|
@@ -49,10 +51,12 @@ class LSLOfflinePlayer:
|
|
|
49
51
|
logger.critical(error_msg)
|
|
50
52
|
raise ValueError(error_msg)
|
|
51
53
|
|
|
52
|
-
if got_fname:
|
|
53
|
-
(self._path_raw, data, sfreq, line_noise, coord_list, coord_names) =
|
|
54
|
+
if got_fname:
|
|
55
|
+
(self._path_raw, data, sfreq, line_noise, coord_list, coord_names) = (
|
|
56
|
+
io.read_BIDS_data(f_name)
|
|
57
|
+
)
|
|
54
58
|
|
|
55
|
-
elif got_raw:
|
|
59
|
+
elif got_raw:
|
|
56
60
|
self._path_raw = raw
|
|
57
61
|
|
|
58
62
|
elif got_sfreq_data:
|
|
@@ -8,7 +8,7 @@ import os
|
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from py_neuromodulation import NMSettings
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
|
|
13
13
|
class LSLStream:
|
|
14
14
|
"""
|
|
@@ -21,7 +21,7 @@ class LSLStream:
|
|
|
21
21
|
|
|
22
22
|
Parameters:
|
|
23
23
|
-----------
|
|
24
|
-
settings :
|
|
24
|
+
settings : settings.NMSettings object
|
|
25
25
|
stream_name : str, optional
|
|
26
26
|
Name of the stream to connect to. If not provided, the first available stream is used.
|
|
27
27
|
|
|
@@ -32,8 +32,10 @@ class LSLStream:
|
|
|
32
32
|
under the same name.
|
|
33
33
|
"""
|
|
34
34
|
from mne_lsl.stream import StreamLSL
|
|
35
|
+
|
|
35
36
|
self.stream: StreamLSL
|
|
36
|
-
|
|
37
|
+
self.keyboard_interrupt = False
|
|
38
|
+
|
|
37
39
|
self.settings = settings
|
|
38
40
|
self._n_seconds_wait_before_disconnect = 3
|
|
39
41
|
try:
|
|
@@ -50,18 +52,16 @@ class LSLStream:
|
|
|
50
52
|
|
|
51
53
|
if self.stream.sinfo is None:
|
|
52
54
|
raise RuntimeError("Stream info is None. Check if the stream is running.")
|
|
53
|
-
|
|
55
|
+
|
|
54
56
|
self.winsize = settings.segment_length_features_ms / self.stream.sinfo.sfreq
|
|
55
57
|
self.sampling_interval = 1 / self.settings.sampling_rate_features_hz
|
|
56
58
|
|
|
57
59
|
# If not running the generator when the escape key is pressed.
|
|
58
60
|
self.headless: bool = not os.environ.get("DISPLAY")
|
|
59
61
|
if not self.headless:
|
|
60
|
-
from
|
|
62
|
+
from py_neuromodulation.utils.keyboard import KeyboardListener
|
|
61
63
|
|
|
62
|
-
self.listener =
|
|
63
|
-
on_press=lambda key: key != keyboard.Key.esc # type: ignore
|
|
64
|
-
)
|
|
64
|
+
self.listener = KeyboardListener(("esc", self.set_keyboard_interrupt))
|
|
65
65
|
self.listener.start()
|
|
66
66
|
|
|
67
67
|
def get_next_batch(self) -> Iterator[tuple[np.ndarray, np.ndarray]]:
|
|
@@ -111,6 +111,10 @@ class LSLStream:
|
|
|
111
111
|
|
|
112
112
|
logger.info(f"Stream time: {timestamp[-1] - stream_start_time}")
|
|
113
113
|
|
|
114
|
-
if not self.headless and
|
|
114
|
+
if not self.headless and self.keyboard_interrupt:
|
|
115
115
|
logger.info("Keyboard interrupt")
|
|
116
|
+
self.listener.stop()
|
|
116
117
|
self.stream.disconnect()
|
|
118
|
+
|
|
119
|
+
def set_keyboard_interrupt(self):
|
|
120
|
+
self.keyboard_interrupt = True
|
|
@@ -5,27 +5,30 @@ from typing import ClassVar
|
|
|
5
5
|
from pydantic import Field, model_validator
|
|
6
6
|
|
|
7
7
|
from py_neuromodulation import PYNM_DIR, logger, user_features
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
from py_neuromodulation.utils.types import (
|
|
9
10
|
BoolSelector,
|
|
10
11
|
FrequencyRange,
|
|
11
12
|
PreprocessorName,
|
|
12
13
|
_PathLike,
|
|
14
|
+
NMBaseModel,
|
|
15
|
+
NormMethod,
|
|
13
16
|
)
|
|
14
17
|
|
|
15
|
-
from py_neuromodulation.
|
|
16
|
-
from py_neuromodulation.
|
|
17
|
-
from py_neuromodulation.
|
|
18
|
-
from py_neuromodulation.
|
|
19
|
-
|
|
20
|
-
from py_neuromodulation.
|
|
21
|
-
from py_neuromodulation.
|
|
22
|
-
from py_neuromodulation.
|
|
23
|
-
from py_neuromodulation.
|
|
24
|
-
from py_neuromodulation.
|
|
25
|
-
from py_neuromodulation.
|
|
26
|
-
from py_neuromodulation.
|
|
27
|
-
from py_neuromodulation.
|
|
28
|
-
from py_neuromodulation.
|
|
18
|
+
from py_neuromodulation.processing.filter_preprocessing import FilterSettings
|
|
19
|
+
from py_neuromodulation.processing.normalization import NormalizationSettings
|
|
20
|
+
from py_neuromodulation.processing.resample import ResamplerSettings
|
|
21
|
+
from py_neuromodulation.processing.projection import ProjectionSettings
|
|
22
|
+
|
|
23
|
+
from py_neuromodulation.filter import KalmanSettings
|
|
24
|
+
from py_neuromodulation.features import BispectraSettings
|
|
25
|
+
from py_neuromodulation.features import NoldsSettings
|
|
26
|
+
from py_neuromodulation.features import MNEConnectivitySettings
|
|
27
|
+
from py_neuromodulation.features import FooofSettings
|
|
28
|
+
from py_neuromodulation.features import CoherenceSettings
|
|
29
|
+
from py_neuromodulation.features import SharpwaveSettings
|
|
30
|
+
from py_neuromodulation.features import OscillatorySettings, BandPowerSettings
|
|
31
|
+
from py_neuromodulation.features import BurstsSettings
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
class FeatureSelection(BoolSelector):
|
|
@@ -90,15 +93,15 @@ class NMSettings(NMBaseModel):
|
|
|
90
93
|
fft_settings: OscillatorySettings = OscillatorySettings()
|
|
91
94
|
welch_settings: OscillatorySettings = OscillatorySettings()
|
|
92
95
|
stft_settings: OscillatorySettings = OscillatorySettings()
|
|
93
|
-
bandpass_filter_settings:
|
|
96
|
+
bandpass_filter_settings: BandPowerSettings = BandPowerSettings()
|
|
94
97
|
kalman_filter_settings: KalmanSettings = KalmanSettings()
|
|
95
|
-
burst_settings:
|
|
98
|
+
burst_settings: BurstsSettings = BurstsSettings()
|
|
96
99
|
sharpwave_analysis_settings: SharpwaveSettings = SharpwaveSettings()
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
mne_connectivity_settings: MNEConnectivitySettings = MNEConnectivitySettings()
|
|
101
|
+
coherence_settings: CoherenceSettings = CoherenceSettings()
|
|
102
|
+
fooof_settings: FooofSettings = FooofSettings()
|
|
103
|
+
nolds_settings: NoldsSettings = NoldsSettings()
|
|
104
|
+
bispectrum_settings: BispectraSettings = BispectraSettings()
|
|
102
105
|
|
|
103
106
|
def __init__(self, *args, **kwargs) -> None:
|
|
104
107
|
super().__init__(*args, **kwargs)
|
|
@@ -247,7 +250,7 @@ class NMSettings(NMBaseModel):
|
|
|
247
250
|
|
|
248
251
|
@staticmethod
|
|
249
252
|
def get_default() -> "NMSettings":
|
|
250
|
-
return NMSettings.from_file(PYNM_DIR / "
|
|
253
|
+
return NMSettings.from_file(PYNM_DIR / "default_settings.yaml")
|
|
251
254
|
|
|
252
255
|
@staticmethod
|
|
253
256
|
def list_normalization_methods() -> list[NormMethod]:
|
|
@@ -281,7 +284,7 @@ def reset_settings(settings: NMSettings) -> NMSettings:
|
|
|
281
284
|
return settings.reset()
|
|
282
285
|
|
|
283
286
|
|
|
284
|
-
def
|
|
287
|
+
def get_fast_compute() -> NMSettings:
|
|
285
288
|
return NMSettings.get_fast_compute()
|
|
286
289
|
|
|
287
290
|
|