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.
Files changed (57) hide show
  1. py_neuromodulation/__init__.py +16 -10
  2. py_neuromodulation/{nm_RMAP.py → analysis/RMAP.py} +2 -2
  3. py_neuromodulation/analysis/__init__.py +4 -0
  4. py_neuromodulation/{nm_decode.py → analysis/decode.py} +4 -4
  5. py_neuromodulation/{nm_analysis.py → analysis/feature_reader.py} +21 -20
  6. py_neuromodulation/{nm_plots.py → analysis/plots.py} +54 -12
  7. py_neuromodulation/{nm_stats.py → analysis/stats.py} +2 -8
  8. py_neuromodulation/{nm_settings.yaml → default_settings.yaml} +6 -9
  9. py_neuromodulation/features/__init__.py +31 -0
  10. py_neuromodulation/features/bandpower.py +165 -0
  11. py_neuromodulation/{nm_bispectra.py → features/bispectra.py} +8 -5
  12. py_neuromodulation/{nm_bursts.py → features/bursts.py} +14 -9
  13. py_neuromodulation/{nm_coherence.py → features/coherence.py} +17 -13
  14. py_neuromodulation/{nm_features.py → features/feature_processor.py} +30 -53
  15. py_neuromodulation/{nm_fooof.py → features/fooof.py} +11 -8
  16. py_neuromodulation/{nm_hjorth_raw.py → features/hjorth_raw.py} +10 -5
  17. py_neuromodulation/{nm_linelength.py → features/linelength.py} +1 -1
  18. py_neuromodulation/{nm_mne_connectivity.py → features/mne_connectivity.py} +5 -6
  19. py_neuromodulation/{nm_nolds.py → features/nolds.py} +5 -7
  20. py_neuromodulation/{nm_oscillatory.py → features/oscillatory.py} +7 -181
  21. py_neuromodulation/{nm_sharpwaves.py → features/sharpwaves.py} +13 -4
  22. py_neuromodulation/filter/__init__.py +3 -0
  23. py_neuromodulation/{nm_kalmanfilter.py → filter/kalman_filter.py} +67 -71
  24. py_neuromodulation/filter/kalman_filter_external.py +1890 -0
  25. py_neuromodulation/{nm_filter.py → filter/mne_filter.py} +128 -219
  26. py_neuromodulation/filter/notch_filter.py +93 -0
  27. py_neuromodulation/processing/__init__.py +10 -0
  28. py_neuromodulation/{nm_artifacts.py → processing/artifacts.py} +2 -3
  29. py_neuromodulation/{nm_preprocessing.py → processing/data_preprocessor.py} +19 -25
  30. py_neuromodulation/{nm_filter_preprocessing.py → processing/filter_preprocessing.py} +3 -4
  31. py_neuromodulation/{nm_normalization.py → processing/normalization.py} +9 -7
  32. py_neuromodulation/{nm_projection.py → processing/projection.py} +14 -14
  33. py_neuromodulation/{nm_rereference.py → processing/rereference.py} +13 -13
  34. py_neuromodulation/{nm_resample.py → processing/resample.py} +1 -4
  35. py_neuromodulation/stream/__init__.py +3 -0
  36. py_neuromodulation/{nm_run_analysis.py → stream/data_processor.py} +42 -42
  37. py_neuromodulation/stream/generator.py +53 -0
  38. py_neuromodulation/{nm_mnelsl_generator.py → stream/mnelsl_player.py} +10 -6
  39. py_neuromodulation/{nm_mnelsl_stream.py → stream/mnelsl_stream.py} +13 -9
  40. py_neuromodulation/{nm_settings.py → stream/settings.py} +27 -24
  41. py_neuromodulation/{nm_stream.py → stream/stream.py} +217 -188
  42. py_neuromodulation/utils/__init__.py +2 -0
  43. py_neuromodulation/{nm_define_nmchannels.py → utils/channels.py} +14 -9
  44. py_neuromodulation/{nm_database.py → utils/database.py} +2 -2
  45. py_neuromodulation/{nm_IO.py → utils/io.py} +42 -77
  46. py_neuromodulation/utils/keyboard.py +52 -0
  47. py_neuromodulation/{nm_logger.py → utils/logging.py} +3 -3
  48. py_neuromodulation/{nm_types.py → utils/types.py} +72 -14
  49. {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.6.dist-info}/METADATA +3 -11
  50. py_neuromodulation-0.0.6.dist-info/RECORD +89 -0
  51. py_neuromodulation/FieldTrip.py +0 -589
  52. py_neuromodulation/_write_example_dataset_helper.py +0 -83
  53. py_neuromodulation/nm_generator.py +0 -45
  54. py_neuromodulation/nm_stream_abc.py +0 -166
  55. py_neuromodulation-0.0.5.dist-info/RECORD +0 -83
  56. {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.6.dist-info}/WHEEL +0 -0
  57. {py_neuromodulation-0.0.5.dist-info → py_neuromodulation-0.0.6.dist-info}/licenses/LICENSE +0 -0
