-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcoh_handle_objects.py
404 lines (334 loc) · 12.2 KB
/
coh_handle_objects.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
"""Methods for creating and handling objects."""
from datetime import datetime
from typing import Any, Union
import numpy as np
import mne
from numpy.typing import NDArray
from coh_handle_entries import create_lambda, rearrange_axes
extra_info_keys = [
"ch_regions",
"ch_subregions",
"ch_hemispheres",
"ch_reref_types",
"ch_epoch_orders",
"metadata",
]
class FillableObject:
"""Creates an empty object that can be filled with attributes.
PARAMETERS
----------
attrs : dict
- The attributes to fill the object with. Keys and values of the
dictionary are added as the attributes of the object and their values,
respectively.
"""
def __init__(self, attrs: dict):
for name, value in attrs.items():
setattr(self, name, value)
def create_extra_info(data: dict) -> dict[dict]:
"""Create a dictionary for holding additional information used in Signal
objects.
PARAMETERS
----------
data : dict
- Data dictionary in the same format as that derived from a Signal object
to extract the extra information from.
RETURNS
-------
extra_info : dict[dict]
- Additional information extracted from the data dictionary.
"""
ch_dict_keys = [
"ch_regions",
"ch_subregions",
"ch_hemispheres",
"ch_reref_types",
"ch_epoch_orders",
]
extra_info = {}
for key in extra_info_keys:
if key in data.keys():
if key in ch_dict_keys:
extra_info[key] = {
name: data[key][name] for name in data["ch_names"]
}
else:
extra_info[key] = data[key]
return extra_info
def create_mne_data_object(
data: Union[list, NDArray],
data_dimensions: list[str],
ch_names: list[str],
ch_types: list[str],
sfreq: Union[int, float],
ch_coords: Union[list[list[Union[int, float]]], None] = None,
annotations: mne.Annotations | None = None,
meas_date: datetime | None = None,
events: Union[np.ndarray, None] = None,
tmin: float = 0.0,
event_id: Union[dict, None] = None,
subject_info: Union[dict, None] = None,
verbose: bool = True,
) -> tuple[Union[mne.io.Raw, mne.Epochs], list[str]]:
"""Creates an MNE Raw or Epochs object, depending on the structure of the
data.
PARAMETERS
----------
data : list | numpy array
- The data of the object.
data_dimensions : list[str]
- Names of axes in 'data'.
- If "epochs" is in the dimensions, an MNE Epochs object is created,
otherwise an MNE Raw object is created.
- MNE Epochs objects expect data to be in the dimensions ["epochs",
"channels", "timepoints"], which the data axes are rearranged to if they
do not match.
- MNE Raw objects expect data to be in the dimensions ["channels",
"timepoints"], which the data axes are rearranged to if they do not
match.
ch_names : list[str]
- Names of the channels.
ch_types : list[str]
- Types of the channels.
sfreq : Union[int, float]
- Sampling frequency of the data.
ch_coords : list[list[int | float]] | None; default None
- Coordinates of the channels.
annotations : MNE Annotations | None (default None)
Annotations to add to the Raw object.
meas_date : datetime | None (default None)
Measurement date of the date to add to the Raw object.
events : numpy ndarray | None; default None
- Events to add to the Epochs object.
tmin : float; default 0.0
- Time of the first sample before an event in the epochs to add to the
Epochs object.
event_id : dict | None; default None
- Event IDs to add to the Epochs object.
subject_info : dict | None; default None
- Information about the subject from which the data was collected.
verbose : bool; default True
- Verbosity setting of the generated MNE object.
RETURNS
-------
data_object : MNE Raw | MNE Epochs
- The generated MNE object containing the data.
new_dimensions : list[str]
- Names of the new axes in 'data'.
- ["epochs", "channels", "timepoints"] if 'data_object' is an MNE Epochs
object.
- ["channels", "timepoints"] if 'data_object' is an MNE Raw object.
"""
data_info = create_mne_data_info(
ch_names=ch_names,
ch_types=ch_types,
sfreq=sfreq,
subject_info=subject_info,
verbose=verbose,
)
if "epochs" in data_dimensions:
new_dimensions = ["epochs", "channels", "timepoints"]
data = rearrange_axes(
obj=data,
old_order=data_dimensions,
new_order=new_dimensions,
)
data_object = mne.EpochsArray(
data=data,
info=data_info,
events=events,
tmin=tmin,
event_id=event_id,
verbose=verbose,
)
else:
new_dimensions = ["channels", "timepoints"]
data = rearrange_axes(
obj=data,
old_order=data_dimensions,
new_order=new_dimensions,
)
data_object = mne.io.RawArray(
data=data, info=data_info, verbose=verbose
)
data_object.set_meas_date(meas_date)
if annotations is not None:
data_object.set_annotations(annotations)
if ch_coords is not None:
data_object._set_channel_positions(pos=ch_coords, names=ch_names)
return data_object, new_dimensions
def create_mne_data_info(
ch_names: list[str],
ch_types: list[str],
sfreq: Union[int, float],
subject_info: Union[dict, None] = None,
verbose: bool = True,
) -> mne.Info:
"""Create an MNE Info object.
PARAMETERS
----------
ch_names : list[str]
- Names of the channels.
ch_types : list[str]
- Types of the channels.
sfreq : Union[int, float]
- Sampling frequency of the data.
subject_info : dict | None; default None
- Information about the subject from which the data was collected.
verbose : bool; default True
- Verbosity setting of the generated MNE object.
RETURNS
-------
data_info : MNE Info
- Information about the data.
"""
data_info = mne.create_info(
ch_names=ch_names, sfreq=sfreq, ch_types=ch_types, verbose=verbose
)
if subject_info is not None:
data_info["subject_info"] = subject_info
return data_info
def nested_changes_list(contents: list, changes: dict) -> None:
"""Makes changes to the specified values occurring within nested
dictionaries of lists of a parent list.
PARAMETERS
----------
contents : list
- The list containing nested dictionaries and lists whose values should be
changed.
changes : dict
- Dictionary specifying the changes to make, with the keys being the
values that should be changed, and the values being what the values
should be changed to.
"""
for value in contents:
if isinstance(value, list):
nested_changes_list(contents=value, changes=changes)
elif isinstance(value, dict):
nested_changes_dict(contents=value, changes=changes)
else:
if value in changes.keys():
value = changes[value]
def nested_changes_dict(contents: dict, changes: dict) -> None:
"""Makes changes to the specified values occurring within nested
dictionaries or lists of a parent dictionary.
PARAMETERS
----------
contents : dict
- The dictionary containing nested dictionaries and lists whose values
should be changed.
changes : dict
- Dictionary specifying the changes to make, with the keys being the
values that should be changed, and the values being what the values
should be changed to.
"""
for key, value in contents.items():
if isinstance(value, list):
nested_changes_list(contents=value, changes=changes)
elif isinstance(value, dict):
nested_changes_dict(contents=value, changes=changes)
else:
if value in changes.keys():
contents[key] = changes[value]
def nested_changes(contents: Union[dict, list], changes: dict) -> None:
"""Makes changes to the specified values occurring within nested
dictionaries or lists of a parent dictionary or list.
PARAMETERS
----------
contents : dict | list
- The dictionary or list containing nested dictionaries and lists whose
values should be changed.
changes : dict
- Dictionary specifying the changes to make, with the keys being the
values that should be changed, and the values being what the values
should be changed to.
"""
if isinstance(contents, dict):
nested_changes_dict(contents=contents, changes=changes)
elif isinstance(contents, list):
nested_changes_list(contents=contents, changes=changes)
else:
raise TypeError(
"Error when changing nested elements of an object:\nProcessing "
f"objects of type '{type(contents)}' is not supported. Only 'list' "
"and 'dict' objects can be processed."
)
def numpy_to_python(obj: Union[dict, list, np.generic]) -> Any:
"""Iterates through all entries of an object and converts any numpy elements
into their base Python object types, e.g. float32 into float, ndarray into
list, etc...
PARAMETERS
----------
obj : dict | list | numpy generic
- The object whose entries should be iterated through and, if numpy
objects, converted to their equivalent base Python types.
RETURNS
-------
Any
- The object whose entries, if numpy objects, have been converted to their
equivalent base Python types.
"""
if isinstance(obj, dict):
return numpy_to_python_dict(obj)
elif isinstance(obj, list):
return numpy_to_python_list(obj)
elif isinstance(obj, np.generic):
return obj.item()
else:
raise TypeError(
"Error when changing nested elements of an object:\nProcessing "
f"objects of type '{type(obj)}' is not supported. Only 'list' "
", 'dict', and 'numpy generic' objects can be processed."
)
def numpy_to_python_dict(obj: dict) -> dict:
"""Iterates through all entries of a dictionary and converts any numpy
elements into their base Python object types, e.g. float32 into float,
ndarray into list, etc...
PARAMETERS
----------
obj : dict
- The dictionary whose entries should be iterated through and, if numpy
objects, converted to their equivalent base Python types.
RETURNS
-------
new_obj : dict
- The dictionary whose entries, if numpy objects, have been converted to
their equivalent base Python types.
"""
new_obj = {}
for key, value in obj.items():
if isinstance(value, list):
new_obj[key] = numpy_to_python_list(value)
elif isinstance(value, dict):
new_obj[key] = numpy_to_python_dict(value)
elif type(value).__module__ == np.__name__:
new_obj[key] = getattr(value, "tolist", create_lambda(value))()
else:
new_obj[key] = value
return new_obj
def numpy_to_python_list(obj: list) -> list:
"""Iterates through all entries of a list and converts any numpy elements
into their base Python object types, e.g. float32 into float, ndarray into
list, etc...
PARAMETERS
----------
obj : list
- The list whose entries should be iterated through and, if numpy objects,
converted to their equivalent base Python types.
RETURNS
-------
new_obj : list
- The list whose entries, if numpy objects, have been converted to their
equivalent base Python types.
"""
new_obj = []
for value in obj:
if isinstance(value, list):
new_obj.append(numpy_to_python_list(value))
elif isinstance(value, dict):
new_obj.append(numpy_to_python_dict(value))
elif type(value).__module__ == np.__name__:
new_obj.append(getattr(value, "tolist", create_lambda(value))())
else:
new_obj.append(value)
return new_obj