analyse_14_plot_sample_picture_word_topo.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. %% Setup
  2. addpath('matlab_extensions');
  3. % import eeglab (assumes eeglab has been added to path), e.g.
  4. addpath('C:/EEGLAB/eeglab2020_0')
  5. [ALLEEG, EEG, CURRENTSET, ALLCOM] = eeglab;
  6. erps = {'sample', 'sample_picture', 'sample_response'};
  7. % settings for x ticks
  8. time_mins = [-200, -200, -1000];
  9. time_steps = [100, 200, 200];
  10. % special settings for y range of fixed effect slopes
  11. slope_y_lims = {[], [-4, 4], []};
  12. slope_y_ticks = {[], -4:2:4, []};
  13. % settings for the event lines and topo plots
  14. event_times = {[500], [1000], []};
  15. event_rects = {[], [1150, 1650], []};
  16. erp_topo_times = {[105, 185, 235, 300, 400, 660], [110, 150, 225, 600, 800, 1150], [-800, -600, -400, -200, 0, 200]};
  17. for e_nr = 1:numel(erps)
  18. e = erps{e_nr};
  19. % import LMM estimates
  20. res_file = sprintf('analyse_13_%s_res.csv', e);
  21. fe_res = readtable(res_file);
  22. fe_res.ch_name = upper(fe_res.ch_name); % upper case for case insensitivity in joins
  23. % get summary variables
  24. channels = table2array( unique(fe_res(:, 'ch_name')) );
  25. times = table2array( sortrows(unique(fe_res(:, 'time'))) );
  26. fes = table2array( unique(fe_res(:, 'fe_lab')) );
  27. % get channel locations
  28. chanlocs = pop_chanedit([], 'load', {'BioSemi64.loc', 'filetype', 'loc'});
  29. % get channel order
  30. ch_name = upper({chanlocs.labels}');
  31. ch_order = cell2mat({chanlocs.urchan}');
  32. chan_orders = table(ch_name, ch_order);
  33. % only keep channels in the data in the table, and the chanlocs struct
  34. ch_in_fe = false(numel(chan_orders.ch_name), 0);
  35. for i = 1:numel(chan_orders.ch_name)
  36. ch = chan_orders.ch_name{i};
  37. ch_in_fe(i) = sum( strcmp(unique(fe_res.ch_name), ch) );
  38. end
  39. chan_orders = chan_orders(ch_in_fe, :);
  40. chanlocs = pop_chanedit(chanlocs, 'delete', find(~ch_in_fe));
  41. % join channel order to fe_res
  42. fe_res = join(fe_res, chan_orders, 'Keys', 'ch_name');
  43. %% get xyz colours
  44. % note: order of x and y are flipped, and y is reversed, to approximate the MNE coordinates
  45. rgb = [-[chanlocs.Y]; [chanlocs.X]; [chanlocs.Z]];
  46. for colour_chan = 1:3
  47. rgb(colour_chan, :) = normalize(rgb(colour_chan, :), 'range');
  48. end
  49. %% get fixed effects in matrices
  50. % coerce fixed effects into matrices of time x channel
  51. mats = struct();
  52. for fe = fes'
  53. % subset relevant rows of table
  54. fe_res_i = fe_res(strcmp(fe_res.fe_lab, fe), :);
  55. % sort rows of table
  56. fe_res_i = sortrows(fe_res_i, {'ch_order', 'time'});
  57. % extract fixed effect estimates and put in matrix
  58. % (matlab reshape uses fortan order)
  59. field_name = regexprep(fe{:}, '*', ''); % asterisk disallowed in field name
  60. field_name = regexprep(field_name, '\s\s', '_'); % space disallowed in field name
  61. mats.(field_name) = reshape( fe_res_i.Estimate, [numel(times), numel(channels)] );
  62. end
  63. %% Join all plots together
  64. fig = figure;
  65. set(0, 'DefaultLineLineWidth', 1);
  66. set(0, 'DefaultAxesLineWidth', 1);
  67. vhline_width = 1.25;
  68. fe_names = {'Intercept', 'Congruency', 'Predictability', 'Congruency_Predictability'};
  69. % get tidy labels
  70. fe_labels = {};
  71. max_nchar = 0;
  72. for fe = 1:numel(fe_names)
  73. fe_labels{fe} = split( strcat( regexprep(fe_names{fe}, '_', ' ×nl'), ' (µV)' ), 'nl' );
  74. if strlength(fe_labels{fe}) > max_nchar
  75. max_nchar = strlength(fe_labels{fe});
  76. end
  77. end
  78. % pad and justify with spaces
  79. for fe = 1:numel(fe_names)
  80. fe_labels{fe} = strjust(pad(fe_labels{fe}, max_nchar), 'center');
  81. end
  82. % get topoplot settings
  83. y_lims = [floor(min(fe_res.Estimate)), ceil(max(fe_res.Estimate))];
  84. map_lims = [-max(abs(y_lims)), max(abs(y_lims))];
  85. map_range = map_lims(2) - map_lims(1);
  86. map_ticks = map_lims(1):(map_range/4):map_lims(2);
  87. cmap_resolution = 101;
  88. topo_resolution = 101;
  89. bwr = [0, 0, 1; 1, 1, 1; 1, 0, 0];
  90. %cmap_bwr = interp1([-cmap_resolution/2; 0; cmap_resolution/2], bwr, (-cmap_resolution/2:cmap_resolution/2));
  91. cmap_bwr = twilight_shifted(cmap_resolution);
  92. % linear interpolation to focus on specific timepoints
  93. topo_times = erp_topo_times{e_nr};
  94. % x axis for timecourse
  95. time_lims = [round(min(times), -1), round(max(times), -1)];
  96. time_ticks = time_mins(e_nr):time_steps(e_nr):time_lims(2);
  97. % parameters for adjusting plotting space
  98. x_adj_l = 0.025; % offset space to be added to the left
  99. x_adj_r = 0.01; % space to be added to width
  100. y_adj_top = -0.05; % space to be added (or removed if negative) from the top
  101. % define the shape of the subplots
  102. sp_ncol = numel(topo_times);
  103. sp_nrow = 2 * numel(fe_names);
  104. % generate subplot coordinates in normalised units
  105. % xmin = .175;
  106. xmin = .255;
  107. xmax = .975;
  108. ymin = -0.05;
  109. ymax = 0.89;
  110. x_width = (xmax-xmin)/sp_ncol;
  111. x_props = xmin:x_width:xmax;
  112. y_height = (ymax-ymin)/sp_nrow;
  113. y_props = flip(ymin:y_height:ymax);
  114. y_props_topo_adj = y_props + y_height*0.075;
  115. topo_yheight = y_height * 0.8;
  116. % plot!
  117. % plot colours for timecourse legend (must come first to preserve colorbar)
  118. colour_legend_pos = [xmin/2 - x_width/1.5, y_props_topo_adj(1), x_width, topo_yheight]; % x position may need adjusting if fig size changes
  119. subplot('Position', colour_legend_pos);
  120. hold on;
  121. topoplot(zeros(numel(chan_orders.ch_name), 0), chanlocs, 'electrodes', 'off');
  122. % set line width
  123. set(findall(gca, 'Type', 'Line'), 'LineWidth', 1);
  124. for i = 1:numel(chan_orders.ch_name)
  125. topoplot(zeros(numel(chan_orders.ch_name), 0), chanlocs, 'colormap', [0,0,0], 'emarker', {'.', rgb(:, i)', 11, 1}, 'plotchans', i, 'headrad', 0);
  126. end
  127. hold off
  128. % now, plot everything else
  129. for fe = 1:numel(fe_names)
  130. fe_name = fe_names{fe};
  131. mat_fe = mats.(fe_name);
  132. if strcmp(fe_name, 'Intercept')
  133. fe_topo_lims = map_lims;
  134. else
  135. if numel(slope_y_lims{e_nr})>0
  136. fe_topo_lims = slope_y_lims{e_nr};
  137. else
  138. fe_topo_lims = map_lims;
  139. end
  140. end
  141. topo_nfus = zeros(numel(topo_times), 2);
  142. % plot topoplots
  143. for time_nr = 1:numel(topo_times)
  144. subplot('Position', [x_props(time_nr), y_props_topo_adj( fe * 2 - 1 ), x_width, topo_yheight]);
  145. mat_fe_topo_t = interp1(times, mat_fe, topo_times(time_nr));
  146. topoplot(mat_fe_topo_t, chanlocs, 'colormap', cmap_bwr, 'gridscale', topo_resolution, 'electrodes', 'off', 'maplimits', fe_topo_lims);
  147. % get lines
  148. lines = findall(gca, 'Type', 'Line');
  149. % set line width
  150. set(lines, 'LineWidth', 1);
  151. % get surfaces to extract y locations
  152. surf = findall(gca, 'Type', 'Surface');
  153. % get topo location to draw line to
  154. % [tx, ty] = ds2nfu(mean(lines(4).XData), min(lines(4).YData));
  155. [tx, ty] = ds2nfu(0, min(surf.YData(:)));
  156. topo_nfus(time_nr, :) = [tx, ty];
  157. end
  158. % plot timecourse
  159. subplot('Position', [x_props(1), y_props( fe * 2 ), x_width*sp_ncol, y_height])
  160. hold on
  161. % plot rectangle for jittered period
  162. rect = event_rects{e_nr};
  163. if ~isempty(rect)
  164. rectangle('Position', [rect(1) map_lims(1) rect(2)-rect(1) map_range], 'FaceColor', [0.4, 0.4, 0.4], 'EdgeColor', 'none')
  165. end
  166. if ~strcmp(fe_name, 'Intercept')
  167. plot(times, mats.('Intercept'), 'Color', [0.8, 0.8, 0.8]);
  168. end
  169. for ch = 1:size(mat_fe, 2)
  170. plot(times, mat_fe(:, ch), 'Color', rgb(:, ch), 'LineWidth', 0.55);
  171. end
  172. xlim(time_lims)
  173. xticks(time_ticks)
  174. set(gca,'FontSize', 8)
  175. ylim(fe_topo_lims)
  176. xline(0, '--', 'LineWidth', vhline_width)
  177. yline(0, '--', 'LineWidth', vhline_width)
  178. for t = event_times{e_nr}
  179. xline(t, '--', 'LineWidth', vhline_width)
  180. end
  181. tc_nfus = zeros(numel(topo_times), 2);
  182. for time_nr = 1:numel(topo_times)
  183. xline(topo_times(time_nr), 'LineWidth', vhline_width, 'Color', [0.4, 0.4, 0.4])
  184. % get the location of the top of the line
  185. [tcx, tcy] = ds2nfu(topo_times(time_nr), max(fe_topo_lims));
  186. tc_nfus(time_nr, :) = [tcx, tcy];
  187. end
  188. hold off
  189. % draw annotation lines between lines and topoplots
  190. for time_nr = 1:numel(topo_times)
  191. % colour is set to .6 intensity rather than .4 in vertical lines above - hacky solution for bug(?)
  192. annotation('line', [tc_nfus(time_nr, 1), topo_nfus(time_nr, 1)], [tc_nfus(time_nr, 2), topo_nfus(time_nr, 2)], 'Color', [0.6, 0.6, 0.6], 'LineWidth', vhline_width);
  193. end
  194. % plot styling
  195. % add colorbar to the y axis to show corresponding y and colour values
  196. ax = gca;
  197. ax_pos = get(ax, 'Position');
  198. cb = colorbar(ax);
  199. if strcmp(fe_name, 'Intercept')
  200. cb.Ticks = map_ticks;
  201. else
  202. if numel(slope_y_ticks{e_nr})>0
  203. cb.Ticks = slope_y_ticks{e_nr};
  204. else
  205. cb.Ticks = map_ticks;
  206. end
  207. end
  208. % cb.Ticks = map_ticks;
  209. cb.TickDirection = 'out';
  210. cb.Position = [ax_pos(1)-0.015, ax_pos(2), .015, ax_pos(4)];
  211. cb.Label.String = fe_labels{fe};
  212. cb.Label.Rotation = 0;
  213. cb.Label.VerticalAlignment = 'middle';
  214. cb.Label.HorizontalAlignment = 'right';
  215. cb.FontSize = 8;
  216. cb.Label.FontSize = 11;
  217. ax.Colormap = cmap_bwr;
  218. ax.CLim = fe_topo_lims;
  219. ax.YTickLabels = [];
  220. ax.YLabel = [];
  221. ax.YAxis.Visible = 'off';
  222. % ticks outside plot
  223. set(gca,'TickDir','out');
  224. if strcmp(fe_name, 'Congruency_Predictability')
  225. % add x label
  226. xlabel('Time (ms)', 'FontSize', 12)
  227. else
  228. % remove x tick labels
  229. set(gca,'XTickLabel', {[]});
  230. end
  231. % put white line over the default axes to remove them while keeping the ticks and labels (hacky)
  232. ax2 = axes('Position',gca().Position,...
  233. 'XColor',[1 1 1],...
  234. 'YColor',[1 1 1],...
  235. 'Color','none',...
  236. 'XTick',[],...
  237. 'YTick',[]);
  238. end
  239. set(fig, 'Units', 'Inches', 'Position', [0, 0, 6.5, 7], 'PaperUnits', 'Inches', 'PaperSize', [6.5, 7])
  240. plot_file = sprintf('figs/14_topo_timecourse_%s.pdf', e);
  241. exportgraphics(fig, plot_file, 'BackgroundColor','none')
  242. if strcmp(e, 'sample')
  243. % plot the N400 model predictions
  244. N400_t = 400;
  245. pred_levels_uncoded = 0.1:0.1:1;
  246. pred_levels = interp1([0.07, 1], [0, 1], pred_levels_uncoded); % because predictability was normalised between 0 and 1
  247. %cong_levels = [-0.5, 0.5];
  248. cong_levels = [-0.4938494, 0.5061506]; % [incong, cong] - copied from the unique deviation values in the script that fits the models
  249. B = struct();
  250. for fe_name = fe_names
  251. B.(fe_name{:}) = interp1(times, mats.(fe_name{:}), N400_t);
  252. end
  253. model_preds = zeros(numel(pred_levels), numel(cong_levels), height(chan_orders));
  254. % topoplot(B.Congruency, chanlocs, 'colormap', cmap_bwr, 'gridscale', topo_resolution, 'electrodes', 'off');
  255. for pred_lvl_nr = 1:numel(pred_levels)
  256. p = pred_levels(pred_lvl_nr);
  257. for cong_lvl_nr = 1:numel(cong_levels)
  258. c = cong_levels(cong_lvl_nr);
  259. model_preds(pred_lvl_nr, cong_lvl_nr, :) = B.Intercept +...
  260. B.Congruency * c +...
  261. B.Predictability * p +...
  262. B.Congruency_Predictability * p * c;
  263. end
  264. end
  265. abs_max = max(abs([floor(min(model_preds, [], 'all')*2)/2, ceil(max(model_preds, [], 'all')*2)/2]));
  266. preds_topo_lims = [-abs_max, abs_max];
  267. N400_fig = figure;
  268. t = tiledlayout(numel(cong_levels), numel(pred_levels) + 2, 'TileSpacing', 'none', 'Padding', 'none');
  269. for cong_lvl_nr = [2, 1] %1:numel(cong_levels)
  270. nexttile;
  271. if cong_lvl_nr == 1
  272. cong_lab = 'Incongruent';
  273. else
  274. cong_lab = 'Congruent';
  275. end
  276. text(0.33, 0.5, cong_lab, 'FontWeight', 'normal', 'HorizontalAlignment', 'center', 'FontSize', 9)
  277. ax = gca;
  278. ax.Visible = 0;
  279. for pred_lvl_nr = 1:numel(pred_levels)
  280. t_i = nexttile;
  281. topoplot(model_preds(pred_lvl_nr, cong_lvl_nr, :), chanlocs, 'colormap', cmap_bwr, 'gridscale', topo_resolution, 'electrodes', 'off', 'maplimits', preds_topo_lims);
  282. set(findall(gca, 'Type', 'Line'), 'LineWidth', 0.5);
  283. if cong_lvl_nr == 2
  284. title( sprintf('%g%%', pred_levels_uncoded(pred_lvl_nr)*100), 'FontWeight', 'normal', 'FontSize', 9)
  285. end
  286. end
  287. if cong_lvl_nr == 2
  288. nexttile([2 1])
  289. ax = gca;
  290. ax.Visible = 0;
  291. cb = colorbar(t_i);
  292. cb.Ticks = preds_topo_lims(1):2.5:preds_topo_lims(2);
  293. cb.TickDirection = 'out';
  294. cb.Label.String = 'N400 Amplitude (µV)';
  295. cb.FontSize = 8;
  296. cb.Label.FontSize = 9;
  297. cb.Position = [0.92,0.13,0.0125,0.75];
  298. cb.Label.Position(1) = 0.8 * cb.Label.Position(1);
  299. end
  300. end
  301. set(N400_fig, 'Units', 'Inches', 'Position', [0, 0, 6.5, 1.4], 'PaperUnits', 'Inches', 'PaperSize', [6.5, 1.4])
  302. N400_plot_file = sprintf('figs/14_N400_%s.pdf', e);
  303. exportgraphics(N400_fig, N400_plot_file, 'BackgroundColor','none')
  304. end
  305. end