@@ -2,13 +2,13 @@ import os
2
2
  import platform
3
3
  from pathlib import PurePath
4
4
  from importlib.metadata import version
5
- from .nm_logger import NMLogger
5
+ from py_neuromodulation.utils.logging import NMLogger
6
6
 
7
7
  #####################################
8
8
  # Globals and environment variables #
9
9
  #####################################
10
10
 
11
- __version__ = version(__package__) # get version from pyproject.toml
11
+ __version__ = version("py_neuromodulation") # get version from pyproject.toml
12
12
 
13
13
  # Check if the module is running headless (no display) for tests and doc builds
14
14
  PYNM_HEADLESS: bool = not os.environ.get("DISPLAY")
@@ -64,11 +64,17 @@ logger = NMLogger(__name__) # logger initialization first to prevent circular i
64
64
  ####################################
65
65
  # API: Exposed classes and methods #
66
66
  ####################################
67
- from .nm_stream import Stream as Stream
68
- from .nm_run_analysis import DataProcessor as DataProcessor
69
- from .nm_settings import NMSettings as NMSettings
70
- from .nm_features import (
71
- add_custom_feature as add_custom_feature,
72
- remove_custom_feature as remove_custom_feature,
73
- NMFeature as NMFeature,
74
- )
67
+ from .stream.stream import Stream
68
+ from .stream.data_processor import DataProcessor
69
+ from .stream.settings import NMSettings
70
+
71
+ from .analysis.feature_reader import FeatureReader
72
+ from .features.feature_processor import add_custom_feature, remove_custom_feature
73
+
74
+ from .utils import types
75
+ from .utils import io
76
+
77
+ from . import stream
78
+ from . import analysis
79
+
80
+ from .stream.settings import get_default_settings, get_fast_compute, reset_settings
@@ -8,8 +8,8 @@ import pandas as pd
8
8
  import nibabel as nib
9
9
  from matplotlib import pyplot as plt
10
10
 
11
- from py_neuromodulation.nm_plots import reg_plot
12
- from py_neuromodulation.nm_types import _PathLike
11
+ from py_neuromodulation.plots import reg_plot
12
+ from py_neuromodulation.types import _PathLike
13
13
  from py_neuromodulation import PYNM_DIR
14
14
 
15
15
  LIST_STRUC_UNCONNECTED_GRIDPOINTS_HULL = [256, 385, 417, 447, 819, 914]
@@ -0,0 +1,4 @@
1
+ from .plots import *
2
+ from .stats import *
3
+ from .decode import Decoder
4
+ from .feature_reader import FeatureReader
@@ -65,11 +65,11 @@ class Decoder:
65
65
  label: np.ndarray | None = None,
66
66
  label_name: str | None = None,
67
67
  used_chs: list[str] = [],
68
- model = LinearRegression(),
68
+ model=LinearRegression(),
69
69
  eval_method: Callable = r2_score,
70
- cv_method = model_selection.KFold(n_splits=3, shuffle=False),
70
+ cv_method=model_selection.KFold(n_splits=3, shuffle=False),
71
71
  use_nested_cv: bool = False,
72
- threshold_score = True,
72
+ threshold_score=True,
73
73
  mov_detection_threshold: float = 0.5,
74
74
  TRAIN_VAL_SPLIT: bool = False,
75
75
  RUN_BAY_OPT: bool = False,
@@ -89,7 +89,7 @@ class Decoder:
89
89
  model_save: bool = False,
90
90
  ) -> None:
