-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathecogtools.py
348 lines (263 loc) · 11.5 KB
/
ecogtools.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
from __future__ import print_function
import pandas as pd
import numpy as np
import mne
import json
import os
import matplotlib.pyplot as plt
import seaborn as sns
class Data:
def __init__(self):
for key, value in self.parameters.items():
setattr(self, key, value)
self.load_data()
self.check_directories()
self.evoked_list = []
def load_physiology_data(self):
"""
Given filepath for ecog file,
return mne raw_data object of ecog time series data.
"""
if self.ecogfile.endswith(".edf"):
self.phys = mne.io.read_raw_edf(self.ecogfile, preload=False)
elif self.ecogfile.endswith(".fif"):
self.phys = mne.io.read_raw_fif(self.ecogfile, preload=False)
def load_behavioral_data(self):
"""
Given filepath for behavioral data json file,
return dataframe of behavioral data to be used
to make events dataframe.
"""
with open(self.behavfile) as fp:
beh = json.load(fp)
dat_list = beh['data']
self.dat = pd.DataFrame(dat_list)
def load_trigger_data(self):
"""
Given filepath for trigger csv file,
return dataframe of trigger data.
"""
self.trig = pd.read_csv(self.trigfile, index_col=0, dtype={'trigger':'int64', 'trigger_index':'int64'})
def melt_events(self):
"""
Given a dataframe dat with one line per trial and a list of
strings giving names of columns to consider as events,
return a dataframe with one line per (trial, event) combination.
"""
id_names = [c for c in self.dat.columns if not c in self.event_names]
evt = pd.melt(self.dat, id_vars=id_names, value_vars=self.event_names, var_name='trigger_name', value_name='cpu_trigger_time')
evt.sort_values(by='cpu_trigger_time', inplace=True)
evt.reset_index(drop=True, inplace=True)
self.evt = evt
def merge_events_and_triggers(self):
"""
Combine behavioral data from the task with triggers from the csv file.
"""
trig_filt = self.trig.query("task == @self.taskname")
trig_filt.reset_index(drop=True, inplace=True)
self.trig_and_behav =pd.concat([self.evt, trig_filt], axis=1)
def define_events(self):
"""
Given dataframe with trigger information from csv file,
create numpy array of tuples that MNE uses to define events
in format (trigger value, 0, trigger number)
where trigger value corresponds to timing in physiology
data (edf).
"""
self.events = np.c_[self.trig_and_behav['trigger_index'].values, np.zeros(self.trig_and_behav.shape[0], dtype='int64'), self.trig_and_behav['trigger'].values]
def load_data(self):
"""
Load all data (physiology-edf, triggers-csv, behavioral-json).
Combine triggers and behavioral data to common dataframe.
Create array for defining events in MNE.
"""
# Load data from file
self.load_physiology_data()
self.load_behavioral_data()
self.load_trigger_data()
# Important information from edf file
self.ch_names = self.phys.ch_names
self.sfreq = self.phys.info['sfreq']
# Unpivot behavioral dataframe
self.melt_events()
# Combine behavioral data and triggers.
self.merge_events_and_triggers()
# Create array for MNE
self.define_events()
def check_directories(self):
folder = "patient_" + self.patient_num + '/' + self.taskname + "_plots/"
if not os.path.exists(folder):
os.makedirs(folder)
self.image_folder = folder
def remove_irrelevant_channels(self, *args):
"""
Removes irrelevant channels from ch_names list for easier plotting
(channels with no ECoG data). Can pass list of channels or use
default list that includes Event, Stim, and two EKG channels.
In both cases, will remove extra C### channels.
"""
if args:
irrelevant_channels = args
else:
irrelevant_channels = ["Event", "STI 014", "EKGL", "EKGR"]
self.ch_names = [value for value in self.ch_names if not value in irrelevant_channels and not value.startswith("C")]
def initialize_epochs_object(self, channels_of_interest, **kwargs):
"""
Given ecog data phys, events, and event_id, plus option tmnin,
tmax, and channels of interest (picks), create MNE epochs object.
"""
if "tmin" not in kwargs:
kwargs["tmin"] = -1.0
if "tmax" not in kwargs:
kwargs["tmax"] = 5.0
channel_indices = [i for i, j in enumerate(self.phys.ch_names) for k in channels_of_interest if j == k]
self.epochs = mne.Epochs(self.phys, self.events, event_id=self.event_id, picks = channel_indices, add_eeg_ref=False, **kwargs)
self.epochs.load_data()
def create_evoked(self, condition):
"""
Average across epochs for one condition of task.
Append to list of all evoked variables created.
Return evoked object.
"""
evoked = self.epochs[condition].average()
self.evoked_list.append(evoked)
return evoked
def compute_power(self, condition, **kwargs):
"""
Computer power and inter-trial coherence using tfr_morlet on one condition
of main epochs variable for task.
"""
power, itc = mne.time_frequency.tfr_morlet(self.epochs[condition], **kwargs)
return power, itc
def compute_diff_power(self, power1, power2):
"""
Create AverageTFR object of ratio of two powers.
"""
combined = power1 - power2
combined.data = power1.data / power2.data
return combined
class ToM_Localizer(Data):
def __init__(self, patient_num):
"""
Class to import and analyze data for ToM Localizer task.
"""
self.patient_num = patient_num
self.taskname = "ToM_Loc"
self.import_parameters()
Data.__init__(self)
self.set_triggers()
def import_parameters(self):
"""
Parameters are .json files saved in
ecog_data_analysis for specific patients
and specific tasks. They are imported and
used as attributes that are passed to class.
"""
filepath = self.patient_num + "_analysis.json"
with open(filepath) as fp:
parameters = json.load(fp)
with open("ToM_Loc_analysis.json") as fp:
parameters_task = json.load(fp)
parameters_task["behavfile"] = parameters["behavfilefolder"]+parameters_task["behavfile"]+parameters["patient_num"]+".json"
parameters_task.update(parameters)
self.parameters = parameters_task
def set_triggers(self):
"""
Add one to trigger numbers for photograph condition
to distinguish trigger events for MNE.
1, 4, 16 (belief) becomes 2, 5, 17 (photograph).
"""
for i in range(len(self.trig_and_behav)):
if self.trig_and_behav.loc[i, "trial_cond"] == "p":
self.events[i, 2] += 1
self.trig_and_behav.loc[i, "trigger"] += 1
class ToM_2010(Data):
def __init__(self, patient_num):
"""
Class to import and analyze data for ToM 2010 task.
"""
self.patient_num = patient_num
self.taskname = "ToM_2010"
self.import_parameters()
Data.__init__(self)
self.set_triggers()
def import_parameters(self):
"""
Parameters are .json files saved in
ecog_data_analysis for specific patients
and specific tasks. They are imported and
used as attributes that are passed to class.
"""
filepath = self.patient_num + "_analysis.json"
with open(filepath) as fp:
parameters = json.load(fp)
with open("ToM_2010_analysis.json") as fp:
parameters_task = json.load(fp)
parameters_task["behavfile"] = parameters["behavfilefolder"]+parameters_task["behavfile"]+parameters["patient_num"]+".json"
parameters_task.update(parameters)
self.parameters = parameters_task
def set_triggers(self):
"""
Add one to trigger numbers to distinguish between
four distinct cominations of events (all within quest_start
for now): mental x physical & expected x unexpected.
"""
for i in range(len(self.trig_and_behav)):
if self.trig_and_behav.loc[i, "trigger_name"] == "quest_start":
if self.trig_and_behav.loc[i, "state"] == "mental" and self.trig_and_behav.loc[i, "condition"] == "unexpected":
self.events[i, 2] += 1
self.trig_and_behav.loc[i, "trigger"] += 1
elif self.trig_and_behav.loc[i, "state"] == "physical":
if self.trig_and_behav.loc[i, "condition"] == "expected":
self.events[i, 2] += 2
self.trig_and_behav.loc[i, "trigger"] += 2
elif self.trig_and_behav.loc[i, "condition"] == "unexpected":
self.events[i, 2] += 3
self.trig_and_behav.loc[i, "trigger"] += 3
for i in range(len(self.trig_and_behav)):
if self.trig_and_behav.loc[i, "trigger_name"] == "time_of_response":
if self.trig_and_behav.loc[i, "state"] == "mental" and self.trig_and_behav.loc[i, "condition"] == "unexpected":
self.events[i, 2] += 1
self.trig_and_behav.loc[i, "trigger"] += 1
elif self.trig_and_behav.loc[i, "state"] == "physical":
if self.trig_and_behav.loc[i, "condition"] == "expected":
self.events[i, 2] += 2
self.trig_and_behav.loc[i, "trigger"] += 2
elif self.trig_and_behav.loc[i, "condition"] == "unexpected":
self.events[i, 2] += 3
self.trig_and_behav.loc[i, "trigger"] += 3
class faces_task(Data):
def __init__(self, patient_num):
"""
Class to import and analyze data for ToM Localizer task.
"""
self.patient_num = patient_num
self.taskname = "faces"
self.import_parameters()
Data.__init__(self)
self.set_triggers()
def import_parameters(self):
"""
Parameters are .json files saved in
ecog_data_analysis for specific patients
and specific tasks. They are imported and
used as attributes that are passed to class.
"""
filepath = self.patient_num + "_analysis.json"
with open(filepath) as fp:
parameters = json.load(fp)
with open("faces_task_analysis.json") as fp:
parameters_task = json.load(fp)
parameters_task["behavfile"] = parameters["behavfilefolder"]+parameters_task["behavfile"]+parameters["patient_num"]+".json"
parameters_task.update(parameters)
self.parameters = parameters_task
def set_triggers(self):
"""
Add one to trigger numbers for photograph condition
to distinguish trigger events for MNE.
1, 4, 16 (belief) becomes 2, 5, 17 (photograph).
"""
for i in range(len(self.trig_and_behav)):
if self.trig_and_behav.loc[i, "type"] == "happy":
self.events[i, 2] += 1
self.trig_and_behav.loc[i, "trigger"] += 1