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.
- py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -34
- py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +95 -106
- py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +107 -119
- py_neuromodulation/__init__.py +80 -13
- py_neuromodulation/{nm_RMAP.py → analysis/RMAP.py} +496 -531
- py_neuromodulation/analysis/__init__.py +4 -0
- py_neuromodulation/{nm_decode.py → analysis/decode.py} +918 -992
- py_neuromodulation/{nm_analysis.py → analysis/feature_reader.py} +994 -1074
- py_neuromodulation/{nm_plots.py → analysis/plots.py} +627 -612
- py_neuromodulation/{nm_stats.py → analysis/stats.py} +458 -480
- py_neuromodulation/data/README +6 -6
- py_neuromodulation/data/dataset_description.json +8 -8
- py_neuromodulation/data/participants.json +32 -32
- py_neuromodulation/data/participants.tsv +2 -2
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -5
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -11
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -18
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -35
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -13
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -2
- py_neuromodulation/default_settings.yaml +241 -0
- py_neuromodulation/features/__init__.py +31 -0
- py_neuromodulation/features/bandpower.py +165 -0
- py_neuromodulation/features/bispectra.py +157 -0
- py_neuromodulation/features/bursts.py +297 -0
- py_neuromodulation/features/coherence.py +255 -0
- py_neuromodulation/features/feature_processor.py +121 -0
- py_neuromodulation/features/fooof.py +142 -0
- py_neuromodulation/features/hjorth_raw.py +57 -0
- py_neuromodulation/features/linelength.py +21 -0
- py_neuromodulation/features/mne_connectivity.py +148 -0
- py_neuromodulation/features/nolds.py +94 -0
- py_neuromodulation/features/oscillatory.py +249 -0
- py_neuromodulation/features/sharpwaves.py +432 -0
- py_neuromodulation/filter/__init__.py +3 -0
- py_neuromodulation/filter/kalman_filter.py +67 -0
- py_neuromodulation/filter/kalman_filter_external.py +1890 -0
- py_neuromodulation/filter/mne_filter.py +128 -0
- py_neuromodulation/filter/notch_filter.py +93 -0
- py_neuromodulation/grid_cortex.tsv +40 -40
- py_neuromodulation/liblsl/libpugixml.so.1.12 +0 -0
- py_neuromodulation/liblsl/linux/bionic_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/bookworm_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/focal_amd46/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/jammy_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/jammy_x86/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/linux/noble_amd64/liblsl.1.16.2.so +0 -0
- py_neuromodulation/liblsl/macos/amd64/liblsl.1.16.2.dylib +0 -0
- py_neuromodulation/liblsl/macos/arm64/liblsl.1.16.0.dylib +0 -0
- py_neuromodulation/liblsl/windows/amd64/liblsl.1.16.2.dll +0 -0
- py_neuromodulation/liblsl/windows/x86/liblsl.1.16.2.dll +0 -0
- py_neuromodulation/processing/__init__.py +10 -0
- py_neuromodulation/{nm_artifacts.py → processing/artifacts.py} +29 -25
- py_neuromodulation/processing/data_preprocessor.py +77 -0
- py_neuromodulation/processing/filter_preprocessing.py +78 -0
- py_neuromodulation/processing/normalization.py +175 -0
- py_neuromodulation/{nm_projection.py → processing/projection.py} +370 -394
- py_neuromodulation/{nm_rereference.py → processing/rereference.py} +97 -95
- py_neuromodulation/{nm_resample.py → processing/resample.py} +56 -50
- py_neuromodulation/stream/__init__.py +3 -0
- py_neuromodulation/stream/data_processor.py +325 -0
- py_neuromodulation/stream/generator.py +53 -0
- py_neuromodulation/stream/mnelsl_player.py +94 -0
- py_neuromodulation/stream/mnelsl_stream.py +120 -0
- py_neuromodulation/stream/settings.py +292 -0
- py_neuromodulation/stream/stream.py +427 -0
- py_neuromodulation/utils/__init__.py +2 -0
- py_neuromodulation/{nm_define_nmchannels.py → utils/channels.py} +305 -302
- py_neuromodulation/utils/database.py +149 -0
- py_neuromodulation/utils/io.py +378 -0
- py_neuromodulation/utils/keyboard.py +52 -0
- py_neuromodulation/utils/logging.py +66 -0
- py_neuromodulation/utils/types.py +251 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/METADATA +28 -33
- py_neuromodulation-0.0.6.dist-info/RECORD +89 -0
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/WHEEL +1 -1
- {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.6.dist-info}/licenses/LICENSE +21 -21
- py_neuromodulation/FieldTrip.py +0 -589
- py_neuromodulation/_write_example_dataset_helper.py +0 -65
- py_neuromodulation/nm_EpochStream.py +0 -92
- py_neuromodulation/nm_IO.py +0 -417
- py_neuromodulation/nm_across_patient_decoding.py +0 -927
- py_neuromodulation/nm_bispectra.py +0 -168
- py_neuromodulation/nm_bursts.py +0 -198
- py_neuromodulation/nm_coherence.py +0 -205
- py_neuromodulation/nm_cohortwrapper.py +0 -435
- py_neuromodulation/nm_eval_timing.py +0 -239
- py_neuromodulation/nm_features.py +0 -116
- py_neuromodulation/nm_features_abc.py +0 -39
- py_neuromodulation/nm_filter.py +0 -219
- py_neuromodulation/nm_filter_preprocessing.py +0 -91
- py_neuromodulation/nm_fooof.py +0 -159
- py_neuromodulation/nm_generator.py +0 -37
- py_neuromodulation/nm_hjorth_raw.py +0 -73
- py_neuromodulation/nm_kalmanfilter.py +0 -58
- py_neuromodulation/nm_linelength.py +0 -33
- py_neuromodulation/nm_mne_connectivity.py +0 -112
- py_neuromodulation/nm_nolds.py +0 -93
- py_neuromodulation/nm_normalization.py +0 -214
- py_neuromodulation/nm_oscillatory.py +0 -448
- py_neuromodulation/nm_run_analysis.py +0 -435
- py_neuromodulation/nm_settings.json +0 -338
- py_neuromodulation/nm_settings.py +0 -68
- py_neuromodulation/nm_sharpwaves.py +0 -401
- py_neuromodulation/nm_stream_abc.py +0 -218
- py_neuromodulation/nm_stream_offline.py +0 -359
- py_neuromodulation/utils/_logging.py +0 -24
- 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
|
py_neuromodulation/nm_filter.py
DELETED
|
@@ -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, :]
|
py_neuromodulation/nm_fooof.py
DELETED
|
@@ -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]
|