91
91
  """Initialize here a feature file for processing
92
- Read settings.json nm_channels.csv and features.csv
92
+ Read settings.json channels.csv and features.csv
93
93
  Read target label
94
94
 
95
95
  Parameters
@@ -10,10 +10,11 @@ from sklearn.model_selection import KFold
10
10
 
11
11
  from scipy.stats import zscore as scipy_zscore
12
12
 
13
- from py_neuromodulation import nm_IO, nm_plots
14
- from py_neuromodulation.nm_decode import Decoder
15
- from py_neuromodulation.nm_types import _PathLike
16
- from py_neuromodulation.nm_settings import NMSettings
13
+ from py_neuromodulation.utils import io
14
+ from py_neuromodulation.analysis import plots
15
+ from py_neuromodulation.analysis.decode import Decoder
16
+ from py_neuromodulation.utils.types import _PathLike
17
+ from py_neuromodulation.stream.settings import NMSettings
17
18
 
18
19
 
19
20
  target_filter_str = {
@@ -46,14 +47,14 @@ class FeatureReader:
46
47
 
47
48
  """
48
49
  self.feature_dir = feature_dir
49
- self.feature_list: list[str] = nm_IO.get_run_list_indir(self.feature_dir)
50
+ self.feature_list: list[str] = io.get_run_list_indir(self.feature_dir)
50
51
  self.feature_file = feature_file if feature_file else self.feature_list[0]
51
52
 
52
53
  FILE_BASENAME = PurePath(self.feature_file).stem
53
54
  PATH_READ_FILE = str(PurePath(self.feature_dir, FILE_BASENAME, FILE_BASENAME))
54
55
 
55
56
  self.settings = NMSettings.from_file(PATH_READ_FILE)
56
- self.sidecar = nm_IO.read_sidecar(PATH_READ_FILE)
57
+ self.sidecar = io.read_sidecar(PATH_READ_FILE)
57
58
  if self.sidecar["sess_right"] is None:
58
59
  if "coords" in self.sidecar:
59
60
  if len(self.sidecar["coords"]["cortex_left"]["ch_names"]) > 0:
@@ -61,22 +62,22 @@ class FeatureReader:
61
62
  if len(self.sidecar["coords"]["cortex_right"]["ch_names"]) > 0:
62
63
  self.sidecar["sess_right"] = True
63
64
  self.sfreq = self.sidecar["sfreq"]
64
- self.nm_channels = nm_IO.read_nm_channels(PATH_READ_FILE)
65
- self.feature_arr = nm_IO.read_features(PATH_READ_FILE)
65
+ self.channels = io.read_channels(PATH_READ_FILE)
66
+ self.feature_arr = io.read_features(PATH_READ_FILE)
66
67
 
67
- self.ch_names = self.nm_channels.new_name
68
+ self.ch_names = self.channels.new_name
68
69
  self.used_chs = list(
69
- self.nm_channels[
70
- (self.nm_channels["target"] == 0) & (self.nm_channels["used"] == 1)
70
+ self.channels[
71
+ (self.channels["target"] == 0) & (self.channels["used"] == 1)
71
72
  ]["new_name"]
72
73
  )
73
- self.ch_names_ECOG = self.nm_channels.query(
74
+ self.ch_names_ECOG = self.channels.query(
74
75
  '(type=="ecog") and (used == 1) and (status=="good")'
75
76
  ).new_name.to_list()
76
77
 
77
78
  # init plotter
78
- self.nmplotter = nm_plots.NM_Plot()
79
- if self.nm_channels["target"].sum() > 0:
79
+ self.nmplotter = plots.NM_Plot()
80
+ if self.channels["target"].sum() > 0:
80
81
  self.label_name = self._get_target_ch()
81
82
  self.label = self.read_target_ch(
82
83
  self.feature_arr,
@@ -86,7 +87,7 @@ class FeatureReader:
86
87
  )
87
88
 
88
89
  def _get_target_ch(self) -> str:
89
- target_names = list(self.nm_channels[self.nm_channels["target"] == 1]["name"])
90
+ target_names = list(self.channels[self.channels["target"] == 1]["name"])
90
91
  target_clean = [
91
92
  target_name
92
93
  for target_name in target_names
@@ -312,7 +313,7 @@ class FeatureReader:
312
313
  threshold=threshold,
313
314
  )
314
315
 
315
- nm_plots.plot_epochs_avg(
316
+ plots.plot_epochs_avg(
316
317
  X_epoch=X_epoch,
317
318
  y_epoch=y_epoch,
318
319
  epoch_len=epoch_len,
@@ -376,7 +377,7 @@ class FeatureReader:
376
377
  else:
377
378
  df = self.feature_arr[self.feature_arr.columns[::-1]]
378
379
 
379
- nm_plots.plot_all_features(
380
+ plots.plot_all_features(
380
381
  df=df,
381
382
  time_limit_low_s=time_limit_low_s,
382
383
  time_limit_high_s=time_limit_high_s,
@@ -517,12 +518,12 @@ class FeatureReader:
517
518
  def plot_feature_series_time(
518
519
  self,
519
520
  ):
520
- self.nmplotter.plot_feature_series_time(self.feature_arr)
521
+ plots.plot_feature_series_time(self.feature_arr)
521
522
 
522
523
  def plot_corr_matrix(
523
524
  self,
524
525
  ):
525
- return nm_plots.plot_corr_matrix(
526
+ return plots.plot_corr_matrix(
526
527
  self.feature_arr,
527
528
  )
528
529
 
@@ -963,7 +964,7 @@ class FeatureReader:
963
964
  )
964
965
 
965
966
  if save_results:
966
- nm_IO.save_general_dict(
967
+ io.save_general_dict(
967
968
  dict_=performance_dict,
968
969
  path_out=PATH_OUT,
969
970
  prefix=folder_name,
@@ -1,13 +1,11 @@
1
1
  import numpy as np
2
2
  import pandas as pd
3
- from scipy.stats import zscore as scipy_zscore
4
3
  from matplotlib import pyplot as plt
5
4
  from matplotlib import gridspec
6
5
  import seaborn as sb
7
6
  from pathlib import PurePath
8
-
9
- from py_neuromodulation.nm_types import _PathLike
10
- from py_neuromodulation import logger
7
+ from py_neuromodulation import logger, PYNM_DIR
8
+ from py_neuromodulation.utils.types import _PathLike
11
9
 
12
10
 
13
11
  def plot_df_subjects(
@@ -83,8 +81,10 @@ def plot_epoch(
83
81
  str_label: str = "",
84
82
  ytick_labelsize: float | None = None,
85
83
  ):
84
+ from scipy.stats import zscore
85
+
86
86
  if z_score is None:
87
- X_epoch = scipy_zscore(
87
+ X_epoch = zscore(
88
88
  np.nan_to_num(np.nanmean(np.squeeze(X_epoch), axis=0)),
89
89
  axis=0,
90
90
  nan_policy="omit",
@@ -128,9 +128,8 @@ def plot_epoch(
128
128
  def reg_plot(
129
129
  x_col: str, y_col: str, data: pd.DataFrame, out_path_save: str | None = None
130
130
  ):
131
-
132
- from py_neuromodulation.nm_stats import permutationTestSpearmansRho
133
-
131
+ from py_neuromodulation.analysis.stats import permutationTestSpearmansRho
132
+
134
133
  plt.figure(figsize=(4, 4), dpi=300)
135
134
  rho, p = permutationTestSpearmansRho(
136
135
  data[x_col],
@@ -307,6 +306,8 @@ def plot_epochs_avg(
307
306
  figsize_x: float = 8,
308
307
  figsize_y: float = 8,
309
308
  ) -> None:
309
+ from scipy.stats import zscore
310
+
310
311
  # cut channel name of for axis + "_" for more dense plot
311
312
  if not feature_names:
312
313
  if cut_ch_name_cols and None not in (ch_name, feature_names):
@@ -315,7 +316,7 @@ def plot_epochs_avg(
315
316
  ]
316
317
 
317
318
  if normalize_data:
318
- X_epoch_mean = scipy_zscore(
319
+ X_epoch_mean = zscore(
319
320
  np.nanmean(np.squeeze(X_epoch), axis=0), axis=0, nan_policy="omit"
320
321
  ).T
321
322
  else:
@@ -424,6 +425,8 @@ def plot_all_features(
424
425
  OUT_PATH: _PathLike = "",
425
426
  feature_file: str = "",
426
427
  ):
428
+ from scipy.stats import zscore
429
+
427
430
  if time_limit_high_s is not None:
428
431
  df = df[df["time"] < time_limit_high_s * 1000]
429
432
  if time_limit_low_s is not None:
@@ -431,7 +434,7 @@ def plot_all_features(
431
434
 
432
435
  cols_plt = [c for c in df.columns if c != "time"]
433
436
  if normalize:
434
- data_plt = scipy_zscore(df[cols_plt], nan_policy="omit")
437
+ data_plt = zscore(df[cols_plt], nan_policy="omit")
435
438
  else:
436
439
  data_plt = df[cols_plt]
437
440
 
@@ -460,6 +463,47 @@ def plot_all_features(
460
463
  plt.savefig(plt_path, bbox_inches="tight")
461
464
 
462
465
 
466
+ def read_plot_modules(
467
+ PATH_PLOT: _PathLike = PYNM_DIR / "plots",
468
+ ):
469
+ """Read required .mat files for plotting
470
+
471
+ Parameters
472
+ ----------
473
+ PATH_PLOT : regexp, optional
474
+ path to plotting files, by default
475
+ """
476
+ from py_neuromodulation.utils.io import loadmat
477
+
478
+ faces = loadmat(PurePath(PATH_PLOT, "faces.mat"))
479
+ vertices = loadmat(PurePath(PATH_PLOT, "Vertices.mat"))
480
+ grid = loadmat(PurePath(PATH_PLOT, "grid.mat"))["grid"]
481
+ stn_surf = loadmat(PurePath(PATH_PLOT, "STN_surf.mat"))
482
+ x_ver = stn_surf["vertices"][::2, 0]
483
+ y_ver = stn_surf["vertices"][::2, 1]
484
+ x_ecog = vertices["Vertices"][::1, 0]
485
+ y_ecog = vertices["Vertices"][::1, 1]
486
+ z_ecog = vertices["Vertices"][::1, 2]
487
+ x_stn = stn_surf["vertices"][::1, 0]
488
+ y_stn = stn_surf["vertices"][::1, 1]
489
+ z_stn = stn_surf["vertices"][::1, 2]
490
+
491
+ return (
492
+ faces,
493
+ vertices,
494
+ grid,
495
+ stn_surf,
496
+ x_ver,
497
+ y_ver,
498
+ x_ecog,
499
+ y_ecog,
500
+ z_ecog,
501
+ x_stn,
502
+ y_stn,
503
+ z_stn,
504
+ )
505
+
506
+
463
507
  class NM_Plot:
464
508
  def __init__(
465
509
  self,
@@ -475,8 +519,6 @@ class NM_Plot:
475
519
  self.sess_right = sess_right
476
520
  self.proj_matrix_cortex = proj_matrix_cortex
477
521
 
478
- from py_neuromodulation.nm_IO import read_plot_modules
479
-
480
522
  (
481
523
  self.faces,
482
524
  self.vertices,
@@ -9,11 +9,6 @@ import pandas as pd
9
9
  import scipy.stats as stats
10
10
 
11
11
 
12
- def fitlm(x, y):
13
- import statsmodels.api as sm
14
- return sm.OLS(y, sm.add_constant(x)).fit()
15
-
16
-
17
12
  def fitlm_kfold(x, y, kfold_splits=5):
18
13
  from sklearn.linear_model import LinearRegression
19
14
  from sklearn.model_selection import KFold
@@ -28,7 +23,6 @@ def fitlm_kfold(x, y, kfold_splits=5):
28
23
  for i, (train, test) in enumerate(kfold.split(x, y)):
29
24
  model.fit(x.iloc[train, :], y.iloc[train, :])
30
25
  score = model.score(x.iloc[test, :], y.iloc[test, :])
31
- # mdl = fitlm(np.squeeze(y.iloc[test,:].transpose()), np.squeeze(model.predict(x.iloc[test, :])))
32
26
  scores.append(score)
33
27
  coeffs = np.vstack((coeffs, model.coef_))
34
28
  coeffs = list(np.delete(coeffs, 0))
@@ -303,9 +297,9 @@ def cluster_wise_p_val_correction(p_arr, p_sig=0.05, num_permutations=10000):
303
297
  p (float) : significance level of highest cluster
304
298
  p_min_index : indices of significant samples
305
299
  """
306
- from skimage.measure import label as measure_label
300
+ from scipy.ndimage import label as measure_label
307
301
 
308
- labels, num_clusters = measure_label(p_arr <= p_sig, return_num=True)
302
+ labels, num_clusters = measure_label(p_arr <= p_sig)
309
303
 
310
304
  # loop through clusters of p_val series or image
311
305
  index_cluster = {}
@@ -1,5 +1,4 @@
1
1
  ---
2
-
3
2
  ########################
4
3
  ### General settings ###
5
4
  ########################
@@ -15,7 +14,6 @@ frequency_ranges_hz: # frequency band ranges can be added, removed and altered
15
14
  high_gamma: [90, 200]
16
15
  HFA: [200, 400]
17
16
 
18
-
19
17
  # Enabled features
20
18
  features:
21
19
  raw_hjorth: true
@@ -62,7 +60,6 @@ preprocessing_filter:
62
60
  lowpass_filter_cutoff_hz: 200
63
61
  highpass_filter_cutoff_hz: 3
64
62
 
65
-
66
63
  ################################
67
64
  ### Postprocessing settings ####
68
65
  ################################
@@ -82,7 +79,6 @@ project_cortex_settings:
82
79
  project_subcortex_settings:
83
80
  max_dist_mm: 5
84
81
 
85
-
86
82
  #################################
87
83
  ### Feature specific settings ###
88
84
  #################################
@@ -154,6 +150,7 @@ sharpwave_analysis_settings:
154
150
  sharpwave_features:
155
151
  peak_left: false
156
152
  peak_right: false
153
+ num_peaks: false
157
154
  trough: false
158
155
  width: false
159
156
  prominence: true
@@ -185,7 +182,7 @@ sharpwave_analysis_settings:
185
182
  var: []
186
183
  apply_estimator_between_peaks_and_troughs: true
187
184
 
188
- coherence:
185
+ coherence_settings:
189
186
  channels: [] # List of channel pairs, empty by default. Each pair is a list of two channels.
190
187
  # Example channels: [[STN_RIGHT_0, ECOG_RIGHT_0], [STN_RIGHT_1, ECOG_RIGHT_1]]
191
188
  frequency_bands: [high_beta]
@@ -197,7 +194,7 @@ coherence:
197
194
  coh: true
198
195
  icoh: true
199
196
 
200
- fooof:
197
+ fooof_settings:
201
198
  aperiodic:
202
199
  exponent: true
203
200
  offset: true
@@ -214,7 +211,7 @@ fooof:
214
211
  freq_range_hz: [2, 40]
215
212
  knee: true
216
213
 
217
- nolds_features:
214
+ nolds_settings:
218
215
  sample_entropy: false
219
216
  correlation_dimension: false
220
217
  lyapunov_exponent: true
@@ -224,11 +221,11 @@ nolds_features:
224
221
  raw: true
225
222
  frequency_bands: [low_beta]
226
223
 
227
- mne_connectiviy:
224
+ mne_connectiviy_settings:
228
225
  method: plv
229
226
  mode: multitaper
230
227
 
231
- bispectrum:
228
+ bispectrum_settings:
232
229
  f1s: [5, 35]
233
230
  f2s: [5, 35]
234
231
  compute_features_for_whole_fband_range: true
@@ -0,0 +1,31 @@
1
+ # Expose feature settings
2
+ from py_neuromodulation.features.bispectra import BispectraSettings
3
+ from py_neuromodulation.features.coherence import CoherenceSettings
4
+ from py_neuromodulation.features.fooof import FooofSettings
5
+ from py_neuromodulation.features.mne_connectivity import MNEConnectivitySettings
6
+ from py_neuromodulation.features.nolds import NoldsSettings
7
+ from py_neuromodulation.features.sharpwaves import SharpwaveSettings
8
+ from py_neuromodulation.features.bursts import BurstsSettings
9
+ from py_neuromodulation.features.oscillatory import OscillatorySettings
10
+ from py_neuromodulation.features.bandpower import BandPowerSettings
11
+
12
+
13
+ # Expose feature classes
14
+ from py_neuromodulation.features.linelength import LineLength
15
+ from py_neuromodulation.features.hjorth_raw import Hjorth, Raw
16
+ from py_neuromodulation.features.bispectra import Bispectra
17
+ from py_neuromodulation.features.coherence import Coherence
18
+ from py_neuromodulation.features.fooof import FooofAnalyzer
19
+ from py_neuromodulation.features.mne_connectivity import MNEConnectivity
20
+ from py_neuromodulation.features.nolds import Nolds
21
+ from py_neuromodulation.features.sharpwaves import SharpwaveAnalyzer
22
+ from py_neuromodulation.features.bursts import Bursts
23
+ from py_neuromodulation.features.oscillatory import FFT, STFT, Welch
24
+ from py_neuromodulation.features.bandpower import BandPower
25
+
26
+ # Expose feature processor and custom feature functions
27
+ from py_neuromodulation.features.feature_processor import (
28
+ FeatureProcessors,
29
+ add_custom_feature,
30
+ remove_custom_feature,
31
+ )
@@ -0,0 +1,165 @@
1
+ import numpy as np
2
+ from collections.abc import Sequence
3
+ from typing import TYPE_CHECKING
4
+ from pydantic import field_validator
5
+
6
+ from py_neuromodulation.utils.types import NMBaseModel, BoolSelector, NMFeature
7
+
8
+ if TYPE_CHECKING:
9
+ from py_neuromodulation.stream.settings import NMSettings
10
+ from py_neuromodulation.filter import KalmanSettings
11
+
12
+
13
+ class BandpowerFeatures(BoolSelector):
14
+ activity: bool = True
15
+ mobility: bool = False
16
+ complexity: bool = False
17
+
18
+
19
+ class BandPowerSettings(NMBaseModel):
20
+ segment_lengths_ms: dict[str, int] = {
21
+ "theta": 1000,
22
+ "alpha": 500,
23
+ "low beta": 333,
24
+ "high beta": 333,
25
+ "low gamma": 100,
26
+ "high gamma": 100,
27
+ "HFA": 100,
28
+ }
29
+ bandpower_features: BandpowerFeatures = BandpowerFeatures()
30
+ log_transform: bool = True
31
+ kalman_filter: bool = False
32
+
33
+ @field_validator("bandpower_features")
34
+ @classmethod
35
+ def bandpower_features_validator(cls, bandpower_features: BandpowerFeatures):
36
+ assert (
37
+ len(bandpower_features.get_enabled()) > 0
38
+ ), "Set at least one bandpower_feature to True."
39
+
40
+ return bandpower_features
41
+
42
+ def validate_fbands(self, settings: "NMSettings") -> None:
43
+ for fband_name, seg_length_fband in self.segment_lengths_ms.items():
44
+ assert seg_length_fband <= settings.segment_length_features_ms, (
45
+ f"segment length {seg_length_fband} needs to be smaller than "
46
+ f" settings['segment_length_features_ms'] = {settings.segment_length_features_ms}"
47
+ )
48
+
49
+ for fband_name in settings.frequency_ranges_hz.keys():
50
+ assert fband_name in self.segment_lengths_ms, (
51
+ f"frequency range {fband_name} "
52
+ "needs to be defined in settings.bandpass_filter_settings.segment_lengths_ms]"
53
+ )
54
+
55
+
56
+ class BandPower(NMFeature):
57
+ def __init__(
58
+ self,
59
+ settings: "NMSettings",
60
+ ch_names: Sequence[str],
61
+ sfreq: float,
62
+ use_kf: bool | None = None,
63
+ ) -> None:
64
+ settings.validate()
65
+
66
+ self.bp_settings: BandPowerSettings = settings.bandpass_filter_settings
67
+ self.kalman_filter_settings: KalmanSettings = settings.kalman_filter_settings
68
+ self.sfreq = sfreq
69
+ self.ch_names = ch_names
70
+ self.KF_dict: dict = {}
71
+
72
+ from py_neuromodulation.filter import MNEFilter
73
+
74
+ self.bandpass_filter = MNEFilter(
75
+ f_ranges=[
76
+ tuple(frange) for frange in settings.frequency_ranges_hz.values()
77
+ ],
78
+ sfreq=self.sfreq,
79
+ filter_length=self.sfreq - 1,
80
+ verbose=False,
81
+ )
82
+
83
+ if use_kf or (use_kf is None and self.bp_settings.kalman_filter):
84
+ self.init_KF("bandpass_activity")
85
+
86
+ seglengths = self.bp_settings.segment_lengths_ms
87
+
88
+ self.feature_params = []
89
+ for ch_idx, ch_name in enumerate(self.ch_names):
90
+ for f_band_idx, f_band in enumerate(settings.frequency_ranges_hz.keys()):
91
+ seglength_ms = seglengths[f_band]
92
+ seglen = int(np.floor(self.sfreq / 1000 * seglength_ms))
93
+ for bp_feature in self.bp_settings.bandpower_features.get_enabled():
94
+ feature_name = "_".join([ch_name, "bandpass", bp_feature, f_band])
95
+ self.feature_params.append(
96
+ (
97
+ ch_idx,
98
+ f_band_idx,
99
+ seglen,
100
+ bp_feature,
101
+ feature_name,
102
+ )
103
+ )
104
+
105
+ def init_KF(self, feature: str) -> None:
106
+ from py_neuromodulation.filter import define_KF
107
+
108
+ for f_band in self.kalman_filter_settings.frequency_bands:
109
+ for channel in self.ch_names:
110
+ self.KF_dict["_".join([channel, feature, f_band])] = define_KF(
111
+ self.kalman_filter_settings.Tp,
112
+ self.kalman_filter_settings.sigma_w,
113
+ self.kalman_filter_settings.sigma_v,
114
+ )
115
+
116
+ def update_KF(self, feature_calc: np.floating, KF_name: str) -> np.floating:
117
+ if KF_name in self.KF_dict:
118
+ self.KF_dict[KF_name].predict()
119
+ self.KF_dict[KF_name].update(feature_calc)
120
+ feature_calc = self.KF_dict[KF_name].x[0]
121
+ return feature_calc
122
+
123
+ def calc_feature(self, data: np.ndarray) -> dict:
124
+ data = self.bandpass_filter.filter_data(data)
125
+
126
+ feature_results = {}
127
+ for (
128
+ ch_idx,
129
+ f_band_idx,
130
+ seglen,
131
+ bp_feature,
132
+ feature_name,
133
+ ) in self.feature_params:
134
+ feature_results[feature_name] = self.calc_bp_feature(
135
+ bp_feature, feature_name, data[ch_idx, f_band_idx, -seglen:]
136
+ )
137
+
138
+ return feature_results
139
+
140
+ def calc_bp_feature(self, bp_feature, feature_name, data):
141
+ match bp_feature:
142
+ case "activity":
143
+ feature_calc = np.var(data)
144
+ if self.bp_settings.log_transform:
145
+ feature_calc = np.log10(feature_calc)
146
+ if self.KF_dict:
147
+ feature_calc = self.update_KF(feature_calc, feature_name)
148
+ case "mobility":
149
+ feature_calc = np.sqrt(np.var(np.diff(data)) / np.var(data))
150
+ case "complexity":
151
+ feature_calc = self.calc_complexity(data)
152
+ case _:
153
+ raise ValueError(f"Unknown bandpower feature: {bp_feature}")
154
+
155
+ return np.nan_to_num(feature_calc)
156
+
157
+ @staticmethod
158
+ def calc_complexity(data: np.ndarray) -> float:
159
+ dat_deriv = np.diff(data)
160
+ deriv_variance = np.var(dat_deriv)
161
+ mobility = np.sqrt(deriv_variance / np.var(data))
162
+ dat_deriv_2_var = np.var(np.diff(dat_deriv))
163
+ deriv_mobility = np.sqrt(dat_deriv_2_var / deriv_variance)
164
+
165
+ return deriv_mobility / mobility
@@ -1,15 +1,18 @@
1
1
  from collections.abc import Iterable
2
2
  from pydantic import field_validator
3
- from py_neuromodulation.nm_types import NMBaseModel
4
3
  from typing import TYPE_CHECKING, Callable
5
4
 
6
5
  import numpy as np
7
6
 
8
- from py_neuromodulation.nm_features import NMFeature
9
- from py_neuromodulation.nm_types import BoolSelector, FrequencyRange
7
+ from py_neuromodulation.utils.types import (
8
+ NMBaseModel,
9
+ NMFeature,
10
+ BoolSelector,
11
+ FrequencyRange,
12
+ )
10
13
 
11
14
  if TYPE_CHECKING:
12
- from py_neuromodulation.nm_settings import NMSettings
15
+ from py_neuromodulation import NMSettings
13
16
 
14
17
 
15
18
  class BispectraComponents(BoolSelector):
@@ -67,7 +70,7 @@ class Bispectra(NMFeature):
67
70
  self.sfreq = sfreq
68
71
  self.ch_names = ch_names
69
72
  self.frequency_ranges_hz = settings.frequency_ranges_hz
70
- self.settings: BispectraSettings = settings.bispectrum
73
+ self.settings: BispectraSettings = settings.bispectrum_settings
71
74
 
72
75
  assert all(
73
76
  f_band_bispectrum in settings.frequency_ranges_hz