analyse_02_preprocess_pictureword.m 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. %% Setup
  2. % paths to data
  3. eeg_path = fullfile('raw_data', 'eeg-pc', 'pictureword');
  4. beh_path = fullfile('raw_data', 'stim-pc', 'data', 'pictureword');
  5. % import eeglab (assumes eeglab has been added to path), e.g.
  6. addpath('C:/EEGLAB/eeglab2020_0')
  7. [ALLEEG, EEG, CURRENTSET, ALLCOM] = eeglab;
  8. % This script uses fastica algorithm for ICA, so FastICA needs to be on the path, e.g.
  9. addpath('C:/EEGLAB/FastICA_25')
  10. % region of interest for trial-level ROI average
  11. roi = {'TP7', 'CP5', 'P7', 'P5', 'P9', 'PO7', 'PO3', 'O1'};
  12. % cutoff probability for identifying eye and muscle related ICA components with ICLabel
  13. icl_cutoff = 0.85;
  14. % sigma parameter for ASR
  15. asr_sigma = 20;
  16. %% Clear output folders
  17. delete(fullfile('max_elec_data', '*.csv'))
  18. delete(fullfile('sample_data', '*.csv'))
  19. %% Import lab book
  20. % handle commas in vectors
  21. lab_book_file = fullfile('raw_data', 'stim-pc', 'participants.csv');
  22. lab_book_raw_dat = fileread(lab_book_file);
  23. [regstart, regend] = regexp(lab_book_raw_dat, '\[.*?\]');
  24. for regmatch_i = 1:numel(regstart)
  25. str_i = lab_book_raw_dat(regstart(regmatch_i):regend(regmatch_i));
  26. str_i(str_i==',') = '.';
  27. lab_book_raw_dat(regstart(regmatch_i):regend(regmatch_i)) = str_i;
  28. end
  29. lab_book_fixed_file = fullfile('raw_data', 'stim-pc', 'participants_tmp.csv');
  30. lab_book_fixed_conn = fopen(lab_book_fixed_file, 'w');
  31. fprintf(lab_book_fixed_conn, lab_book_raw_dat);
  32. fclose(lab_book_fixed_conn);
  33. lab_book_readopts = detectImportOptions(lab_book_fixed_file, 'VariableNamesLine', 1, 'Delimiter', ',');
  34. % read subject ids as class character
  35. lab_book_readopts.VariableTypes{strcmp(lab_book_readopts.SelectedVariableNames, 'subj_id')} = 'char';
  36. lab_book = readtable(lab_book_fixed_file, lab_book_readopts);
  37. delete(lab_book_fixed_file)
  38. %% Count the total number of excluded electrodes
  39. n_bads = 0;
  40. n_bads_per_s = zeros(size(lab_book, 1), 0);
  41. for subject_nr = 1:size(lab_book, 1)
  42. bad_channels = eval(strrep(strrep(strrep(lab_book.pw_bad_channels{subject_nr}, '[', '{'), ']', '}'), '.', ','));
  43. n_bads_per_s(subject_nr) = numel(bad_channels);
  44. n_bads = n_bads + numel(bad_channels);
  45. end
  46. perc_bads = n_bads / (64 * size(lab_book, 1)) * 100;
  47. %% Import max electrode info
  48. % this contains participants' maximal electrodes for the N170 from the
  49. % localisation task
  50. max_elecs = readtable('max_elecs.csv');
  51. %% Iterate over subjects
  52. % record trial exclusions
  53. total_excl_trials_incorr = zeros(1, size(lab_book, 1));
  54. total_excl_trials_rt = zeros(1, size(lab_book, 1));
  55. n_bad_ica = zeros(size(lab_book, 1), 0);
  56. % table that will contain all sample-level tables
  57. all_sample_level = table();
  58. for subject_nr = 1:size(lab_book, 1)
  59. subject_id = lab_book.subj_id{subject_nr};
  60. fprintf('\n\n Subject Iteration %g/%g, ID: %s\n', subject_nr, size(lab_book, 1), subject_id)
  61. %% get subject-specific info from lab book
  62. exclude = lab_book.exclude(subject_nr);
  63. bad_channels = eval(strrep(strrep(strrep(lab_book.pw_bad_channels{subject_nr}, '[', '{'), ']', '}'), '.', ','));
  64. bad_trigger_indices = eval(strrep(lab_book.pw_bad_trigger_indices{subject_nr}, '.', ','));
  65. % add PO4 to bad channels, which seems to be consistently noisy, even when not marked as bad
  66. if sum(strcmp('PO4', bad_channels))==0
  67. bad_channels(numel(bad_channels)+1) = {'PO4'};
  68. end
  69. %% abort if excluded
  70. if exclude
  71. fprintf('Subject %s excluded. Preprocessing aborted.\n', subject_id)
  72. fprintf('Lab book note: %s\n', lab_book.note{subject_nr})
  73. continue
  74. end
  75. %% load participant's data
  76. % load raw eeg
  77. raw_datapath = fullfile(eeg_path, append(subject_id, '.bdf'));
  78. % abort if no EEG data collected yet
  79. if ~isfile(raw_datapath)
  80. fprintf('Subject %s skipped: no EEG data found\n', subject_id)
  81. continue
  82. end
  83. EEG = pop_biosig(raw_datapath, 'importevent', 'on', 'rmeventchan', 'off');
  84. % load behavioural
  85. all_beh_files = dir(beh_path);
  86. beh_regex_matches = regexpi({all_beh_files.name}, append('^', subject_id, '_.+\.csv$'), 'match');
  87. regex_emptymask = cellfun('isempty', beh_regex_matches);
  88. beh_regex_matches(regex_emptymask) = [];
  89. subj_beh_files = cellfun(@(x) x{:}, beh_regex_matches, 'UniformOutput', false);
  90. if size(subj_beh_files)>1
  91. fprintf('%g behavioural files found?\n', size(subj_beh_files))
  92. break
  93. end
  94. beh_datapath = fullfile(beh_path, subj_beh_files{1});
  95. beh = readtable(beh_datapath);
  96. %% Set data features
  97. % set channel locations
  98. orig_locs = EEG.chanlocs;
  99. EEG.chanlocs = pop_chanedit(EEG.chanlocs, 'load', {'BioSemi64.loc', 'filetype', 'loc'}); % doesn't match order for the data
  100. % set channel types
  101. for ch_nr = 1:64
  102. EEG.chanlocs(ch_nr).type = 'EEG';
  103. end
  104. for ch_nr = 65:72
  105. EEG.chanlocs(ch_nr).type = 'EOG';
  106. end
  107. for ch_nr = 73:79
  108. EEG.chanlocs(ch_nr).type = 'MISC';
  109. end
  110. for ch_nr = 65:79
  111. EEG.chanlocs(ch_nr).theta = [];
  112. EEG.chanlocs(ch_nr).radius = [];
  113. EEG.chanlocs(ch_nr).sph_theta = [];
  114. EEG.chanlocs(ch_nr).sph_phi = [];
  115. EEG.chanlocs(ch_nr).X = [];
  116. EEG.chanlocs(ch_nr).Y = [];
  117. EEG.chanlocs(ch_nr).Z = [];
  118. end
  119. % change the order of channels in EEG.data to match the new order in chanlocs
  120. data_reordered = EEG.data;
  121. for ch_nr = 1:64
  122. % make sure the new eeg data array matches the listed order
  123. ch_lab = EEG.chanlocs(ch_nr).labels;
  124. orig_locs_idx = find(strcmp(lower({orig_locs.labels}), lower(ch_lab)));
  125. data_reordered(ch_nr, :) = EEG.data(orig_locs_idx, :);
  126. end
  127. EEG.data = data_reordered;
  128. % remove unused channels
  129. EEG = pop_select(EEG, 'nochannel', 69:79);
  130. % remove bad channels
  131. ur_chanlocs = EEG.chanlocs; % store a copy of the full channel locations before removing (for later interpolation)
  132. bad_channels_indices = find(ismember(lower({EEG.chanlocs.labels}), lower(bad_channels)));
  133. EEG = pop_select(EEG, 'nochannel', bad_channels_indices);
  134. %% Identify events (trials)
  135. % make the sopen function happy
  136. x = fileparts( which('sopen') );
  137. rmpath(x);
  138. addpath(x,'-begin');
  139. % build the events manually from the raw eeg file (pop_biosig removes event offsets)
  140. % NB: this assumes no resampling between reading the BDF file and now
  141. bdf_dat = sopen(raw_datapath, 'r', [0, Inf], 'OVERFLOWDETECTION:OFF');
  142. event_types = bdf_dat.BDF.Trigger.TYP;
  143. event_pos = bdf_dat.BDF.Trigger.POS;
  144. event_time = EEG.times(event_pos);
  145. sclose(bdf_dat);
  146. clear bdf_dat;
  147. triggers = struct(...
  148. 'off', 0,...
  149. 'A1', 1,...
  150. 'A2', 2,...
  151. 'practice', 25,...
  152. 'image', 99);
  153. % add 61440 to each trigger value (because of number of bits in pp)
  154. trigger_labels = fieldnames(triggers);
  155. for field_nr = 1:numel(trigger_labels)
  156. triggers.(trigger_labels{field_nr}) = triggers.(trigger_labels{field_nr}) + 61440;
  157. end
  158. % remove the first trigger if it is at time 0 and has a value which isn't a recognised trigger
  159. if (event_time(1)==0 && ~ismember(event_types(1), [triggers.off, triggers.A1, triggers.A2, triggers.practice, triggers.image]))
  160. event_types(1) = [];
  161. event_pos(1) = [];
  162. event_time(1) = [];
  163. end
  164. % remove the new first trigger if it has a value of off
  165. if (event_types(1)==triggers.off)
  166. event_types(1) = [];
  167. event_pos(1) = [];
  168. event_time(1) = [];
  169. end
  170. % check every second trigger is an offset
  171. offset_locs = find(event_types==triggers.off);
  172. if any(offset_locs' ~= 2:2:numel(event_types))
  173. fprintf('Expected each second trigger to be an off?')
  174. break
  175. end
  176. % check every first trigger is non-zero
  177. onset_locs = find(event_types~=triggers.off);
  178. if any(onset_locs' ~= 1:2:numel(event_types))
  179. fprintf('Expected each first trigger to be an event?')
  180. break
  181. end
  182. % create the events struct manually
  183. events_onset_types = event_types(onset_locs);
  184. events_onsets = event_pos(onset_locs);
  185. events_offsets = event_pos(offset_locs);
  186. events_durations = events_offsets - events_onsets;
  187. EEG.event = struct();
  188. for event_nr = 1:numel(events_onsets)
  189. EEG.event(event_nr).type = events_onset_types(event_nr);
  190. EEG.event(event_nr).latency = events_onsets(event_nr);
  191. EEG.event(event_nr).offset = events_offsets(event_nr);
  192. EEG.event(event_nr).duration = events_durations(event_nr);
  193. end
  194. % copy the details over to urevent
  195. EEG.urevent = EEG.event;
  196. % record the urevent
  197. for event_nr = 1:numel(events_onsets)
  198. EEG.event(event_nr).urevent = event_nr;
  199. end
  200. % remove bad events recorded in lab book (misfired triggers)
  201. EEG = pop_editeventvals(EEG, 'delete', find(ismember([EEG.event.urevent], bad_trigger_indices)));
  202. % remove practice trials
  203. EEG = pop_editeventvals(EEG, 'delete', find(ismember([EEG.event.type], triggers.practice)));
  204. % remove triggers saying that image is displayed
  205. EEG = pop_editeventvals(EEG, 'delete', find(ismember([EEG.event.type], triggers.image)));
  206. % check the events make sense
  207. if sum(~ismember([EEG.event.type], [triggers.A1, triggers.A2])) > 0
  208. fprintf('Unexpected trial types?\n')
  209. break
  210. end
  211. if numel({EEG.event.type})~=200
  212. fprintf('%g trial triggers detected?\n', numel({EEG.event.type}))
  213. break
  214. end
  215. if sum(ismember([EEG.event.type], [triggers.A1])) ~= sum(ismember([EEG.event.type], [triggers.A2]))
  216. fprintf('Unequal number of congruent and incongruent trials?\n')
  217. break
  218. end
  219. % add the trials' onsets, offsets, durations, and triggers to the behavioural data
  220. beh.event = zeros(size(beh, 1), 1);
  221. beh.latency = zeros(size(beh, 1), 1);
  222. for row_nr = 1:size(beh, 1)
  223. cond_i = beh.condition(row_nr);
  224. beh.event(row_nr) = triggers.(cond_i{:});
  225. beh.latency(row_nr) = EEG.event(row_nr).latency;
  226. beh.offset(row_nr) = EEG.event(row_nr).offset;
  227. beh.duration(row_nr) = EEG.event(row_nr).duration;
  228. beh.duration_ms(row_nr) = (EEG.event(row_nr).duration * 1000/EEG.srate) - 500; % minus 500 as event timer starts at word presentation, but rt timer starts once word turns green
  229. end
  230. % check events expected in beh are same as those in the events struct
  231. if any(beh.event' ~= [EEG.event.type])
  232. fprintf('%g mismatches between behavioural data and triggers?\n', sum(beh.event' ~= [EEG.event.type]))
  233. break
  234. end
  235. % check the difference between the durations and the response times (should be very small)
  236. % hist(beh.rt - beh.duration_ms, 100)
  237. % plot(beh.rt, beh.duration, 'o')
  238. % record trial numbers in EEG.event
  239. for row_nr = 1:size(beh, 1)
  240. EEG.event(row_nr).trl_nr = beh.trl_nr(row_nr);
  241. end
  242. %% Remove segments of data that fall outside of blocks
  243. % record block starts
  244. beh.is_block_start(1) = 1;
  245. for row_nr = 2:size(beh, 1)
  246. beh.is_block_start(row_nr) = beh.block_nr(row_nr) - beh.block_nr(row_nr-1) == 1;
  247. end
  248. % record block ends
  249. beh.is_block_end(size(beh, 1)) = 1;
  250. for row_nr = 1:(size(beh, 1)-1)
  251. beh.is_block_end(row_nr) = beh.block_nr(row_nr+1) - beh.block_nr(row_nr) == 1;
  252. end
  253. % record block boundaries (first start and last end point of each block, with 0.75 seconds buffer)
  254. beh.block_boundary = zeros(size(beh, 1), 1);
  255. for row_nr = 1:size(beh, 1)
  256. if beh.is_block_start(row_nr)
  257. beh.block_boundary(row_nr) = beh.latency(row_nr) - (EEG.srate * 0.75);
  258. elseif beh.is_block_end(row_nr)
  259. beh.block_boundary(row_nr) = beh.offset(row_nr) + (EEG.srate * 0.75);
  260. end
  261. end
  262. % get the boundary indices in required format (start1, end1; start2, end2; start3, end3)
  263. block_boundaries = reshape(beh.block_boundary(beh.block_boundary~=0), 2, [])';
  264. % remove anything outside of blocks
  265. EEG = pop_select(EEG, 'time', (block_boundaries / EEG.srate));
  266. %% Trial selection
  267. % include only correct responses
  268. beh_filt_acc_only = beh(beh.acc==1, :);
  269. excl_trials_incorr = size(beh, 1)-size(beh_filt_acc_only, 1);
  270. total_excl_trials_incorr(subject_nr) = excl_trials_incorr;
  271. fprintf('Lost %g trials to incorrect responses\n', excl_trials_incorr)
  272. % include only responses between 100 and 1500 ms
  273. beh_filt = beh_filt_acc_only(beh_filt_acc_only.rt<=1500, :);
  274. excl_trials_rt = size(beh_filt_acc_only, 1)-size(beh_filt, 1);
  275. total_excl_trials_rt(subject_nr) = excl_trials_rt;
  276. fprintf('Lost %g trials to RTs above 1500\n', excl_trials_rt)
  277. fprintf('Lost %g trials in total to behavioural data\n', size(beh, 1)-size(beh_filt, 1))
  278. % filter the events structure
  279. discarded_trls = beh.trl_nr(~ismember(beh.trl_nr, beh_filt.trl_nr));
  280. discarded_events_indices = []; % (collect in a for loop, as [EEG.event.trl_nr] would remove missing data)
  281. for event_nr = 1:size(EEG.event, 2)
  282. if ismember(EEG.event(event_nr).trl_nr, discarded_trls)
  283. discarded_events_indices = [discarded_events_indices, event_nr];
  284. end
  285. end
  286. EEG = pop_editeventvals(EEG, 'delete', discarded_events_indices);
  287. % check the discarded trials are the expected length
  288. if numel(discarded_trls) ~= size(beh, 1)-size(beh_filt, 1)
  289. fprintf('Mismatch between behavioural data and EEG events in the number of trials to discard?')
  290. break
  291. end
  292. % check the sizes match
  293. if numel([EEG.event.trl_nr]) ~= size(beh_filt, 1)
  294. fprintf('Inconsistent numbers of trials between events structure and behavioural data after discarding trials?')
  295. break
  296. end
  297. % check the trl numbers match
  298. if any([EEG.event.trl_nr]' ~= beh_filt.trl_nr)
  299. fprintf('Trial IDs mmismatch between events structure and behavioural data after discarding trials?')
  300. break
  301. end
  302. %% Rereference, downsample, and filter
  303. % rereference
  304. EEG = pop_reref(EEG, []);
  305. % downsample
  306. EEG = pop_resample(EEG, 512);
  307. % filter
  308. % EEG = eeglab_butterworth(EEG, 0.5, 40, 4, 1:size(EEG.chanlocs, 2)); % preregistered filter
  309. EEG = eeglab_butterworth(EEG, 0.1, 40, 4, 1:size(EEG.chanlocs, 2)); % filter with lower highpass
  310. %% ICA
  311. % apply ASR
  312. %EEG_no_asr = EEG;
  313. EEG = clean_asr(EEG, asr_sigma, [], [], [], [], [], [], [], [], 1024); % The last number is available memory in mb, needed for reproducibility
  314. rng(3101) % set seed for reproducibility
  315. EEG = pop_runica(EEG, 'icatype', 'fastica', 'approach', 'symm');
  316. % classify components with ICLabel
  317. EEG = iclabel(EEG);
  318. % store results for easy indexing
  319. icl_res = EEG.etc.ic_classification.ICLabel.classifications;
  320. icl_classes = EEG.etc.ic_classification.ICLabel.classes;
  321. % identify and remove artefact components
  322. artefact_comps = find(icl_res(:, strcmp(icl_classes, 'Eye')) >= icl_cutoff | icl_res(:, strcmp(icl_classes, 'Muscle')) >= icl_cutoff);
  323. fprintf('Removing %g artefact-related ICA components\n', numel(artefact_comps))
  324. n_bad_ica(subject_nr) = numel(artefact_comps);
  325. %EEG_no_iclabel = EEG;
  326. EEG = pop_subcomp(EEG, artefact_comps);
  327. %% Interpolate bad channels
  328. % give the original chanlocs structure so EEGLAB interpolates the missing electrode(s)
  329. if numel(bad_channels)>0
  330. EEG = pop_interp(EEG, ur_chanlocs);
  331. end
  332. %% Epoch the data
  333. % identify and separate into epochs
  334. EEG_epo = pop_epoch(EEG, {triggers.A1, triggers.A2}, [-0.25, 1]);
  335. % remove baseline
  336. EEG_epo = pop_rmbase(EEG_epo, [-200, 0]);
  337. %% Check data
  338. % pop_eegplot(EEG);
  339. %
  340. %
  341. % PO7_idx = find(strcmp({EEG_epo.chanlocs.labels}, 'PO7'));
  342. %
  343. % topo_times = [0, 100, 125, 150, 175, 200, 250, 300, 350, 400, 450, 500];
  344. % pop_topoplot(EEG_epo, 1, topo_times)
  345. %
  346. %
  347. % figure;
  348. % hold on;
  349. %
  350. % for ch_nr = 1:64
  351. % subplot(8, 8, ch_nr);
  352. % plot(EEG_epo.times, mean(EEG_epo.data(ch_nr, :, :), 3))
  353. % end
  354. %
  355. % break
  356. %% Get trial-level microvolts for main analysis
  357. disp('Getting trial-level results...')
  358. max_elec = max_elecs.max_elec_bacs(max_elecs.subject_id == str2double(subject_id));
  359. max_time = max_elecs.max_time_bacs(max_elecs.subject_id == str2double(subject_id));
  360. [max_time_idx, max_time_closest] = eeglab_closest_time(EEG_epo, max_time);
  361. % get the index of the maximal electrode
  362. max_elec_idx = find(strcmp({EEG_epo.chanlocs.labels}, max_elec{:}));
  363. % check the maximal electrode is there
  364. if isempty(max_elec_idx)
  365. fprintf('Could not find maximal electrode %s in EEG.chanlocs?\n', max_elec{:})
  366. break
  367. end
  368. % Plot the average ERP for the maximal electrode, showing the maximal timepoint
  369. %plot(EEG_epo.times, mean(EEG_epo.data(max_elec_idx, :, :), 3))
  370. %hold on
  371. %plot(EEG_epo.times(max_time_idx), mean(EEG_epo.data(max_elec_idx, max_time_idx, :), 'all'), 'o')
  372. %line(max_time_closest)
  373. % get trial-level microvolts for maximal electrode
  374. % (per-trial average of 3 samples centred on maximal timepoint)
  375. main_res = beh_filt;
  376. max_time_idx_lower = max_time_idx - 1;
  377. max_time_idx_upper = max_time_idx + 1;
  378. main_res.uV = squeeze(mean(EEG_epo.data(max_elec_idx, max_time_idx_lower:max_time_idx_upper, :)));
  379. %% Get trial-level microvolts for maximal electrode from word vs. noise comparison
  380. max_elec_noise = max_elecs.max_elec_noise(max_elecs.subject_id == str2double(subject_id));
  381. max_time_noise = max_elecs.max_time_noise(max_elecs.subject_id == str2double(subject_id));
  382. [max_time_noise_idx, max_time_noise_closest] = eeglab_closest_time(EEG_epo, max_time);
  383. % get the index of the maximal electrode
  384. max_elec_noise_idx = find(strcmp({EEG_epo.chanlocs.labels}, max_elec_noise));
  385. % check the maximal electrode is there
  386. if isempty(max_elec_noise_idx)
  387. fprintf('Could not find maximal electrode %s (word vs. noise) in EEG.chanlocs?\n', max_elec{:})
  388. break
  389. end
  390. % Plot the average ERP for the maximal electrode, showing the maximal timepoint
  391. %plot(EEG_epo.times, mean(EEG_epo.data(max_elec_idx, :, :), 3))
  392. %hold on
  393. %plot(EEG_epo.times(max_time_idx), mean(EEG_epo.data(max_elec_idx, max_time_idx, :), 'all'), 'o')
  394. %xline(max_time_closest)
  395. % get trial-level microvolts for maximal electrode
  396. % (per-trial average of 3 samples centred on maximal timepoint)
  397. max_time_idx_lower_noise = max_time_noise_idx - 1;
  398. max_time_idx_upper_noise = max_time_noise_idx + 1;
  399. main_res.uV_noise_elec = squeeze(mean(EEG_epo.data(max_elec_noise_idx, max_time_idx_lower_noise:max_time_idx_upper_noise, :)));
  400. %% Get trial-level average microvolts in ROI, from 150 to 250 ms
  401. % get the indices of the ROI
  402. roi_chan_idx = ismember({EEG.chanlocs.labels}, roi);
  403. % check the right number of channels
  404. if sum(roi_chan_idx)~=numel(roi)
  405. fprintf('Expected %g channels in ROI, but data has %g?\n', numel(roi), sum(roi_chan_idx))
  406. break
  407. end
  408. % get indices of time window
  409. targ_window = [150, 250];
  410. targ_window_idx = EEG_epo.times >= targ_window(1) & EEG_epo.times <= targ_window(2);
  411. % get per-trial averages across ROI, within the target window
  412. main_res.uV_roi = squeeze(mean(EEG_epo.data(roi_chan_idx, targ_window_idx, :), [1, 2]));
  413. %% Get sample level microvolts for exploratory analysis
  414. disp('Getting sample-level results...')
  415. % resample to 256 Hz
  416. EEG_256 = pop_resample(EEG, 256);
  417. % get epochs of low-srate data
  418. EEG_epo_256 = pop_epoch(EEG_256, {triggers.A1, triggers.A2}, [-0.25, 1]);
  419. % remove baseline
  420. EEG_epo_256 = pop_rmbase(EEG_epo_256, [-200, 0]);
  421. % pre-allocate the table
  422. var_names = {'subj_id', 'stim_grp', 'resp_grp', 'item_nr', 'ch_name', 'time', 'uV'};
  423. var_types = {'string', 'string', 'string', 'double', 'string', 'double', 'double'};
  424. nrows = 64 * size(EEG_epo_256.times, 2) * size(beh_filt, 1);
  425. sample_res = table('Size',[nrows, numel(var_names)], 'VariableTypes',var_types, 'VariableNames',var_names);
  426. sample_res.subj_id = repmat(beh_filt.subj_id, 64*size(EEG_epo_256.times, 2), 1);
  427. sample_res.stim_grp = repmat(beh_filt.stim_grp, 64*size(EEG_epo_256.times, 2), 1);
  428. sample_res.resp_grp = repmat(beh_filt.resp_grp, 64*size(EEG_epo_256.times, 2), 1);
  429. % get the 64 channel eeg data as an array
  430. eeg_arr = EEG_epo_256.data(1:64, :, :);
  431. % a vector of all eeg data
  432. eeg_vec = squeeze(reshape(eeg_arr, 1, 1, []));
  433. % array and vector of the channel labels for each value in EEG.data
  434. channel_labels_arr = cell(size(eeg_arr));
  435. channel_label_lookup = {EEG_epo_256.chanlocs.labels};
  436. for chan_nr = 1:size(eeg_arr, 1)
  437. channel_labels_arr(chan_nr, :, :) = repmat(channel_label_lookup(chan_nr), size(channel_labels_arr, 2), size(channel_labels_arr, 3));
  438. end
  439. channel_labels_vec = squeeze(reshape(channel_labels_arr, 1, 1, []));
  440. % array and vector of the item numbers for each value in EEG.data
  441. times_arr = zeros(size(eeg_arr));
  442. times_lookup = EEG_epo_256.times;
  443. for time_idx = 1:size(eeg_arr, 2)
  444. times_arr(:, time_idx, :) = repmat(times_lookup(time_idx), size(times_arr, 1), size(times_arr, 3));
  445. end
  446. times_vec = squeeze(reshape(times_arr, 1, 1, []));
  447. % array and vector of the trial numbers
  448. trials_arr = zeros(size(eeg_arr));
  449. trials_lookup = beh_filt.item_nr;
  450. for trl_idx = 1:size(eeg_arr, 3)
  451. trials_arr(:, :, trl_idx) = repmat(trials_lookup(trl_idx), size(trials_arr, 1), size(trials_arr, 2));
  452. end
  453. trials_vec = squeeze(reshape(trials_arr, 1, 1, []));
  454. % store sample-level results in the table
  455. sample_res.ch_name = channel_labels_vec;
  456. sample_res.item_nr = trials_vec;
  457. sample_res.time = times_vec;
  458. sample_res.uV = eeg_vec;
  459. % look up and store some info about the trials
  460. trial_info_lookup = beh_filt(:, {'item_nr', 'condition', 'image', 'string'});
  461. sample_res = outerjoin(sample_res, trial_info_lookup, 'MergeKeys', true);
  462. % sort by time, channel, item_nr
  463. sample_res = sortrows(sample_res, {'time', 'ch_name', 'item_nr'});
  464. %% save the results
  465. disp('Saving results...')
  466. writetable(main_res, fullfile('max_elec_data', [subject_id, '.csv']));
  467. writetable(sample_res, fullfile('sample_data', [subject_id, '.csv']));
  468. %% concatenate the sample-level data
  469. all_sample_level = [all_sample_level; sample_res];
  470. end
  471. fprintf('\nFinished preprocessing picture-word data!\n')
  472. %% Functions
  473. % custom function for applying a Butterworth filter to EEGLAB data
  474. function EEG = eeglab_butterworth(EEG, low, high, order, chanind)
  475. fprintf('Applying Butterworth filter between %g and %g Hz (order of %g)\n', low, high, order)
  476. % create filter
  477. [b, a] = butter(order, [low, high]/(EEG.srate/2));
  478. % apply to data (requires transposition for filtfilt)
  479. data_trans = single(filtfilt(b, a, double(EEG.data(chanind, :)')));
  480. EEG.data(chanind, :) = data_trans';
  481. end
  482. % custom function for finding the closest timepoint in an EEG dataset
  483. function [idx, closesttime] = eeglab_closest_time(EEG, time)
  484. dists = abs(EEG.times - time);
  485. idx = find(dists == min(dists));
  486. % in the unlikely case there are two equidistant times, select one randomly
  487. if numel(idx) > 1
  488. fprintf('Two equidistant times! Selecting one randomly.')
  489. idx = idx(randperm(numel(idx)));
  490. idx = idx(1);
  491. end
  492. closesttime = EEG.times(idx);
  493. end