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,302 +1,305 @@
1
- """Module for handling nm_channels."""
2
- from typing import Iterable, Optional, Union
3
-
4
- import pandas as pd
5
- import numpy as np
6
-
7
-
8
- _LFP_TYPES = ["seeg", "dbs", "lfp"] # must be lower-case
9
-
10
-
11
- def set_channels(
12
- ch_names: list[str],
13
- ch_types: list[str],
14
- reference: Union[list, str] = "default",
15
- bads: Optional[list[str]] = None,
16
- new_names: Union[str, list[str]] = "default",
17
- ecog_only: bool = False,
18
- used_types: Optional[Iterable[str]] = ("ecog", "dbs", "seeg"),
19
- target_keywords: Optional[Iterable[str]] = ("mov", "squared", "label"),
20
- ) -> pd.DataFrame:
21
- """Return dataframe with channel-specific settings in nm_channels format.
22
-
23
- Return an nm_channels dataframe with the columns: "name", "rereference",
24
- "used", "target", "type", "status", "new_name"]. "name" is set to ch_names,
25
- "rereference" can be specified individually. "used" is set to 1 for all
26
- channel types specified in `used_types`, else to 0. "target" is set to 1
27
- for all channels containing any of the `target_keywords`, else to 0.
28
-
29
- Possible channel types:
30
- https://github.com/mne-tools/mne-python/blob/6ae3b22033c745cce5cd5de9b92da54c13c36484/doc/_includes/channel_types.rst
31
-
32
- Arguments
33
- ---------
34
- ch_names : list
35
- list of channel names.
36
- ch_types : list
37
- list of channel types. Should optimally be of the types: "ECOG",
38
- "DBS" or "SEEG".
39
- reference : str | list of str | None, default: 'default'
40
- re-referencing scheme. Default is "default". This sets ECOG channel
41
- references to "average" and creates a bipolar referencing scheme
42
- for LFP/DBS/SEEG channels, where each channel is referenced to
43
- the adjacent lower channel, split by left and right hemisphere.
44
- For this, the channel names must contain the substring "_L_" and/or
45
- "_R_" (lower or upper case). CAVE: Adjacent channels will be
46
- determined using the sort() function.
47
- bads : str | list of str, default: None
48
- channels that should be marked as bad and not be used for
49
- average re-referencing etc.
50
- new_names : list of str | None, default: 'default'
51
- new channel names that should be used when writing out the
52
- features and results. Useful when applying re-referencing. Set to
53
- 'None' if no renaming should be performed. 'default' will infer
54
- channel renaming from re-referencing information. If a list is
55
- given, it should be in the same order as 'ch_names'.
56
- ECOG_ONLY : boolean, default: False
57
- if True, set only 'ecog' channel type to used
58
- used_types : iterable of str | None, default : ("ecog", "dbs", "seeg")
59
- data channel types to be used. Set to `None` to use no channel
60
- types.
61
- target_keywords : iterable of str | None, default : ("ecog", "dbs", "seeg")
62
- keywords for target channels
63
-
64
- Returns
65
- -------
66
- df: DataFrame in nm_channels format
67
- """
68
- if not (len(ch_names) == len(ch_types)):
69
- raise ValueError(
70
- "Number of `ch_names` and `ch_types` must match."
71
- f"Got: {len(ch_names)} `ch_names` and {len(ch_types)} `ch_types`."
72
- )
73
-
74
- df = pd.DataFrame(
75
- data=None,
76
- columns=[
77
- "name",
78
- "rereference",
79
- "used",
80
- "target",
81
- "type",
82
- "status",
83
- "new_name",
84
- ],
85
- )
86
- df["name"] = ch_names
87
-
88
- if used_types:
89
- if type(used_types) is str:
90
- used_types = [used_types] # Even if the user passes only ("ecog"), the if statement bellow will work
91
- used_list = []
92
- for ch_type in ch_types:
93
- if any(
94
- use_type.lower() == ch_type.lower() for use_type in used_types
95
- ):
96
- used_list.append(1)
97
- else:
98
- used_list.append(0)
99
- df["used"] = used_list
100
- else:
101
- df["used"] = 0
102
-
103
- if target_keywords:
104
- if type(target_keywords) is str:
105
- target_keywords = [target_keywords]
106
- targets = []
107
- for ch_name in ch_names:
108
- if any(kw.lower() in ch_name.lower() for kw in target_keywords):
109
- targets.append(1)
110
- else:
111
- targets.append(0)
112
- df["target"] = targets
113
- else:
114
- df["target"] = 0
115
-
116
- # note: BIDS types are in caps, mne.io.RawArray types lower case
117
- # so that 'type' will be in lower case here
118
- df["type"] = ch_types
119
-
120
- if ecog_only:
121
- df.loc[(df["type"] == "seeg") | (df["type"] == "dbs"), "used"] = 0
122
-
123
- if isinstance(reference, str):
124
- if reference.lower() == "default":
125
- df = _get_default_references(
126
- df=df, ch_names=ch_names, ch_types=ch_types
127
- )
128
- else:
129
- raise ValueError(
130
- "`reference` must be either `default`, `None` or "
131
- "an iterable of new reference channel names. "
132
- f"Got: {reference}."
133
- )
134
-
135
- elif isinstance(reference, list):
136
- if len(ch_names) != len(reference):
137
- raise ValueError(
138
- "Number of `ch_names` and `reference` must match."
139
- f"Got: {len(ch_names)} `ch_names` and {len(reference)}"
140
- " `references`."
141
- )
142
- df["rereference"] = reference
143
- elif not reference:
144
- df.loc[:, "rereference"] = "None"
145
- else:
146
- raise ValueError(
147
- "`reference` must be either `default`, None or "
148
- "an iterable of new reference channel names. "
149
- f"Got: {reference}."
150
- )
151
-
152
- if bads:
153
- if isinstance(bads, str):
154
- bads = [bads]
155
- df["status"] = ["bad" if ch in bads else "good" for ch in ch_names]
156
- df.loc[
157
- df["status"] == "bad", "used"
158
- ] = 0 # setting bad channels to not being used
159
- else:
160
- df["status"] = "good"
161
-
162
- if not new_names:
163
- df["new_name"] = ch_names
164
- elif isinstance(new_names, str):
165
- if new_names.lower() != "default":
166
- raise ValueError(
167
- "`new_names` must be either `default`, None or "
168
- "an iterable of new channel names. Got: "
169
- f"{new_names}."
170
- )
171
- new_names = []
172
- for name, ref in zip(df["name"], df["rereference"]):
173
- if ref == "None":
174
- new_names.append(name)
175
- elif type(ref) == float:
176
- if np.isnan(ref):
177
- new_names.append(name)
178
- elif ref == "average":
179
- new_names.append(name + "-avgref")
180
- else:
181
- new_names.append(name + "-" + ref)
182
- df["new_name"] = new_names
183
- elif hasattr(new_names, "__iter__"):
184
- if len(new_names) != len(ch_names):
185
- raise ValueError(
186
- "Number of `ch_names` and `new_names` must match."
187
- f" Got: {len(ch_names)} `ch_names` and {len(new_names)}"
188
- " `new_names`."
189
- )
190
- else:
191
- df["new_name"] = ch_names
192
- else:
193
- raise ValueError(
194
- "`new_names` must be either `default`, None or"
195
- f" an iterable of new channel names. Got: {new_names}."
196
- )
197
- return df
198
-
199
-
200
- def _get_default_references(
201
- df: pd.DataFrame, ch_names: list[str], ch_types: list[str]
202
- ) -> pd.DataFrame:
203
- """Add references with default settings (ECOG CAR, LFP bipolar)."""
204
- ecog_chs = []
205
- lfp_chs = []
206
- other_chs = []
207
- for ch_name, ch_type in zip(ch_names, ch_types):
208
- if "ecog" in ch_type.lower() or "ecog" in ch_name.lower():
209
- ecog_chs.append(ch_name)
210
- elif any(
211
- lfp_type in ch_type.lower() or lfp_type in ch_name.lower()
212
- for lfp_type in _LFP_TYPES
213
- ):
214
- lfp_chs.append(ch_name)
215
- else:
216
- other_chs.append(ch_name)
217
- lfp_l = [
218
- lfp_ch
219
- for lfp_ch in lfp_chs
220
- if ("_l_" in lfp_ch.lower()) or ("_left_" in lfp_ch.lower())
221
- ]
222
- lfp_l.sort()
223
- lfp_r = [
224
- lfp_ch
225
- for lfp_ch in lfp_chs
226
- if ("_r_" in lfp_ch.lower()) or ("_right_" in lfp_ch.lower())
227
- ]
228
- lfp_r.sort()
229
- lfp_l_refs = [
230
- lfp_l[i - 1] if i > 0 else lfp_l[-1] for i, _ in enumerate(lfp_l)
231
- ]
232
- lfp_r_refs = [
233
- lfp_r[i - 1] if i > 0 else lfp_r[-1] for i, _ in enumerate(lfp_r)
234
- ]
235
- ref_idx = list(df.columns).index("rereference")
236
- if len(ecog_chs) > 1:
237
- for ecog_ch in ecog_chs:
238
- df.iloc[df[df["name"] == ecog_ch].index[0], ref_idx] = "average"
239
- if len(lfp_l) > 1: # if there is only a single channel, the channel would be subtracted from itself
240
- for i, lfp in enumerate(lfp_l):
241
- df.iloc[df[df["name"] == lfp].index[0], ref_idx] = lfp_l_refs[i]
242
- if len(lfp_r) > 1:
243
- for i, lfp in enumerate(lfp_r):
244
- df.iloc[df[df["name"] == lfp].index[0], ref_idx] = lfp_r_refs[i]
245
- for other_ch in other_chs:
246
- df.iloc[df[df["name"] == other_ch].index[0], ref_idx] = "None"
247
-
248
- df = df.replace(np.nan, "None")
249
-
250
- return df
251
-
252
- def get_default_channels_from_data(
253
- data: np.array, car_rereferencing:bool=True,
254
- ):
255
- """Return default nm_channels dataframe with
256
- ecog datatype, no bad channels, no targets, common average rereferencing
257
-
258
- Parameters
259
- ----------
260
- data : np.array
261
- Data array in shape (n_channels, n_time)
262
- car_rereferencing : bool, optional
263
- use common average rereferencing, by default True
264
-
265
- Returns
266
- -------
267
- pd.DataFrame
268
- nm_channel dataframe containing columns:
269
- - name
270
- - rereference
271
- - used
272
- - target
273
- - type
274
- - status
275
- - new_name
276
- """
277
-
278
- ch_name = [f"ch{idx}" for idx in range(data.shape[0])]
279
- status = ["good" for _ in range(data.shape[0])]
280
- type_nm = ["ecog" for _ in range(data.shape[0])]
281
-
282
- if car_rereferencing is True:
283
- rereference = ["average" for _ in range(data.shape[0])]
284
- new_name = [f"{ch}-avgref" for ch in ch_name]
285
- else:
286
- rereference = ["None" for _ in range(data.shape[0])]
287
- new_name = ch_name
288
-
289
- new_name = [f"{ch}-avgref" for ch in ch_name]
290
- target = np.array([0 for _ in range(data.shape[0])])
291
- used = np.array([1 for _ in range(data.shape[0])])
292
-
293
- df = pd.DataFrame()
294
- df["name"] = ch_name
295
- df["rereference"] = rereference
296
- df["used"] = used
297
- df["target"] = target
298
- df["type"] = type_nm
299
- df["status"] = status
300
- df["new_name"] = new_name
301
-
302
- return df
1
+ """Module for handling channels."""
2
+
3
+ from collections.abc import Iterable
4
+ from typing import TYPE_CHECKING
5
+ import numpy as np
6
+
7
+ if TYPE_CHECKING:
8
+ import pandas as pd
9
+
10
+ _LFP_TYPES = ["seeg", "dbs", "lfp"] # must be lower-case
11
+
12
+
13
+ def set_channels(
14
+ ch_names: list[str],
15
+ ch_types: list[str],
16
+ reference: list | str = "default",
17
+ bads: list[str] | None = None,
18
+ new_names: str | list[str] = "default",
19
+ ecog_only: bool = False,
20
+ used_types: Iterable[str] | None = ("ecog", "dbs", "seeg"),
21
+ target_keywords: Iterable[str] | None = ("mov", "squared", "label"),
22
+ ) -> "pd.DataFrame":
23
+ """Return dataframe with channel-specific settings in channels format.
24
+
25
+ Return an channels dataframe with the columns: "name", "rereference",
26
+ "used", "target", "type", "status", "new_name"]. "name" is set to ch_names,
27
+ "rereference" can be specified individually. "used" is set to 1 for all
28
+ channel types specified in `used_types`, else to 0. "target" is set to 1
29
+ for all channels containing any of the `target_keywords`, else to 0.
30
+
31
+ Possible channel types:
32
+ https://github.com/mne-tools/mne-python/blob/6ae3b22033c745cce5cd5de9b92da54c13c36484/doc/_includes/channel_types.rst
33
+
34
+ Arguments
35
+ ---------
36
+ ch_names : list
37
+ list of channel names.
38
+ ch_types : list
39
+ list of channel types. Should optimally be of the types: "ECOG",
40
+ "DBS" or "SEEG".
41
+ reference : str | list of str | None, default: 'default'
42
+ re-referencing scheme. Default is "default". This sets ECOG channel
43
+ references to "average" and creates a bipolar referencing scheme
44
+ for LFP/DBS/SEEG channels, where each channel is referenced to
45
+ the adjacent lower channel, split by left and right hemisphere.
46
+ For this, the channel names must contain the substring "_L_" and/or
47
+ "_R_" (lower or upper case). CAVE: Adjacent channels will be
48
+ determined using the sort() function.
49
+ bads : str | list of str, default: None
50
+ channels that should be marked as bad and not be used for
51
+ average re-referencing etc.
52
+ new_names : list of str | None, default: 'default'
53
+ new channel names that should be used when writing out the
54
+ features and results. Useful when applying re-referencing. Set to
55
+ 'None' if no renaming should be performed. 'default' will infer
56
+ channel renaming from re-referencing information. If a list is
57
+ given, it should be in the same order as 'ch_names'.
58
+ ecog_only : boolean, default: False
59
+ if True, set only 'ecog' channel type to used
60
+ used_types : iterable of str | None, default : ("ecog", "dbs", "seeg")
61
+ data channel types to be used. Set to `None` to use no channel
62
+ types.
63
+ target_keywords : iterable of str | None, default : ("ecog", "dbs", "seeg")
64
+ keywords for target channels
65
+
66
+ Returns
67
+ -------
68
+ df: DataFrame in channels format
69
+ """
70
+ import pandas as pd
71
+
72
+ if not (len(ch_names) == len(ch_types)):
73
+ raise ValueError(
74
+ "Number of `ch_names` and `ch_types` must match."
75
+ f"Got: {len(ch_names)} `ch_names` and {len(ch_types)} `ch_types`."
76
+ )
77
+
78
+ df = pd.DataFrame(
79
+ data=None,
80
+ columns=[
81
+ "name",
82
+ "rereference",
83
+ "used",
84
+ "target",
85
+ "type",
86
+ "status",
87
+ "new_name",
88
+ ],
89
+ )
90
+ df["name"] = ch_names
91
+
92
+ if used_types:
93
+ if isinstance(used_types, str):
94
+ used_types = [
95
+ used_types
96
+ ] # Even if the user passes only ("ecog"), the if statement bellow will work
97
+ used_list = []
98
+ for ch_type in ch_types:
99
+ if any(use_type.lower() == ch_type.lower() for use_type in used_types):
100
+ used_list.append(1)
101
+ else:
102
+ used_list.append(0)
103
+ df["used"] = used_list
104
+ else:
105
+ df["used"] = 0
106
+
107
+ if target_keywords:
108
+ if isinstance(target_keywords, str):
109
+ target_keywords = [target_keywords]
110
+ targets = []
111
+ for ch_name in ch_names:
112
+ if any(kw.lower() in ch_name.lower() for kw in target_keywords):
113
+ targets.append(1)
114
+ else:
115
+ targets.append(0)
116
+ df["target"] = targets
117
+ else:
118
+ df["target"] = 0
119
+
120
+ # note: BIDS types are in caps, mne.io.RawArray types lower case
121
+ # so that 'type' will be in lower case here
122
+ df["type"] = ch_types
123
+
124
+ if ecog_only:
125
+ df.loc[(df["type"] == "seeg") | (df["type"] == "dbs"), "used"] = 0
126
+
127
+ if isinstance(reference, str):
128
+ if reference.lower() == "default":
129
+ df = _get_default_references(df=df, ch_names=ch_names, ch_types=ch_types)
130
+ else:
131
+ raise ValueError(
132
+ "`reference` must be either `default`, `None` or "
133
+ "an iterable of new reference channel names. "
134
+ f"Got: {reference}."
135
+ )
136
+
137
+ elif isinstance(reference, list):
138
+ if len(ch_names) != len(reference):
139
+ raise ValueError(
140
+ "Number of `ch_names` and `reference` must match."
141
+ f"Got: {len(ch_names)} `ch_names` and {len(reference)}"
142
+ " `references`."
143
+ )
144
+ df["rereference"] = reference
145
+ elif not reference:
146
+ df.loc[:, "rereference"] = "None"
147
+ else:
148
+ raise ValueError(
149
+ "`reference` must be either `default`, None or "
150
+ "an iterable of new reference channel names. "
151
+ f"Got: {reference}."
152
+ )
153
+
154
+ if bads:
155
+ if isinstance(bads, str):
156
+ bads = [bads]
157
+ df["status"] = ["bad" if ch in bads else "good" for ch in ch_names]
158
+ df.loc[df["status"] == "bad", "used"] = (
159
+ 0 # setting bad channels to not being used
160
+ )
161
+ else:
162
+ df["status"] = "good"
163
+
164
+ if not new_names:
165
+ df["new_name"] = ch_names
166
+ elif isinstance(new_names, str):
167
+ if new_names.lower() != "default":
168
+ raise ValueError(
169
+ "`new_names` must be either `default`, None or "
170
+ "an iterable of new channel names. Got: "
171
+ f"{new_names}."
172
+ )
173
+ new_names = []
174
+ for name, ref in zip(df["name"], df["rereference"]):
175
+ if ref == "None":
176
+ new_names.append(name)
177
+ elif isinstance(ref, float):
178
+ if np.isnan(ref):
179
+ new_names.append(name)
180
+ elif ref == "average":
181
+ new_names.append(name + "_avgref")
182
+ else:
183
+ new_names.append(name + "_" + ref)
184
+ df["new_name"] = new_names
185
+ elif hasattr(new_names, "__iter__"):
186
+ if len(new_names) != len(ch_names):
187
+ raise ValueError(
188
+ "Number of `ch_names` and `new_names` must match."
189
+ f" Got: {len(ch_names)} `ch_names` and {len(new_names)}"
190
+ " `new_names`."
191
+ )
192
+ else:
193
+ df["new_name"] = ch_names
194
+ else:
195
+ raise ValueError(
196
+ "`new_names` must be either `default`, None or"
197
+ f" an iterable of new channel names. Got: {new_names}."
198
+ )
199
+ return df
200
+
201
+
202
+ def _get_default_references(
203
+ df: "pd.DataFrame", ch_names: list[str], ch_types: list[str]
204
+ ) -> "pd.DataFrame":
205
+ """Add references with default settings (ECOG CAR, LFP bipolar)."""
206
+ ecog_chs = []
207
+ lfp_chs = []
208
+ other_chs = []
209
+ for ch_name, ch_type in zip(ch_names, ch_types):
210
+ if "ecog" in ch_type.lower() or "ecog" in ch_name.lower():
211
+ ecog_chs.append(ch_name)
212
+ elif any(
213
+ lfp_type in ch_type.lower() or lfp_type in ch_name.lower()
214
+ for lfp_type in _LFP_TYPES
215
+ ):
216
+ lfp_chs.append(ch_name)
217
+ else:
218
+ other_chs.append(ch_name)
219
+ lfp_l = [
220
+ lfp_ch
221
+ for lfp_ch in lfp_chs
222
+ if ("_l_" in lfp_ch.lower()) or ("_left_" in lfp_ch.lower())
223
+ ]
224
+ lfp_l.sort()
225
+ lfp_r = [
226
+ lfp_ch
227
+ for lfp_ch in lfp_chs
228
+ if ("_r_" in lfp_ch.lower()) or ("_right_" in lfp_ch.lower())
229
+ ]
230
+ lfp_r.sort()
231
+ lfp_l_refs = [lfp_l[i - 1] if i > 0 else lfp_l[-1] for i, _ in enumerate(lfp_l)]
232
+ lfp_r_refs = [lfp_r[i - 1] if i > 0 else lfp_r[-1] for i, _ in enumerate(lfp_r)]
233
+ ref_idx = list(df.columns).index("rereference")
234
+ if len(ecog_chs) > 1:
235
+ for ecog_ch in ecog_chs:
236
+ df.iloc[df[df["name"] == ecog_ch].index[0], ref_idx] = "average"
237
+ if (
238
+ len(lfp_l) > 1
239
+ ): # if there is only a single channel, the channel would be subtracted from itself
240
+ for i, lfp in enumerate(lfp_l):
241
+ df.iloc[df[df["name"] == lfp].index[0], ref_idx] = lfp_l_refs[i]
242
+ if len(lfp_r) > 1:
243
+ for i, lfp in enumerate(lfp_r):
244
+ df.iloc[df[df["name"] == lfp].index[0], ref_idx] = lfp_r_refs[i]
245
+ for other_ch in other_chs:
246
+ df.iloc[df[df["name"] == other_ch].index[0], ref_idx] = "None"
247
+
248
+ df = df.replace(np.nan, "None")
249
+
250
+ return df
251
+
252
+
253
+ def get_default_channels_from_data(
254
+ data: np.ndarray,
255
+ car_rereferencing: bool = True,
256
+ ):
257
+ """Return default channels dataframe with
258
+ ecog datatype, no bad channels, no targets, common average rereferencing
259
+
260
+ Parameters
261
+ ----------
262
+ data : np.ndarray
263
+ Data array in shape (n_channels, n_time)
264
+ car_rereferencing : bool, optional
265
+ use common average rereferencing, by default True
266
+
267
+ Returns
268
+ -------
269
+ pd.DataFrame
270
+ nm_channel dataframe containing columns:
271
+ - name
272
+ - rereference
273
+ - used
274
+ - target
275
+ - type
276
+ - status
277
+ - new_name
278
+ """
279
+ import pandas as pd
280
+
281
+ ch_name = [f"ch{idx}" for idx in range(data.shape[0])]
282
+ status = ["good" for _ in range(data.shape[0])]
283
+ type_nm = ["ecog" for _ in range(data.shape[0])]
284
+
285
+ if car_rereferencing:
286
+ rereference = ["average" for _ in range(data.shape[0])]
287
+ new_name = [f"{ch}_avgref" for ch in ch_name]
288
+ else:
289
+ rereference = ["None" for _ in range(data.shape[0])]
290
+ new_name = ch_name
291
+
292
+ new_name = [f"{ch}_avgref" for ch in ch_name]
293
+ target = np.array([0 for _ in range(data.shape[0])])
294
+ used = np.array([1 for _ in range(data.shape[0])])
295
+
296
+ df = pd.DataFrame()
297
+ df["name"] = ch_name
298
+ df["rereference"] = rereference
299
+ df["used"] = used
300
+ df["target"] = target
301
+ df["type"] = type_nm
302
+ df["status"] = status
303
+ df["new_name"] = new_name
304
+
305
+ return df