-
-
Save neuromechanist/c51d93575c7ad6af565d93a2d3b11cc6 to your computer and use it in GitHub Desktop.
Expand the Present Movie annotation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function EEG = expand_events(EEG, stim_file, stim_column, type_name, resample_beyond_thres, remove_unused_cols, cols_to_keep) | |
%EXPAND_EVENTS Expands EEG.event structure with contencts of the stim_file | |
% With complex electrophys tasks such as watching a movie, the task events could | |
% be very long and repetitive across subjects. Instead, we can put the | |
% events in a separate STIM_FILE and expand EEGLAB's EEG.event strucutre | |
% only for processing. This feature will also help to plugin alternative | |
% events, if a researcher comes with their own event markers and | |
% annotation for the same stimulation. | |
% | |
% INPUTS: | |
% EEG: EEGLAB's EEG structure | |
% STIM_FILE: The path to the stimulation file needed for EEG.event | |
% expansion. | |
% STIM_COLUMN: The column that should be used form the stim file to | |
% expand the EEG.event. default is "VALUE". | |
% TYPE_NAME: Since the type column in EEG.event is usually filled | |
% with the Value column in the BIDS *_events.tsv, we expect not fill | |
% this column at all here. However, this results in a EMPTY flag | |
% generated by EEGLAB. To avoid this issue, user can create a filler. | |
% Default is 'stimuli'. | |
% RESAMPLE_BEYOND_5p: In case there is discrepancy between the | |
% key_point times, should the STIM_FILE be resampled beyon 5% of it's | |
% length. If the difference is <5%, the resmapling will be | |
% performed. Default is 0. | |
% REMOVE_UNUSED_COLS: If set to 1, it will remove the EEG.event columns that | |
% were not explicitly used by EEGLAB, or requested by user via COLS_TO_KEEP | |
% from the EEG.event structure. Defaults is 0. | |
% COLS_TO_KEEP: The columns beyond the EEGLAB builin colums that user | |
% want to keep. in EEG.event EEGLAB has reseverd columns in EEG.event, | |
% that are 'type', d'uration', 'latency', 'urevent', and 'epoch'. THE | |
% EEGLAB COLUMNS ARE PROTECTED from removal. Add any other column | |
% that you WANT TO STAY. Defauls is STIM_COLUMN. | |
% | |
% OUTPUTS: | |
% EEG: EEGLAB's EEG strucutre with the expanded EEG.event. | |
% | |
% (c) Seyed Yahya Shirazi, 12/2023 SCCN, INC, UCSD | |
%% Initialize | |
if ~exist('EEG','var') || isempty(EEG) || ~isstruct(EEG), error("No EEG strucutre is detected!"); end | |
if ~exist('stim_file','var') || isempty(stim_file) | |
warning("No explicit STIM_FILE provided, will try to get it from BIDS stim directory"); | |
stim_file = []; % not yet implemented | |
end | |
if ~exist('stim_column','var') || isempty(stim_column) | |
warning("No STIM_COLUMN is provided, will use the VALUE column of the STIM_FILE by default") | |
stim_column = "value"; | |
end | |
if ~exist('type_name','var') || isempty(type_name), type_name = 'stimuli'; else, type_name = char(type_name); end | |
if ~exist('resample_beyond_thres','var') || isempty(resample_beyond_thres), resample_beyond_thres = 0; end | |
if ~exist('remove_unused_cols','var') || isempty(remove_unused_cols), remove_unused_cols = 0; end | |
if ~exist('cols_to_keep','var') || isempty(cols_to_keep), cols_to_keep = ["type", "latency", "urevent", "duration", "epoch", stim_column]; end | |
required_columns = ["onset", "duration"]; | |
discrepancy_threshold = 0.05; | |
%% load and extract STIM-FILE events | |
opts = detectImportOptions(stim_file, "FileType", "text"); | |
opts = setvartype(opts, 'string'); % crtitical to import everything as string/char | |
stim_table = readtable(stim_file, opts); | |
% check if the required columns and stim_colums are presents | |
if ~all(contains(required_columns,stim_table.Properties.VariableNames)) | |
error("required columns (i.e., ONSET and DURATION) are not in the stim file.") | |
end | |
if ~all(contains(stim_column,stim_table.Properties.VariableNames)) | |
error("The stim_columns provided as an input is not included in the stim file.") | |
end | |
% convert onset and duration to double entities | |
stim_table = convertvars(stim_table, required_columns, "double"); | |
%% check the time discrepancy | |
% first find the keys points shared in EEG.event and stim_table | |
EEG_event_keys = string({EEG.event.type}); | |
% find the entries with the same name in each column | |
for s = stim_column | |
keys_present.(s).idx = find(contains(stim_table{:, s}, EEG_event_keys)); | |
if isempty(keys_present.(s).idx) | |
warning("column" + s + " does not contain any of the keywords in the EEG.event.type. Skipping the column.") | |
stim_column(s==stim_column) = []; | |
else | |
% If line below is confusing, EEG_event_keys: row vector, the other variable: column vector. | |
keys_present.(s).map = (stim_table{:, s} == EEG_event_keys); | |
keys_present.(s).eeg_event_idx = find(any(keys_present.(s).map,1)); | |
end | |
end | |
%% identify the time difference | |
discrepancy_flag = 0; | |
for s = stim_column | |
if length(keys_present.(s).idx) == 1 | |
disp("The length of common values for " + s + " is ONE, so there is no time discrepancy.") | |
else | |
keys_present.(s).eeg_timediff = diff([EEG.event(keys_present.(s).eeg_event_idx).latency])/EEG.srate; | |
keys_present.(s).timediff = diff(stim_table{keys_present.(s).idx, "onset"}); | |
keys_present.(s).discrepancy = abs(keys_present.(s).eeg_timediff - keys_present.(s).timediff); | |
disp("The EEG.event and the " + s + " colomn has " + ... | |
string(mean(keys_present.(s).discrepancy)) + " seconds difference"); | |
end | |
if (mean(keys_present.(s).discrepancy) / max(keys_present.(s).eeg_timediff)) > (max(keys_present.(s).eeg_timediff) * discrepancy_threshold) | |
discrepancy_flag = 1; | |
warning("The difference between the EEG.event length and the corresponding events in the TSV file are beyon the threshold") | |
if ~resample_beyond_thres, error("can't correct the timestamps, so will exit w/o results"); end | |
end | |
end | |
%% correct the time difference | |
duplicate_flag = 0; | |
for s = stim_column | |
% this loop should be peformed once for columns with the same insetion points. | |
if find(s==stim_column)>1 && all(keys_present.(s).map == keys_present.(stim_column(1)).map,"all") | |
duplicate_flag = 1; | |
break; | |
end | |
if ~(length(keys_present.(s).idx) == 1) && (discrepancy_flag == 0 || resample_beyond_thres) | |
for i = 1:length(keys_present.(s).discrepancy) | |
correct_ratio = keys_present.(s).eeg_timediff/keys_present.(s).timediff; | |
stim_table{:, "onset"} = (stim_table{:, "onset"} - stim_table{keys_present.(s).idx(2*(i-1)+1), "onset"}) * correct_ratio ... | |
+ stim_table{keys_present.(s).idx(2*(i-1)+1), "onset"}; | |
stim_table{:, "duration"} = stim_table{:, "duration"} * correct_ratio; | |
end | |
end | |
end | |
%% pull a uniform idx to import to EEG.event | |
keys_present.summary.idx = []; | |
keys_present.summary.eeg_event_idx = []; | |
for s = stim_column | |
keys_present.summary.idx = [keys_present.summary.idx, keys_present.(s).idx']; | |
keys_present.summary.eeg_event_idx = [keys_present.summary.eeg_event_idx keys_present.(s).eeg_event_idx]; | |
end | |
[uidx, ia] = unique(keys_present.summary.idx); eeg_uidx = keys_present.summary.eeg_event_idx(ia); | |
[keys_present.summary.uidx, is] = sort(uidx); keys_present.summary.eeg_event_uidx = eeg_uidx(is); | |
%% import the events to EEG.event | |
if length(keys_present.summary.idx) == 1, keys_present.summary.idx(end+1) = height(stim_table); end | |
for i = 1:length(keys_present.summary.uidx) / 2 | |
e0idx = keys_present.summary.eeg_event_uidx(2*(i-1)+1); % idx0 of the segment in EEG.event | |
t0idx = keys_present.summary.uidx(2*(i-1)+1); % idx0 of the segment in the table | |
temp_events = EEG.event(keys_present.summary.eeg_event_uidx(2*i):end); | |
EEG.event(keys_present.summary.eeg_event_uidx(2*i):end) = []; | |
for j = 0:(keys_present.summary.uidx(2*i) - keys_present.summary.uidx(2*(i-1)+1)) | |
EEG.event(e0idx+j).latency = EEG.event(e0idx).latency + ... | |
round((stim_table{t0idx+j,"onset"} - stim_table{t0idx,"onset"}) * EEG.srate); | |
if j ~= 0 && j ~= (keys_present.summary.uidx(2*i) - keys_present.summary.uidx(2*(i-1)+1)) % do not replace EEG.type for the first and last | |
EEG.event(e0idx+j).type = type_name; | |
end | |
for s = stim_column | |
EEG.event(e0idx+j).(s) = char(stim_table{t0idx+j, s}); | |
end | |
end | |
for t = string(fieldnames(temp_events))' | |
for k = 0: (length(temp_events)-1) | |
EEG.event(end+k).(t) = temp_events(k+1).(t); | |
end | |
end | |
end | |
%% delete unused columns | |
% To ensure smooth operation of the EEGLAB suite, sometimes it is good to | |
% remove unnecassary columns. | |
if remove_unused_cols | |
event_fields = string(fieldnames(EEG.event))'; | |
fields_to_remove = event_fields(~contains(event_fields, cols_to_keep)); | |
EEG.event = rmfield(EEG.event, fields_to_remove); | |
end | |
EEG = eeg_checkset(EEG, 'makeur'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
expand_table = "the_present_stimulus-LogLumRatio.tsv"; | |
for e = 1:length(EEG) | |
EEG(e) = expand_events(EEG(e), expand_table, ["shot_number", "LLR"],'shots', 0, 1); | |
end | |
ALLEEG = EEG; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
onset | duration | shot_number | LLR | |
---|---|---|---|---|
0 | n/a | video_start | video_start | |
0 | 7.25 | 1 | n/a | |
7.25 | 3.542 | 2 | -1.557820733 | |
10.792 | 5.208 | 3 | 0.3358234903 | |
16 | 5 | 4 | -0.03306866929 | |
21 | 4.208 | 5 | -0.2070276568 | |
25.208 | 9.375 | 6 | 0.04327900913 | |
34.583 | 0.792 | 7 | -0.1644478802 | |
35.375 | 1.333 | 8 | -0.06762669846 | |
36.708 | 3.167 | 9 | 0.1579466073 | |
39.875 | 3.292 | 10 | 0.2663627968 | |
43.167 | 3.5 | 11 | -0.2664832696 | |
46.667 | 2.333 | 12 | 0.04315495832 | |
49 | 1.917 | 13 | -0.04155285906 | |
50.917 | 4.125 | 14 | 0.04001208653 | |
55.042 | 2.917 | 15 | -0.08485806694 | |
57.958 | 2.042 | 16 | 0.008346347475 | |
60 | 3.958 | 17 | 0.02230250862 | |
63.958 | 1.458 | 18 | -0.06699408597 | |
65.417 | 1.083 | 19 | 0.04519596032 | |
66.5 | 1.917 | 20 | 0.07562682269 | |
68.417 | 2.208 | 21 | -0.09718166316 | |
70.625 | 4.667 | 22 | 0.04645434208 | |
75.292 | 1.667 | 23 | 0.01865315886 | |
76.958 | 0.833 | 24 | 0.02023945652 | |
77.792 | 1.583 | 25 | -0.01044289204 | |
79.375 | 2.292 | 26 | 0.07107341152 | |
81.667 | 4.5 | 27 | -0.07068154365 | |
86.167 | 2.625 | 28 | -0.09355068459 | |
88.792 | 2.542 | 29 | 0.127618289 | |
91.333 | 5.583 | 30 | -0.03570772929 | |
96.917 | 4.458 | 31 | 0.1251853136 | |
101.375 | 3.042 | 32 | -0.1428285775 | |
104.417 | 1.25 | 33 | 0.0998938008 | |
105.667 | 0.792 | 34 | -0.007754262065 | |
106.458 | 1.5 | 35 | -0.06120459462 | |
107.958 | 0.667 | 36 | -0.02361673076 | |
108.625 | 2.208 | 37 | 0.01752653383 | |
110.833 | 3 | 38 | 0.07973702237 | |
113.833 | 2.292 | 39 | -0.08752530512 | |
116.125 | 1 | 40 | 0.05044258775 | |
117.125 | 2.25 | 41 | -0.0466125638 | |
119.375 | 2.708 | 42 | 0.09235464595 | |
122.083 | 3.375 | 43 | -0.1067268907 | |
125.458 | 6.292 | 44 | 0.02212250449 | |
131.75 | 4.833 | 45 | 0.1053584879 | |
136.583 | 3.583 | 46 | -0.06164775268 | |
140.167 | 1.708 | 47 | 0.001148701321 | |
141.875 | 4.292 | 48 | -0.1751332264 | |
146.167 | 4.708 | 49 | 0.1760542644 | |
150.875 | 3.667 | 50 | -0.06821788465 | |
154.542 | 2.958 | 51 | 0.01782704926 | |
157.5 | 3.125 | 52 | -0.0693816018 | |
160.625 | 1.833 | 53 | 0.0899746917 | |
162.458 | 2.792 | 54 | 0.09733030527 | |
165.25 | 6.667 | 55 | -0.2270603551 | |
171.917 | 31.292 | 56 | 0.1188704433 | |
203.208 | n/a | video_stop | video_stop |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment