py-neuromodulation 0.0.4__py3-none-any.whl → 0.0.5__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 (80) 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/FieldTrip.py +589 -589
  5. py_neuromodulation/__init__.py +74 -13
  6. py_neuromodulation/_write_example_dataset_helper.py +83 -65
  7. py_neuromodulation/data/README +6 -6
  8. py_neuromodulation/data/dataset_description.json +8 -8
  9. py_neuromodulation/data/participants.json +32 -32
  10. py_neuromodulation/data/participants.tsv +2 -2
  11. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -5
  12. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -11
  13. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -11
  14. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -18
  15. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -35
  16. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -13
  17. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -2
  18. py_neuromodulation/grid_cortex.tsv +40 -40
  19. py_neuromodulation/liblsl/libpugixml.so.1.12 +0 -0
  20. py_neuromodulation/liblsl/linux/bionic_amd64/liblsl.1.16.2.so +0 -0
  21. py_neuromodulation/liblsl/linux/bookworm_amd64/liblsl.1.16.2.so +0 -0
  22. py_neuromodulation/liblsl/linux/focal_amd46/liblsl.1.16.2.so +0 -0
  23. py_neuromodulation/liblsl/linux/jammy_amd64/liblsl.1.16.2.so +0 -0
  24. py_neuromodulation/liblsl/linux/jammy_x86/liblsl.1.16.2.so +0 -0
  25. py_neuromodulation/liblsl/linux/noble_amd64/liblsl.1.16.2.so +0 -0
  26. py_neuromodulation/liblsl/macos/amd64/liblsl.1.16.2.dylib +0 -0
  27. py_neuromodulation/liblsl/macos/arm64/liblsl.1.16.0.dylib +0 -0
  28. py_neuromodulation/liblsl/windows/amd64/liblsl.1.16.2.dll +0 -0
  29. py_neuromodulation/liblsl/windows/x86/liblsl.1.16.2.dll +0 -0
  30. py_neuromodulation/nm_IO.py +413 -417
  31. py_neuromodulation/nm_RMAP.py +496 -531
  32. py_neuromodulation/nm_analysis.py +993 -1074
  33. py_neuromodulation/nm_artifacts.py +30 -25
  34. py_neuromodulation/nm_bispectra.py +154 -168
  35. py_neuromodulation/nm_bursts.py +292 -198
  36. py_neuromodulation/nm_coherence.py +251 -205
  37. py_neuromodulation/nm_database.py +149 -0
  38. py_neuromodulation/nm_decode.py +918 -992
  39. py_neuromodulation/nm_define_nmchannels.py +300 -302
  40. py_neuromodulation/nm_features.py +144 -116
  41. py_neuromodulation/nm_filter.py +219 -219
  42. py_neuromodulation/nm_filter_preprocessing.py +79 -91
  43. py_neuromodulation/nm_fooof.py +139 -159
  44. py_neuromodulation/nm_generator.py +45 -37
  45. py_neuromodulation/nm_hjorth_raw.py +52 -73
  46. py_neuromodulation/nm_kalmanfilter.py +71 -58
  47. py_neuromodulation/nm_linelength.py +21 -33
  48. py_neuromodulation/nm_logger.py +66 -0
  49. py_neuromodulation/nm_mne_connectivity.py +149 -112
  50. py_neuromodulation/nm_mnelsl_generator.py +90 -0
  51. py_neuromodulation/nm_mnelsl_stream.py +116 -0
  52. py_neuromodulation/nm_nolds.py +96 -93
  53. py_neuromodulation/nm_normalization.py +173 -214
  54. py_neuromodulation/nm_oscillatory.py +423 -448
  55. py_neuromodulation/nm_plots.py +585 -612
  56. py_neuromodulation/nm_preprocessing.py +83 -0
  57. py_neuromodulation/nm_projection.py +370 -394
  58. py_neuromodulation/nm_rereference.py +97 -95
  59. py_neuromodulation/nm_resample.py +59 -50
  60. py_neuromodulation/nm_run_analysis.py +325 -435
  61. py_neuromodulation/nm_settings.py +289 -68
  62. py_neuromodulation/nm_settings.yaml +244 -0
  63. py_neuromodulation/nm_sharpwaves.py +423 -401
  64. py_neuromodulation/nm_stats.py +464 -480
  65. py_neuromodulation/nm_stream.py +398 -0
  66. py_neuromodulation/nm_stream_abc.py +166 -218
  67. py_neuromodulation/nm_types.py +193 -0
  68. {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/METADATA +29 -26
  69. py_neuromodulation-0.0.5.dist-info/RECORD +83 -0
  70. {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/WHEEL +1 -1
  71. {py_neuromodulation-0.0.4.dist-info → py_neuromodulation-0.0.5.dist-info}/licenses/LICENSE +21 -21
  72. py_neuromodulation/nm_EpochStream.py +0 -92
  73. py_neuromodulation/nm_across_patient_decoding.py +0 -927
  74. py_neuromodulation/nm_cohortwrapper.py +0 -435
  75. py_neuromodulation/nm_eval_timing.py +0 -239
  76. py_neuromodulation/nm_features_abc.py +0 -39
  77. py_neuromodulation/nm_settings.json +0 -338
  78. py_neuromodulation/nm_stream_offline.py +0 -359
  79. py_neuromodulation/utils/_logging.py +0 -24
  80. py_neuromodulation-0.0.4.dist-info/RECORD +0 -72
@@ -1,302 +1,300 @@
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 nm_channels."""
2
+
3
+ from collections.abc import Iterable
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: list | str = "default",
15
+ bads: list[str] | None = None,
16
+ new_names: str | list[str] = "default",
17
+ ecog_only: bool = False,
18
+ used_types: Iterable[str] | None = ("ecog", "dbs", "seeg"),
19
+ target_keywords: Iterable[str] | None = ("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 isinstance(used_types, str):
90
+ used_types = [
91
+ used_types
92
+ ] # Even if the user passes only ("ecog"), the if statement bellow will work
93
+ used_list = []
94
+ for ch_type in ch_types:
95
+ if any(use_type.lower() == ch_type.lower() for use_type in used_types):
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 isinstance(target_keywords, 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(df=df, ch_names=ch_names, ch_types=ch_types)
126
+ else:
127
+ raise ValueError(
128
+ "`reference` must be either `default`, `None` or "
129
+ "an iterable of new reference channel names. "
130
+ f"Got: {reference}."
131
+ )
132
+
133
+ elif isinstance(reference, list):
134
+ if len(ch_names) != len(reference):
135
+ raise ValueError(
136
+ "Number of `ch_names` and `reference` must match."
137
+ f"Got: {len(ch_names)} `ch_names` and {len(reference)}"
138
+ " `references`."
139
+ )
140
+ df["rereference"] = reference
141
+ elif not reference:
142
+ df.loc[:, "rereference"] = "None"
143
+ else:
144
+ raise ValueError(
145
+ "`reference` must be either `default`, None or "
146
+ "an iterable of new reference channel names. "
147
+ f"Got: {reference}."
148
+ )
149
+
150
+ if bads:
151
+ if isinstance(bads, str):
152
+ bads = [bads]
153
+ df["status"] = ["bad" if ch in bads else "good" for ch in ch_names]
154
+ df.loc[df["status"] == "bad", "used"] = (
155
+ 0 # setting bad channels to not being used
156
+ )
157
+ else:
158
+ df["status"] = "good"
159
+
160
+ if not new_names:
161
+ df["new_name"] = ch_names
162
+ elif isinstance(new_names, str):
163
+ if new_names.lower() != "default":
164
+ raise ValueError(
165
+ "`new_names` must be either `default`, None or "
166
+ "an iterable of new channel names. Got: "
167
+ f"{new_names}."
168
+ )
169
+ new_names = []
170
+ for name, ref in zip(df["name"], df["rereference"]):
171
+ if ref == "None":
172
+ new_names.append(name)
173
+ elif isinstance(ref, float):
174
+ if np.isnan(ref):
175
+ new_names.append(name)
176
+ elif ref == "average":
177
+ new_names.append(name + "_avgref")
178
+ else:
179
+ new_names.append(name + "_" + ref)
180
+ df["new_name"] = new_names
181
+ elif hasattr(new_names, "__iter__"):
182
+ if len(new_names) != len(ch_names):
183
+ raise ValueError(
184
+ "Number of `ch_names` and `new_names` must match."
185
+ f" Got: {len(ch_names)} `ch_names` and {len(new_names)}"
186
+ " `new_names`."
187
+ )
188
+ else:
189
+ df["new_name"] = ch_names
190
+ else:
191
+ raise ValueError(
192
+ "`new_names` must be either `default`, None or"
193
+ f" an iterable of new channel names. Got: {new_names}."
194
+ )
195
+ return df
196
+
197
+
198
+ def _get_default_references(
199
+ df: pd.DataFrame, ch_names: list[str], ch_types: list[str]
200
+ ) -> pd.DataFrame:
201
+ """Add references with default settings (ECOG CAR, LFP bipolar)."""
202
+ ecog_chs = []
203
+ lfp_chs = []
204
+ other_chs = []
205
+ for ch_name, ch_type in zip(ch_names, ch_types):
206
+ if "ecog" in ch_type.lower() or "ecog" in ch_name.lower():
207
+ ecog_chs.append(ch_name)
208
+ elif any(
209
+ lfp_type in ch_type.lower() or lfp_type in ch_name.lower()
210
+ for lfp_type in _LFP_TYPES
211
+ ):
212
+ lfp_chs.append(ch_name)
213
+ else:
214
+ other_chs.append(ch_name)
215
+ lfp_l = [
216
+ lfp_ch
217
+ for lfp_ch in lfp_chs
218
+ if ("_l_" in lfp_ch.lower()) or ("_left_" in lfp_ch.lower())
219
+ ]
220
+ lfp_l.sort()
221
+ lfp_r = [
222
+ lfp_ch
223
+ for lfp_ch in lfp_chs
224
+ if ("_r_" in lfp_ch.lower()) or ("_right_" in lfp_ch.lower())
225
+ ]
226
+ lfp_r.sort()
227
+ lfp_l_refs = [lfp_l[i - 1] if i > 0 else lfp_l[-1] for i, _ in enumerate(lfp_l)]
228
+ lfp_r_refs = [lfp_r[i - 1] if i > 0 else lfp_r[-1] for i, _ in enumerate(lfp_r)]
229
+ ref_idx = list(df.columns).index("rereference")
230
+ if len(ecog_chs) > 1:
231
+ for ecog_ch in ecog_chs:
232
+ df.iloc[df[df["name"] == ecog_ch].index[0], ref_idx] = "average"
233
+ if (
234
+ len(lfp_l) > 1
235
+ ): # if there is only a single channel, the channel would be subtracted from itself
236
+ for i, lfp in enumerate(lfp_l):
237
+ df.iloc[df[df["name"] == lfp].index[0], ref_idx] = lfp_l_refs[i]
238
+ if len(lfp_r) > 1:
239
+ for i, lfp in enumerate(lfp_r):
240
+ df.iloc[df[df["name"] == lfp].index[0], ref_idx] = lfp_r_refs[i]
241
+ for other_ch in other_chs:
242
+ df.iloc[df[df["name"] == other_ch].index[0], ref_idx] = "None"
243
+
244
+ df = df.replace(np.nan, "None")
245
+
246
+ return df
247
+
248
+
249
+ def get_default_channels_from_data(
250
+ data: np.ndarray,
251
+ car_rereferencing: bool = True,
252
+ ):
253
+ """Return default nm_channels dataframe with
254
+ ecog datatype, no bad channels, no targets, common average rereferencing
255
+
256
+ Parameters
257
+ ----------
258
+ data : np.ndarray
259
+ Data array in shape (n_channels, n_time)
260
+ car_rereferencing : bool, optional
261
+ use common average rereferencing, by default True
262
+
263
+ Returns
264
+ -------
265
+ pd.DataFrame
266
+ nm_channel dataframe containing columns:
267
+ - name
268
+ - rereference
269
+ - used
270
+ - target
271
+ - type
272
+ - status
273
+ - new_name
274
+ """
275
+
276
+ ch_name = [f"ch{idx}" for idx in range(data.shape[0])]
277
+ status = ["good" for _ in range(data.shape[0])]
278
+ type_nm = ["ecog" for _ in range(data.shape[0])]
279
+
280
+ if car_rereferencing:
281
+ rereference = ["average" for _ in range(data.shape[0])]
282
+ new_name = [f"{ch}_avgref" for ch in ch_name]
283
+ else:
284
+ rereference = ["None" for _ in range(data.shape[0])]
285
+ new_name = ch_name
286
+
287
+ new_name = [f"{ch}_avgref" for ch in ch_name]
288
+ target = np.array([0 for _ in range(data.shape[0])])
289
+ used = np.array([1 for _ in range(data.shape[0])])
290
+
291
+ df = pd.DataFrame()
292
+ df["name"] = ch_name
293
+ df["rereference"] = rereference
294
+ df["used"] = used
295
+ df["target"] = target
296
+ df["type"] = type_nm
297
+ df["status"] = status
298
+ df["new_name"] = new_name
299
+
300
+ return df