OSCR

Neural oscillatory dynamics reveal altered top-down and integrative mechanisms during face processing in autistic children and unaffected siblings of autistic children.

Code ↔ Paper

3 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 3 matches
  1. [1] § Methods › EEG recordings & preprocessing ↔ Preprocessing_SFARI_FAST_2025.py, lines 300–339 · score 0.65 · NoisyChannels, Bad channel, RANSAC, pyprep, preprocessing, NA
  2. [2] § Methods › Statistical analysis ↔ Analysis_ERP_FAST.py, lines 1162–1202 · score 0.55 · spatio temporal cluster, zero, MNE, max, permutation, windows
  3. [3] § Methods › ERP analysis ↔ Analysis_ERP_FAST.py, lines 1776–1843 · score 0.50 · P1 window, 180 ms, ERP, latencies, peak, N170

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 3,304 lines · 128 KB · no license · 2 matches

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Wed Mar 26 16:31:53 2025
  4. @author: tvanneau
  5. """
  6. import matplotlib as mpl
  7. new_rc_params = {'text.usetex': False,
  8. "svg.fonttype": 'none'
  9. }
  10. mpl.rcParams.update(new_rc_params)
  11. #============================================
  12. # AVSRT Analysis - Visual Stimulation
  13. #============================================
  14. #loading needed toolboxes
  15. import mne
  16. import numpy as np
  17. import matplotlib.pyplot as plt
  18. import matplotlib.animation as animation
  19. import copy
  20. import tkinter
  21. import pandas as pd
  22. from os.path import isfile
  23. from tkinter import filedialog
  24. import os
  25. from pyprep.find_noisy_channels import NoisyChannels
  26. from pyprep.prep_pipeline import PrepPipeline
  27. import re
  28. list_file = os.listdir('Z://Analysis/SFARI_Analysis/FAST/Theo/Epochs_EEG_Only/TD')
  29. file_path = 'Z://Analysis/SFARI_Analysis/FAST/Theo/Epochs_EEG_Only/TD'
  30. list_files_epochs = [s for s in list_file if '_EEG_epo' in s]
  31. list_files_RT_TD = [s for s in list_file if '_RT_all' in s]
  32. list_files_ET_TD = [s for s in list_file if '_ET_epo' in s]
  33. # Initialize lists to store concatenated EEG epochs and RTs per participant
  34. filtered_RT_lst_TD = []
  35. filtered_epochs_lst_TD = []
  36. percent_rejected_epochs = []
  37. for i in range(len(list_files_epochs)):
  38. # Load EEG, ET, and RT files
  39. eeg_epochs = mne.read_epochs(file_path + "/" + list_files_epochs[i])
  40. et_epochs = mne.read_epochs(file_path + "/" + list_files_ET_TD[i])
  41. rt_data = pd.read_csv(file_path + "/" + list_files_RT_TD[i], sep='\t', encoding='utf-8')
  42. # Standardize channel names in ET epochs
  43. new_channel_names = {ch_name: re.sub(r'_(right|left)', '', ch_name) for ch_name in et_epochs.ch_names}
  44. et_epochs.rename_channels(new_channel_names)
  45. # Get gaze data and filter trials without blinks
  46. time_index = np.where(eeg_epochs.times == 0)[0][0] # Index of t=0
  47. gaze_x_data = et_epochs.get_data(picks='xpos')
  48. gaze_y_data = et_epochs.get_data(picks='ypos')
  49. keep_indices = [j for j in range(gaze_x_data.shape[0])
  50. if not np.isnan(gaze_x_data[j, 0, time_index])
  51. and not np.isnan(gaze_y_data[j, 0, time_index])]
  52. filtered_epochs = eeg_epochs[keep_indices]
  53. filtered_rt = rt_data.iloc[keep_indices].reset_index(drop=True)
  54. percent_rejected_epochs.append(len(filtered_epochs)/len(eeg_epochs))
  55. filtered_RT_lst_TD.append(filtered_rt)
  56. filtered_epochs_lst_TD.append(filtered_epochs)
  57. #%%
  58. list_file = os.listdir('Z://Analysis/SFARI_Analysis/FAST/Theo/Epochs_EEG_Only/ASD')
  59. file_path = 'Z://Analysis/SFARI_Analysis/FAST/Theo/Epochs_EEG_Only/ASD'
  60. list_files_epochs_ASD = [s for s in list_file if '_EEG_epo' in s]
  61. list_files_RT_ASD = [s for s in list_file if '_RT_all' in s]
  62. list_files_ET_ASD = [s for s in list_file if '_ET_epo' in s]
  63. # Initialize lists to store concatenated EEG epochs and RTs per participant
  64. filtered_RT_lst_ASD = []
  65. filtered_epochs_lst_ASD = []
  66. percent_rejected_epochs_ASD = []
  67. for i in range(len(list_files_epochs_ASD)):
  68. # Load EEG, ET, and RT files
  69. eeg_epochs = mne.read_epochs(file_path + "/" + list_files_epochs_ASD[i])
  70. et_epochs = mne.read_epochs(file_path + "/" + list_files_ET_ASD[i])
  71. rt_data = pd.read_csv(file_path + "/" + list_files_RT_ASD[i], sep='\t', encoding='utf-8')
  72. # Standardize channel names in ET epochs
  73. new_channel_names = {ch_name: re.sub(r'_(right|left)', '', ch_name) for ch_name in et_epochs.ch_names}
  74. et_epochs.rename_channels(new_channel_names)
  75. # Get gaze data and filter trials without blinks
  76. time_index = np.where(eeg_epochs.times == 0)[0][0] # Index of t=0
  77. gaze_x_data = et_epochs.get_data(picks='xpos')
  78. gaze_y_data = et_epochs.get_data(picks='ypos')
  79. keep_indices = [j for j in range(gaze_x_data.shape[0])
  80. if not np.isnan(gaze_x_data[j, 0, time_index])
  81. and not np.isnan(gaze_y_data[j, 0, time_index])]
  82. filtered_epochs = eeg_epochs[keep_indices]
  83. filtered_rt = rt_data.iloc[keep_indices].reset_index(drop=True)
  84. percent_rejected_epochs_ASD.append(len(filtered_epochs)/len(eeg_epochs))
  85. filtered_RT_lst_ASD.append(filtered_rt)
  86. filtered_epochs_lst_ASD.append(filtered_epochs)
  87. #%%
  88. list_file = os.listdir('Z://Analysis/SFARI_Analysis/FAST/Theo/Epochs_EEG_Only/SIB')
  89. file_path = 'Z://Analysis/SFARI_Analysis/FAST/Theo/Epochs_EEG_Only/SIB'
  90. list_files_epochs_SIB = [s for s in list_file if '_EEG_epo' in s]
  91. list_files_RT_SIB = [s for s in list_file if '_RT_all' in s]
  92. list_files_ET_SIB = [s for s in list_file if '_ET_epo' in s]
  93. # Initialize lists to store concatenated EEG epochs and RTs per participant
  94. filtered_RT_lst_SIB = []
  95. filtered_epochs_lst_SIB = []
  96. percent_rejected_epochs_SIB = []
  97. for i in range(len(list_files_epochs_SIB)):
  98. # Load EEG, ET, and RT files
  99. eeg_epochs = mne.read_epochs(file_path + "/" + list_files_epochs_SIB[i])
  100. et_epochs = mne.read_epochs(file_path + "/" + list_files_ET_SIB[i])
  101. rt_data = pd.read_csv(file_path + "/" + list_files_RT_SIB[i], sep='\t', encoding='utf-8')
  102. # Standardize channel names in ET epochs
  103. new_channel_names = {ch_name: re.sub(r'_(right|left)', '', ch_name) for ch_name in et_epochs.ch_names}
  104. et_epochs.rename_channels(new_channel_names)
  105. # Get gaze data and filter trials without blinks
  106. time_index = np.where(eeg_epochs.times == 0)[0][0] # Index of t=0
  107. gaze_x_data = et_epochs.get_data(picks='xpos')
  108. gaze_y_data = et_epochs.get_data(picks='ypos')
  109. keep_indices = [j for j in range(gaze_x_data.shape[0])
  110. if not np.isnan(gaze_x_data[j, 0, time_index])
  111. and not np.isnan(gaze_y_data[j, 0, time_index])]
  112. filtered_epochs = eeg_epochs[keep_indices]
  113. filtered_rt = rt_data.iloc[keep_indices].reset_index(drop=True)
  114. percent_rejected_epochs_SIB.append(len(filtered_epochs)/len(eeg_epochs))
  115. filtered_RT_lst_SIB.append(filtered_rt)
  116. filtered_epochs_lst_SIB.append(filtered_epochs)
  117. #%%
  118. # Assuming you have 3 lists: filtered_RT_lst_ASD, filtered_RT_lst_TD, filtered_RT_lst_SIB
  119. # For ASD group
  120. for i, df in enumerate(filtered_RT_lst_ASD):
  121. df['Subject'] = list_files_RT_ASD[i][:5] # Assign real subject number (first 5 characters) directly
  122. # For TD group
  123. for i, df in enumerate(filtered_RT_lst_TD): # Continue numbering
  124. df['Subject'] = list_files_RT_TD[i][:5] # Assign real subject number (first 5 characters)
  125. # For SIB group
  126. for i, df in enumerate(filtered_RT_lst_SIB): # Continue numbering
  127. df['Subject'] = list_files_RT_SIB[i][:4] # Assign real subject number (first 5 characters)
  128. # Create a group label for each list
  129. for df in filtered_RT_lst_ASD:
  130. df['Group'] = 'ASD'
  131. for df in filtered_RT_lst_TD:
  132. df['Group'] = 'TD'
  133. for df in filtered_RT_lst_SIB:
  134. df['Group'] = 'SIB'
  135. #%%
  136. # Concatenate all dataframes into one
  137. df_combined = pd.concat([pd.concat(filtered_RT_lst_ASD),
  138. pd.concat(filtered_RT_lst_TD),
  139. pd.concat(filtered_RT_lst_SIB)])
  140. # Check the combined dataframe
  141. df_combined.head()
  142. #%%
  143. import pandas as pd
  144. import numpy as np
  145. # Step 1: Keep only RT-relevant trials
  146. rt_event_ids = [121, 122, 131, 132]
  147. df_rt_only = df_combined[df_combined['event_id'].isin(rt_event_ids)].copy()
  148. # Step 2: Map each event_id to its corresponding RT column
  149. event_to_rt_col = {
  150. 121: 'face_shadow_RT',
  151. 122: 'face_U_shadow_RT',
  152. 131: 'obj_shadow_RT',
  153. 132: 'obj_U_shadow_RT'
  154. }
  155. # Step 3: Create a new RT column by selecting the correct one for each row
  156. def extract_rt(row):
  157. rt_col = event_to_rt_col.get(row['event_id'])
  158. return row[rt_col] if pd.notnull(rt_col) and pd.notnull(row[rt_col]) else np.nan
  159. df_rt_only['RT'] = df_rt_only.apply(extract_rt, axis=1)
  160. # Step 4: Group by Subject and event_id, and calculate mean RT
  161. mean_rt = df_rt_only.groupby(['Subject', 'event_id'])['RT'].mean().reset_index()
  162. # Step 5 (optional): Pivot for cleaner viewing
  163. mean_rt_pivot = mean_rt.pivot(index='Subject', columns='event_id', values='RT')
  164. mean_rt_pivot.columns = ['RT_121_face_shadow', 'RT_122_face_U_shadow', 'RT_131_obj_shadow', 'RT_132_obj_U_shadow']
  165. mean_rt_pivot = mean_rt_pivot.reset_index()
  166. # Display result
  167. mean_rt_pivot
  168. #%%
  169. import pandas as pd
  170. import numpy as np
  171. # Step 1: Filter relevant event_ids
  172. rt_event_ids = [121, 122, 131, 132]
  173. df_rt_only = df_combined[df_combined['event_id'].isin(rt_event_ids)].copy()
  174. # Step 2: Map each event_id to its corresponding RT column
  175. event_to_rt_col = {
  176. 121: 'face_shadow_RT',
  177. 122: 'face_U_shadow_RT',
  178. 131: 'obj_shadow_RT',
  179. 132: 'obj_U_shadow_RT'
  180. }
  181. # Step 3: Extract correct RT value for each trial
  182. def extract_rt(row):
  183. rt_col = event_to_rt_col.get(row['event_id'])
  184. return row[rt_col] if pd.notnull(rt_col) and pd.notnull(row[rt_col]) else np.nan
  185. df_rt_only['RT'] = df_rt_only.apply(extract_rt, axis=1)
  186. # Step 4: Group by Subject and event_id to get mean RT and count
  187. agg_df = df_rt_only.groupby(['Subject', 'event_id'])['RT'].agg(['mean', 'count']).reset_index()
  188. # Step 5: Pivot for wide-format dataframe
  189. mean_rt_pivot = agg_df.pivot(index='Subject', columns='event_id', values='mean')
  190. count_pivot = agg_df.pivot(index='Subject', columns='event_id', values='count')
  191. # Step 6: Rename columns for clarity
  192. mean_rt_pivot.columns = [f'MeanRT_{eid}' for eid in mean_rt_pivot.columns]
  193. count_pivot.columns = [f'N_{eid}' for eid in count_pivot.columns]
  194. # Step 7: Merge mean RT and count into a single dataframe
  195. summary_df = pd.concat([mean_rt_pivot, count_pivot], axis=1).reset_index()
  196. # Show result
  197. summary_df
  198. #%%
  199. # Make sure Subject is a string so we can check its prefix
  200. summary_df['Subject'] = summary_df['Subject'].astype(str)
  201. # Define group based on prefix
  202. def get_group(subject_id):
  203. if subject_id.startswith('10'):
  204. return 'TD'
  205. elif subject_id.startswith('11'):
  206. return 'ASD'
  207. elif subject_id.startswith('15'):
  208. return 'SIB'
  209. else:
  210. return 'Unknown'
  211. # Apply the function to create 'Group' column
  212. summary_df['Group'] = summary_df['Subject'].apply(get_group)
  213. # Optional: move 'Group' column next to 'Subject'
  214. cols = ['Subject', 'Group'] + [col for col in summary_df.columns if col not in ['Subject', 'Group']]
  215. summary_df = summary_df[cols]
  216. # Show result
  217. summary_df
  218. #%%
  219. import seaborn as sns
  220. import matplotlib.pyplot as plt
  221. # Step 1: Melt the summary dataframe to long format
  222. # Keep only RT columns
  223. rt_cols = [col for col in summary_df.columns if col.startswith('MeanRT_')]
  224. summary_long = summary_df.melt(
  225. id_vars=['Subject', 'Group'],
  226. value_vars=rt_cols,
  227. var_name='TrialType',
  228. value_name='RT'
  229. )
  230. # Step 2: Clean the 'TrialType' column for better labels
  231. summary_long['TrialType'] = summary_long['TrialType'].str.replace('MeanRT_', '')
  232. trial_type_labels = {
  233. '121': 'Face_Shadow',
  234. '122': 'Face_U_Shadow',
  235. '131': 'Obj_Shadow',
  236. '132': 'Obj_U_Shadow'
  237. }
  238. summary_long['TrialType'] = summary_long['TrialType'].map(trial_type_labels)
  239. # Step 3: Plot grouped boxplot
  240. plt.figure(figsize=(10, 6))
  241. sns.boxplot(data=summary_long, x='TrialType', y='RT', hue='Group')
  242. plt.title('Reaction Times by Trial Type and Group')
  243. plt.ylabel('Reaction Time (ms)')
  244. plt.xlabel('Trial Type')
  245. plt.legend(title='Group')
  246. plt.tight_layout()
  247. plt.show()
  248. #%% Calculate ERP
  249. # Adjust the calculate_erp function to include the new baseline correction
  250. def calculate_erp(filtered_epochs_lst, stim_type):
  251. erp_list = []
  252. number_stim = []
  253. # Loop through each subject's epochs
  254. for epochs in filtered_epochs_lst:
  255. # Select epochs based on stimulation type
  256. epochs.set_eeg_reference(ref_channels='average')
  257. epochs_stim = epochs[stim_type]
  258. # Calculate the ERP (mean across epochs)
  259. erp = epochs_stim.average()
  260. erp_list.append(erp)
  261. number_stim.append(len(epochs_stim))
  262. return erp_list, number_stim
  263. # Calculate ERP for each group and stimulation type with the new baseline
  264. # TD group
  265. erp_face_TD, number_stim_face_TD = calculate_erp(filtered_epochs_lst_TD, 'face')
  266. erp_face_ASD, number_stim_face_ASD = calculate_erp(filtered_epochs_lst_ASD, 'face')
  267. erp_face_SIB, number_stim_face_SIB = calculate_erp(filtered_epochs_lst_SIB, 'face')
  268. # erp_face_shadow_TD, number_stim_face_shadow_TD = calculate_erp(filtered_epochs_lst_TD, 'face_shadow')
  269. # erp_face_shadow_ASD, number_stim_face_shadow_ASD = calculate_erp(filtered_epochs_lst_ASD, 'face_shadow')
  270. # erp_face_shadow_SIB, number_stim_face_shadow_SIB = calculate_erp(filtered_epochs_lst_SIB, 'face_shadow')
  271. erp_face_U_TD, number_stim_face_U_TD = calculate_erp(filtered_epochs_lst_TD, 'face_U')
  272. erp_face_U_ASD, number_stim_face_U_ASD = calculate_erp(filtered_epochs_lst_ASD, 'face_U')
  273. erp_face_U_SIB, number_stim_face_U_SIB = calculate_erp(filtered_epochs_lst_SIB, 'face_U')
  274. # erp_face_U_shadow_TD, number_stim_face_U_shadow_TD = calculate_erp(filtered_epochs_lst_TD, 'face_U_shadow')
  275. # erp_face_U_shadow_ASD, number_stim_face_U_shadow_ASD = calculate_erp(filtered_epochs_lst_ASD, 'face_U_shadow')
  276. # erp_face_U_shadow_SIB, number_stim_face_U_shadow_SIB = calculate_erp(filtered_epochs_lst_SIB, 'face_U_shadow')
  277. erp_obj_TD, number_stim_obj_TD = calculate_erp(filtered_epochs_lst_TD, 'obj')
  278. erp_obj_ASD, number_stim_obj_ASD = calculate_erp(filtered_epochs_lst_ASD, 'obj')
  279. erp_obj_SIB, number_stim_obj_SIB = calculate_erp(filtered_epochs_lst_SIB, 'obj')
  280. # erp_obj_shadow_TD, number_stim_obj_shadow_TD = calculate_erp(filtered_epochs_lst_TD, 'obj_shadow')
  281. # erp_obj_shadow_ASD, number_stim_obj_shadow_ASD = calculate_erp(filtered_epochs_lst_ASD, 'obj_shadow')
  282. # erp_obj_shadow_SIB, number_stim_obj_shadow_SIB = calculate_erp(filtered_epochs_lst_SIB, 'obj_shadow')
  283. erp_obj_U_TD, number_stim_obj_U_TD = calculate_erp(filtered_epochs_lst_TD, 'obj_U')
  284. erp_obj_U_ASD, number_stim_obj_U_ASD = calculate_erp(filtered_epochs_lst_ASD, 'obj_U')
  285. erp_obj_U_SIB, number_stim_obj_U_SIB = calculate_erp(filtered_epochs_lst_SIB, 'obj_U')
  286. # erp_obj_U_shadow_TD, number_stim_obj_shadow_TD = calculate_erp(filtered_epochs_lst_TD, 'obj_U_shadow')
  287. # erp_obj_U_shadow_ASD, number_stim_obj_shadow_ASD = calculate_erp(filtered_epochs_lst_ASD, 'obj_U_shadow')
  288. # erp_obj_U_shadow_SIB, number_stim_obj_shadow_SIB = calculate_erp(filtered_epochs_lst_SIB, 'obj_U_shadow')
  289. #%%
  290. import pandas as pd
  291. # Step 1: Combine counts for each group
  292. # Make sure each list is of the same length (number of subjects in that group)
  293. # TD group
  294. df_TD = pd.DataFrame({
  295. 'Group': 'TD',
  296. 'N_121': number_stim_face_TD,
  297. 'N_122': number_stim_face_U_TD,
  298. 'N_131': number_stim_obj_TD,
  299. 'N_132': number_stim_obj_U_TD
  300. })
  301. # ASD group
  302. df_ASD = pd.DataFrame({
  303. 'Group': 'ASD',
  304. 'N_121': number_stim_face_ASD,
  305. 'N_122': number_stim_face_U_ASD,
  306. 'N_131': number_stim_obj_ASD,
  307. 'N_132': number_stim_obj_U_ASD
  308. })
  309. # SIB group
  310. df_SIB = pd.DataFrame({
  311. 'Group': 'SIB',
  312. 'N_121': number_stim_face_SIB,
  313. 'N_122': number_stim_face_U_SIB,
  314. 'N_131': number_stim_obj_SIB,
  315. 'N_132': number_stim_obj_U_SIB
  316. })
  317. # Step 2: Concatenate all into one DataFrame
  318. df_number_stim_all = pd.concat([df_TD, df_ASD, df_SIB], ignore_index=True)
  319. # Display result
  320. df_number_stim_all
  321. #%% Plot topomap - face
  322. Evoked_face_TD = mne.grand_average(erp_face_TD)
  323. Evoked_face_ASD = mne.grand_average(erp_face_ASD)
  324. Evoked_face_SIB = mne.grand_average(erp_face_SIB)
  325. # Evoked_face_shadow_TD = mne.grand_average(erp_face_shadow_TD)
  326. # Evoked_face_shadow_ASD = mne.grand_average(erp_face_shadow_ASD)
  327. # Evoked_face_shadow_SIB = mne.grand_average(erp_face_shadow_SIB)
  328. # times = [-0.05,0.12,0.170,0.250,0.31] # Time in seconds
  329. times = [-0.05,0.12] # Time in seconds
  330. Evoked_face_TD.plot_topomap(times=times, size=1, vlim=(0, 30), cmap='jet')
  331. Evoked_face_ASD.plot_topomap(times=times, size=1, vlim=(5, 30), cmap='jet')
  332. Evoked_face_SIB.plot_topomap(times=times, size=1, vlim=(5, 30), cmap='jet')
  333. # Evoked_face_shadow_TD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  334. # Evoked_face_shadow_ASD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  335. # Evoked_face_shadow_SIB.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  336. #%% save
  337. plt.savefig('ERP_Topomap_Faces_SIB.svg')
  338. #%% Plot ERP - between groups
  339. import numpy as np
  340. import matplotlib.pyplot as plt
  341. # Function to average ERP over specific channels
  342. def average_erp_channels(erp, channel_list):
  343. # Select the specified channels and average the ERP over these channels
  344. selected_channels = erp.copy().pick_channels(channel_list)
  345. avg_erp = np.mean(selected_channels.data, axis=0) # Average over channels
  346. return avg_erp
  347. tmin_epochs=-0.5
  348. tmax_epochs=1.0
  349. import numpy as np
  350. import matplotlib.pyplot as plt
  351. # Function to calculate SEM (Standard Error of the Mean)
  352. def calculate_sem(erp_list):
  353. erp_array = np.array([erp.data for erp in erp_list]) # Convert list of ERPs to a 2D array (n_subjects, n_timepoints)
  354. sem = np.std(erp_array, axis=0) / np.sqrt(erp_array.shape[0]) # SEM = std / sqrt(n_subjects)
  355. return sem
  356. # Function to plot the average ERP with SEM for the three groups for a given stimulation type
  357. def plot_avg_erp_with_sem(erp_TD, sem_TD, erp_ASD, sem_ASD, erp_SIB, sem_SIB, stim_type, channels, sfreq, plot_tmin=-0.1, plot_tmax=0.6):
  358. # Calculate time points based on the sampling frequency (sfreq)
  359. times = np.linspace(tmin_epochs, tmax_epochs, len(erp_TD))
  360. # Find the index range corresponding to the desired plot time window (-0.1 to 0.6 seconds)
  361. idx_min = np.searchsorted(times, plot_tmin)
  362. idx_max = np.searchsorted(times, plot_tmax)
  363. # Slice the ERP data, SEM, and time points for the plot time window
  364. times_plot = times[idx_min:idx_max]
  365. erp_TD_plot = erp_TD[idx_min:idx_max]
  366. sem_TD_plot = sem_TD[idx_min:idx_max]
  367. erp_ASD_plot = erp_ASD[idx_min:idx_max]
  368. sem_ASD_plot = sem_ASD[idx_min:idx_max]
  369. erp_SIB_plot = erp_SIB[idx_min:idx_max]
  370. sem_SIB_plot = sem_SIB[idx_min:idx_max]
  371. # Plotting the ERPs for the three groups with SEM shaded areas
  372. plt.figure(figsize=(10, 6))
  373. # TD group
  374. plt.plot(times_plot, erp_TD_plot, label='TD', color='blue')
  375. plt.fill_between(times_plot, erp_TD_plot - sem_TD_plot, erp_TD_plot + sem_TD_plot, color='blue', alpha=0.3)
  376. # ASD group
  377. plt.plot(times_plot, erp_ASD_plot, label='ASD', color='red')
  378. plt.fill_between(times_plot, erp_ASD_plot - sem_ASD_plot, erp_ASD_plot + sem_ASD_plot, color='red', alpha=0.3)
  379. # SIB group
  380. plt.plot(times_plot, erp_SIB_plot, label='SIB', color='green')
  381. plt.fill_between(times_plot, erp_SIB_plot - sem_SIB_plot, erp_SIB_plot + sem_SIB_plot, color='green', alpha=0.3)
  382. # Add title and labels
  383. plt.title(f'Average ERP with SEM for {stim_type} Stimulation ({", ".join(channels)})')
  384. plt.xlabel('Time (s)')
  385. plt.ylabel('Amplitude (µV)')
  386. plt.legend()
  387. plt.axvline(x=times_plot[12], color='black', linestyle='--')
  388. plt.ylim([-2e-6,22e-6])
  389. # plt.grid(True)
  390. plt.show()
  391. #%% Plot ERP - within group
  392. import numpy as np
  393. import matplotlib.pyplot as plt
  394. # Function to average ERP over specific channels
  395. def average_erp_channels(erp, channel_list):
  396. # Select the specified channels and average the ERP over these channels
  397. selected_channels = erp.copy().pick_channels(channel_list)
  398. avg_erp = np.mean(selected_channels.data, axis=0) # Average over channels
  399. return avg_erp
  400. tmin_epochs=-0.5
  401. tmax_epochs=1.0
  402. import numpy as np
  403. import matplotlib.pyplot as plt
  404. # Function to calculate SEM (Standard Error of the Mean)
  405. def calculate_sem(erp_list):
  406. erp_array = np.array([erp.data for erp in erp_list]) # Convert list of ERPs to a 2D array (n_subjects, n_timepoints)
  407. sem = np.std(erp_array, axis=0) / np.sqrt(erp_array.shape[0]) # SEM = std / sqrt(n_subjects)
  408. return sem
  409. # Function to plot the average ERP with SEM for the three groups for a given stimulation type
  410. def plot_avg_erp_with_sem(erp_TD_face, sem_TD_face, erp_TD_U_face, sem_TD_U_face,
  411. erp_TD_obj, sem_TD_obj, erp_TD_U_obj, sem_TD_U_obj, group, channels, sfreq, plot_tmin=-0.1, plot_tmax=0.6):
  412. # Calculate time points based on the sampling frequency (sfreq)
  413. times = np.linspace(tmin_epochs, tmax_epochs, len(erp_TD_face))
  414. # Find the index range corresponding to the desired plot time window (-0.1 to 0.6 seconds)
  415. idx_min = np.searchsorted(times, plot_tmin)
  416. idx_max = np.searchsorted(times, plot_tmax)
  417. # Slice the ERP data, SEM, and time points for the plot time window
  418. times_plot = times[idx_min:idx_max]
  419. erp_TD_face_plot = erp_TD_face[idx_min:idx_max]
  420. sem_TD_face_plot = sem_TD_face[idx_min:idx_max]
  421. erp_TD_U_face_plot = erp_TD_U_face[idx_min:idx_max]
  422. sem_TD_U_face_plot = sem_TD_U_face[idx_min:idx_max]
  423. erp_TD_obj_plot = erp_TD_obj[idx_min:idx_max]
  424. sem_TD_obj_plot = sem_TD_obj[idx_min:idx_max]
  425. erp_TD_U_obj_plot = erp_TD_U_obj[idx_min:idx_max]
  426. sem_TD_U_obj_plot = sem_TD_U_obj[idx_min:idx_max]
  427. # Plotting the ERPs for the three groups with SEM shaded areas
  428. plt.figure(figsize=(10, 6))
  429. # TD group
  430. plt.plot(times_plot, erp_TD_face_plot, label='Faces', color='#b2182b')
  431. plt.fill_between(times_plot, erp_TD_face_plot - sem_TD_face_plot, erp_TD_face_plot + sem_TD_face_plot, color='#b2182b', alpha=0.3)
  432. # ASD group
  433. plt.plot(times_plot, erp_TD_U_face_plot, label='Inverted Faces', color='#ef8a62')
  434. plt.fill_between(times_plot, erp_TD_U_face_plot - sem_TD_U_face_plot, erp_TD_U_face_plot + sem_TD_U_face_plot, color='#ef8a62', alpha=0.3)
  435. # SIB group
  436. plt.plot(times_plot, erp_TD_obj_plot, label='Object', color='#2166ac')
  437. plt.fill_between(times_plot, erp_TD_obj_plot - sem_TD_obj_plot, erp_TD_obj_plot + sem_TD_obj_plot, color='#2166ac', alpha=0.3)
  438. # SIB group
  439. plt.plot(times_plot, erp_TD_U_obj_plot, label='Inverted Object', color='#67a9cf')
  440. plt.fill_between(times_plot, erp_TD_U_obj_plot - sem_TD_U_obj_plot, erp_TD_U_obj_plot + sem_TD_U_obj_plot, color='#67a9cf', alpha=0.3)
  441. # Add title and labels
  442. plt.title(f'Average ERP with SEM for {group} Stimulation ({", ".join(channels)})')
  443. plt.xlabel('Time (s)')
  444. plt.ylabel('Amplitude (µV)')
  445. plt.legend()
  446. plt.axvline(x=times_plot[12], color='black', linestyle='--')
  447. plt.ylim([-2e-6,30e-6])
  448. # plt.grid(True)
  449. # plt.xlim([0.05,0.175])
  450. plt.show()
  451. #%% Plot ERP - within group with SHADOW ALL STIM TYPES
  452. import numpy as np
  453. import matplotlib.pyplot as plt
  454. # Function to average ERP over specific channels
  455. def average_erp_channels(erp, channel_list):
  456. # Select the specified channels and average the ERP over these channels
  457. selected_channels = erp.copy().pick_channels(channel_list)
  458. avg_erp = np.mean(selected_channels.data, axis=0) # Average over channels
  459. return avg_erp
  460. tmin_epochs=-0.5
  461. tmax_epochs=1.0
  462. import numpy as np
  463. import matplotlib.pyplot as plt
  464. # Function to calculate SEM (Standard Error of the Mean)
  465. def calculate_sem(erp_list):
  466. erp_array = np.array([erp.data for erp in erp_list]) # Convert list of ERPs to a 2D array (n_subjects, n_timepoints)
  467. sem = np.std(erp_array, axis=0) / np.sqrt(erp_array.shape[0]) # SEM = std / sqrt(n_subjects)
  468. return sem
  469. # Function to plot the average ERP with SEM for the three groups for a given stimulation type
  470. def plot_avg_erp_with_sem(erp_TD_face, sem_TD_face,
  471. erp_TD_shadow_face, sem_TD_shadow_face,
  472. erp_TD_U_face, sem_TD_U_face,
  473. erp_TD_shadow_U_face, sem_TD_shadow_U_face,
  474. erp_TD_obj, sem_TD_obj,
  475. erp_TD_shadow_obj, sem_TD_shadow_obj,
  476. erp_TD_U_obj, sem_TD_U_obj,
  477. erp_TD_shadow_U_obj, sem_TD_shadow_U_obj,
  478. group, channels, sfreq, plot_tmin=-0.1, plot_tmax=0.6):
  479. # Calculate time points based on the sampling frequency (sfreq)
  480. times = np.linspace(tmin_epochs, tmax_epochs, len(erp_TD_face))
  481. # Find the index range corresponding to the desired plot time window (-0.1 to 0.6 seconds)
  482. idx_min = np.searchsorted(times, plot_tmin)
  483. idx_max = np.searchsorted(times, plot_tmax)
  484. # Slice the ERP data, SEM, and time points for the plot time window
  485. times_plot = times[idx_min:idx_max]
  486. erp_TD_face_plot = erp_TD_face[idx_min:idx_max]
  487. sem_TD_face_plot = sem_TD_face[idx_min:idx_max]
  488. erp_TD_face_shadow_plot = erp_TD_shadow_face[idx_min:idx_max]
  489. sem_TD_face_shadow_plot = sem_TD_shadow_face[idx_min:idx_max]
  490. erp_TD_U_face_plot = erp_TD_U_face[idx_min:idx_max]
  491. sem_TD_U_face_plot = sem_TD_U_face[idx_min:idx_max]
  492. erp_TD_shadow_U_face_plot = erp_TD_shadow_U_face[idx_min:idx_max]
  493. sem_TD_shadow_U_face_plot = sem_TD_shadow_U_face[idx_min:idx_max]
  494. erp_TD_obj_plot = erp_TD_obj[idx_min:idx_max]
  495. sem_TD_obj_plot = sem_TD_obj[idx_min:idx_max]
  496. erp_TD_shadow_obj_plot = erp_TD_shadow_obj[idx_min:idx_max]
  497. sem_TD_shadow_obj_plot = sem_TD_shadow_obj[idx_min:idx_max]
  498. erp_TD_U_obj_plot = erp_TD_U_obj[idx_min:idx_max]
  499. sem_TD_U_obj_plot = sem_TD_U_obj[idx_min:idx_max]
  500. erp_TD_shadow_U_obj_plot = erp_TD_shadow_U_obj[idx_min:idx_max]
  501. sem_TD_shadow_U_obj_plot = sem_TD_shadow_U_obj[idx_min:idx_max]
  502. # Plotting the ERPs for the three groups with SEM shaded areas
  503. plt.figure(figsize=(10, 6))
  504. # TD group
  505. plt.plot(times_plot, erp_TD_face_plot, label='Faces', color='#b2182b')
  506. plt.fill_between(times_plot, erp_TD_face_plot - sem_TD_face_plot, erp_TD_face_plot + sem_TD_face_plot, color='#b2182b', alpha=0.3)
  507. # TD group
  508. plt.plot(times_plot, erp_TD_face_shadow_plot, label='Shadow Faces', color='#b2182b',linestyle=':')
  509. plt.fill_between(times_plot, erp_TD_face_shadow_plot - sem_TD_face_shadow_plot, erp_TD_face_shadow_plot + sem_TD_face_shadow_plot,
  510. color='#b2182b', alpha=0.3)
  511. # ASD group
  512. plt.plot(times_plot, erp_TD_U_face_plot, label='Inverted face', color='#ef8a62')
  513. plt.fill_between(times_plot, erp_TD_U_face_plot - sem_TD_U_face_plot, erp_TD_U_face_plot + sem_TD_U_face_plot, color='#ef8a62', alpha=0.3)
  514. # ASD group
  515. plt.plot(times_plot, erp_TD_shadow_U_face_plot, label='Inverted face shadow', color='#ef8a62',linestyle=':')
  516. plt.fill_between(times_plot, erp_TD_shadow_U_face_plot - sem_TD_shadow_U_face_plot, erp_TD_shadow_U_face_plot + sem_TD_shadow_U_face_plot,
  517. color='#ef8a62', alpha=0.3)
  518. # SIB group
  519. plt.plot(times_plot, erp_TD_obj_plot, label='Object', color='#2166ac')
  520. plt.fill_between(times_plot, erp_TD_obj_plot - sem_TD_obj_plot, erp_TD_obj_plot + sem_TD_obj_plot, color='#2166ac', alpha=0.3)
  521. # SIB group
  522. plt.plot(times_plot, erp_TD_shadow_obj_plot, label='Shadow Object', color='#2166ac',linestyle=':')
  523. plt.fill_between(times_plot, erp_TD_shadow_obj_plot - sem_TD_shadow_obj_plot, erp_TD_shadow_obj_plot + sem_TD_shadow_obj_plot,
  524. color='#2166ac', alpha=0.3)
  525. # SIB group
  526. plt.plot(times_plot, erp_TD_shadow_U_obj_plot, label='Shadow Object', color='#67a9cf')
  527. plt.fill_between(times_plot, erp_TD_U_obj_plot - sem_TD_U_obj_plot, erp_TD_U_obj_plot + sem_TD_U_obj_plot, color='#67a9cf', alpha=0.3)
  528. # SIB group
  529. plt.plot(times_plot, erp_TD_shadow_U_obj_plot, label='Shadow Inverted Object', color='#67a9cf',linestyle=':')
  530. plt.fill_between(times_plot, erp_TD_shadow_U_obj_plot - sem_TD_shadow_U_obj_plot, erp_TD_shadow_U_obj_plot + sem_TD_shadow_U_obj_plot,
  531. color='#67a9cf', alpha=0.3)
  532. # Add title and labels
  533. plt.title(f'Average ERP with SEM for {group} Stimulation ({", ".join(channels)})')
  534. plt.xlabel('Time (s)')
  535. plt.ylabel('Amplitude (µV)')
  536. plt.legend()
  537. plt.axvline(x=times_plot[12], color='black', linestyle='--')
  538. plt.ylim([-2e-6,17e-6])
  539. # plt.grid(True)
  540. # plt.xlim([0.05,0.175])
  541. plt.show()
  542. #%% Plot ERP - within group with SHADOW
  543. import numpy as np
  544. import matplotlib.pyplot as plt
  545. # Function to average ERP over specific channels
  546. def average_erp_channels(erp, channel_list):
  547. # Select the specified channels and average the ERP over these channels
  548. selected_channels = erp.copy().pick_channels(channel_list)
  549. avg_erp = np.mean(selected_channels.data, axis=0) # Average over channels
  550. return avg_erp
  551. tmin_epochs=-0.5
  552. tmax_epochs=1.0
  553. import numpy as np
  554. import matplotlib.pyplot as plt
  555. # Function to calculate SEM (Standard Error of the Mean)
  556. def calculate_sem(erp_list):
  557. erp_array = np.array([erp.data for erp in erp_list]) # Convert list of ERPs to a 2D array (n_subjects, n_timepoints)
  558. sem = np.std(erp_array, axis=0) / np.sqrt(erp_array.shape[0]) # SEM = std / sqrt(n_subjects)
  559. return sem
  560. # Function to plot the average ERP with SEM for the three groups for a given stimulation type
  561. def plot_avg_erp_with_sem(erp_TD_face, sem_TD_face, erp_TD_U_face, sem_TD_U_face,
  562. erp_TD_obj, sem_TD_obj, erp_TD_U_obj, sem_TD_U_obj, group, channels, sfreq, plot_tmin=-0.1, plot_tmax=0.6):
  563. # Calculate time points based on the sampling frequency (sfreq)
  564. times = np.linspace(tmin_epochs, tmax_epochs, len(erp_TD_face))
  565. # Find the index range corresponding to the desired plot time window (-0.1 to 0.6 seconds)
  566. idx_min = np.searchsorted(times, plot_tmin)
  567. idx_max = np.searchsorted(times, plot_tmax)
  568. # Slice the ERP data, SEM, and time points for the plot time window
  569. times_plot = times[idx_min:idx_max]
  570. erp_TD_face_plot = erp_TD_face[idx_min:idx_max]
  571. sem_TD_face_plot = sem_TD_face[idx_min:idx_max]
  572. erp_TD_U_face_plot = erp_TD_U_face[idx_min:idx_max]
  573. sem_TD_U_face_plot = sem_TD_U_face[idx_min:idx_max]
  574. erp_TD_obj_plot = erp_TD_obj[idx_min:idx_max]
  575. sem_TD_obj_plot = sem_TD_obj[idx_min:idx_max]
  576. erp_TD_U_obj_plot = erp_TD_U_obj[idx_min:idx_max]
  577. sem_TD_U_obj_plot = sem_TD_U_obj[idx_min:idx_max]
  578. # Plotting the ERPs for the three groups with SEM shaded areas
  579. plt.figure(figsize=(10, 6))
  580. # TD group
  581. plt.plot(times_plot, erp_TD_face_plot, label='Faces', color='#b2182b')
  582. plt.fill_between(times_plot, erp_TD_face_plot - sem_TD_face_plot, erp_TD_face_plot + sem_TD_face_plot, color='#b2182b', alpha=0.3)
  583. # ASD group
  584. plt.plot(times_plot, erp_TD_U_face_plot, label='Shadow Faces', color='#ef8a62')
  585. plt.fill_between(times_plot, erp_TD_U_face_plot - sem_TD_U_face_plot, erp_TD_U_face_plot + sem_TD_U_face_plot, color='#ef8a62', alpha=0.3)
  586. # SIB group
  587. plt.plot(times_plot, erp_TD_obj_plot, label='Object', color='#2166ac')
  588. plt.fill_between(times_plot, erp_TD_obj_plot - sem_TD_obj_plot, erp_TD_obj_plot + sem_TD_obj_plot, color='#2166ac', alpha=0.3)
  589. # SIB group
  590. plt.plot(times_plot, erp_TD_U_obj_plot, label='Shadow Object', color='#67a9cf')
  591. plt.fill_between(times_plot, erp_TD_U_obj_plot - sem_TD_U_obj_plot, erp_TD_U_obj_plot + sem_TD_U_obj_plot, color='#67a9cf', alpha=0.3)
  592. # Add title and labels
  593. plt.title(f'Average ERP with SEM for {group} Stimulation ({", ".join(channels)})')
  594. plt.xlabel('Time (s)')
  595. plt.ylabel('Amplitude (µV)')
  596. plt.legend()
  597. plt.axvline(x=times_plot[12], color='black', linestyle='--')
  598. plt.ylim([-2e-6,17e-6])
  599. # plt.grid(True)
  600. # plt.xlim([0.05,0.175])
  601. plt.show()
  602. #%% Plot ERP for TD
  603. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8']
  604. # visual_channels = ['C1', 'Cz', 'C2', 'CP1', 'CPz', 'CP2']
  605. # visual_channels = ['O1','O2']
  606. visual_channels = ['P7','P8']
  607. # visual_channels = ['Pz', 'CPz', 'P3', 'P4', 'POz']
  608. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8','P9','P7','P5','P3','P1','P2','P4','P6','P8','P10']
  609. # visual_channels = ['O1', 'PO7', 'PO3']
  610. # visual_channels = ['O2', 'PO8', 'PO4']
  611. # visual_channels = ['F1', 'Fz', 'F2']
  612. # visual_channels = ['FC1', 'FCz', 'FC2',
  613. # 'F1', 'Fz', 'F2']
  614. # Visual ERP: Calculate and plot the average ERP with SEM for visual stimulation ('V')
  615. avg_erp_face_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_TD], axis=0)
  616. sem_erp_face_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_TD])
  617. avg_erp_face_shadow_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_shadow_TD], axis=0)
  618. sem_erp_face_shadow_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_shadow_TD])
  619. avg_erp_U_face_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_U_TD], axis=0)
  620. sem_erp_U_face_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_U_TD])
  621. avg_erp_shadow_U_face_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_U_shadow_TD], axis=0)
  622. sem_erp_shadow_U_face_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_U_shadow_TD])
  623. avg_erp_obj_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_TD], axis=0)
  624. sem_erp_obj_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_TD])
  625. avg_erp_obj_shadow_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_shadow_TD], axis=0)
  626. sem_erp_obj_shadow_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_shadow_TD])
  627. avg_erp_U_obj_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_TD], axis=0)
  628. sem_erp_U_obj_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_TD])
  629. avg_erp_shadow_U_obj_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_shadow_TD], axis=0)
  630. sem_erp_shadow_U_obj_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_shadow_TD])
  631. # # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  632. # plot_avg_erp_with_sem(avg_erp_face_TD, sem_erp_face_TD, avg_erp_U_face_TD, sem_erp_U_face_TD,
  633. # avg_erp_obj_TD, sem_erp_obj_TD, avg_erp_U_obj_TD, sem_erp_U_obj_TD, 'Faces',
  634. # visual_channels, filtered_epochs_lst_TD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  635. # # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  636. # plot_avg_erp_with_sem(avg_erp_face_TD, sem_erp_face_TD, avg_erp_face_shadow_TD, sem_erp_face_shadow_TD,
  637. # avg_erp_obj_TD, sem_erp_obj_TD, avg_erp_obj_shadow_TD, sem_erp_obj_shadow_TD, 'Faces',
  638. # visual_channels, filtered_epochs_lst_TD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  639. plot_avg_erp_with_sem(avg_erp_face_TD, sem_erp_face_TD,
  640. avg_erp_face_shadow_TD, sem_erp_face_shadow_TD,
  641. avg_erp_U_face_TD, sem_erp_U_face_TD,
  642. avg_erp_shadow_U_face_TD, sem_erp_shadow_U_face_TD,
  643. avg_erp_obj_TD, sem_erp_obj_TD,
  644. avg_erp_obj_shadow_TD, sem_erp_obj_shadow_TD,
  645. avg_erp_U_obj_TD, sem_erp_U_obj_TD,
  646. avg_erp_shadow_U_obj_TD, sem_erp_shadow_U_obj_TD,
  647. 'Faces',
  648. visual_channels, filtered_epochs_lst_TD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  649. #%% Plot topomap - face
  650. Evoked_face_TD = mne.grand_average(erp_face_TD)
  651. Evoked_face_U_TD = mne.grand_average(erp_face_U_TD)
  652. Evoked_obj_TD = mne.grand_average(erp_obj_TD)
  653. Evoked_obj_U_TD = mne.grand_average(erp_obj_U_TD)
  654. times = [-0.05,0.12,0.180,0.260,0.31,0.4,0.5] # Time in seconds
  655. Evoked_face_TD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  656. Evoked_face_U_TD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  657. Evoked_obj_TD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  658. Evoked_obj_U_TD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  659. #%%
  660. from mne import combine_evoked
  661. # Compute difference wave: Face - Inverted Face
  662. # Evoked_diff_face = combine_evoked([Evoked_face_U_TD, Evoked_face_TD], weights=[1, -1])
  663. Evoked_diff_face = combine_evoked([Evoked_face_TD, Evoked_obj_TD], weights=[1, -1])
  664. # Define time points to plot
  665. times = [-0.05, 0.135, 0.180, 0.240] # seconds
  666. # Plot topomap of the difference
  667. Evoked_diff_face.plot_topomap(times=times, size=1, vlim=(-2, 2), cmap='RdBu_r')
  668. #%%
  669. from mne import combine_evoked
  670. # Compute difference wave: Face - Inverted Face
  671. # Evoked_diff_face_ASD = combine_evoked([Evoked_face_U_ASD, Evoked_face_ASD], weights=[1, -1])
  672. Evoked_diff_face_ASD = combine_evoked([Evoked_face_ASD, Evoked_obj_ASD], weights=[1, -1])
  673. # Define time points to plot
  674. # times = [-0.05, 0.135, 0.180, 0.240] # seconds
  675. times = [-0.05, 0.100, 0.160, 0.220, 0.290] # seconds
  676. # Plot topomap of the difference
  677. Evoked_diff_face_ASD.plot_topomap(times=times, size=1, vlim=(-4, 4), cmap='RdBu_r')
  678. #%%
  679. from mne import combine_evoked
  680. # Compute difference wave: Face - Inverted Face
  681. # Evoked_diff_face_SIB = combine_evoked([Evoked_face_U_SIB, Evoked_face_SIB], weights=[1, -1])
  682. Evoked_diff_face_SIB = combine_evoked([Evoked_face_SIB, Evoked_obj_SIB], weights=[1, -1])
  683. # Define time points to plot
  684. # times = [-0.05, 0.135, 0.180, 0.240] # seconds
  685. times = [-0.05, 0.100, 0.160, 0.220, 0.290] # seconds
  686. # Plot topomap of the difference
  687. Evoked_diff_face_SIB.plot_topomap(times=times, size=1, vlim=(-4, 4), cmap='RdBu_r')
  688. #%%
  689. from mne import combine_evoked
  690. # Compute difference wave: Face - Inverted Face
  691. Evoked_diff_face = combine_evoked([Evoked_face_TD, Evoked_obj_TD], weights=[1, -1])
  692. # Define time points to plot
  693. # times = [-0.05, 0.12, 0.180, 0.260, 0.31] # seconds
  694. times = [-0.05, 0.100, 0.160, 0.220, 0.290] # seconds
  695. # Plot topomap of the difference
  696. Evoked_diff_face.plot_topomap(times=times, size=1, vlim=(-4, 4), cmap='RdBu_r')
  697. #%% save
  698. plt.savefig('ERP_Diff_Wave_F_minus_O_SIB.svg')
  699. #%% Diff wave
  700. visual_channels = ['P7']
  701. # Compute difference wave per subject at the selected channels
  702. # diff_erp_subjects = [
  703. # average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  704. # for erp_face, erp_u_face in zip(erp_face_TD, erp_face_U_TD)
  705. # ]
  706. # # Compute difference wave per subject at the selected channels
  707. # diff_erp_subjects = [
  708. # average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  709. # for erp_face, erp_u_face in zip(erp_obj_TD, erp_face_TD)
  710. # ]
  711. # # Compute difference wave per subject at the selected channels
  712. # diff_erp_subjects = [
  713. # average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  714. # for erp_face, erp_u_face in zip(erp_obj_TD, erp_obj_U_TD)
  715. # ]
  716. # Compute difference wave per subject at the selected channels
  717. diff_erp_subjects = [
  718. average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  719. for erp_face, erp_u_face in zip(erp_face_SIB, erp_face_U_SIB)
  720. ]
  721. import numpy as np
  722. # Convert to array
  723. diff_erp_subjects = np.array(diff_erp_subjects)
  724. # Mean and SEM across subjects
  725. avg_diff_erp = np.mean(diff_erp_subjects, axis=0)
  726. sem_diff_erp = np.std(diff_erp_subjects, axis=0) / np.sqrt(len(diff_erp_subjects))
  727. import matplotlib.pyplot as plt
  728. # Time vector
  729. sfreq = filtered_epochs_lst_TD[0].info['sfreq']
  730. n_times = avg_diff_erp.shape[0]
  731. # Extract time vector directly from ERP
  732. times = erp_face_TD[0].times
  733. # Plot
  734. plt.figure(figsize=(10, 4))
  735. plt.plot(times, avg_diff_erp, label='Inverted Face - Face', color='black')
  736. plt.fill_between(times, avg_diff_erp - sem_diff_erp, avg_diff_erp + sem_diff_erp,
  737. color='black', alpha=0.3)
  738. plt.axhline(0, color='gray', linestyle='--')
  739. plt.axvline(0, color='gray', linestyle='--')
  740. plt.title(f'Difference ERP at {visual_channels}')
  741. plt.xlabel('Time (s)')
  742. plt.ylabel('Amplitude (µV)')
  743. plt.legend()
  744. plt.tight_layout()
  745. plt.show()
  746. plt.xlim([-0.1,0.6])
  747. plt.ylim([-7.1e-6,4.5e-6])
  748. #%%
  749. # Define channels
  750. chan_p8 = 'P8'
  751. chan_p7 = 'P7'
  752. # Define time window for N170 (in seconds)
  753. n170_window = (0.170, 0.190)
  754. # Find index of time window
  755. tmin_idx = np.argmin(np.abs(times - n170_window[0]))
  756. tmax_idx = np.argmin(np.abs(times - n170_window[1]))
  757. # Initialize peak values per subject
  758. n170_p8_peaks = []
  759. n170_p7_peaks = []
  760. for erp_face, erp_u_face in zip(erp_face_SIB, erp_face_U_SIB):
  761. # Get data for each channel (difference wave)
  762. diff_p8 = erp_u_face.copy().pick(chan_p8).data - erp_face.copy().pick(chan_p8).data
  763. diff_p7 = erp_u_face.copy().pick(chan_p7).data - erp_face.copy().pick(chan_p7).data
  764. # Extract min value (most negative) in the N170 time window
  765. min_p8 = np.min(diff_p8[0, tmin_idx:tmax_idx])
  766. min_p7 = np.min(diff_p7[0, tmin_idx:tmax_idx])
  767. # Append to lists
  768. n170_p8_peaks.append(min_p8)
  769. n170_p7_peaks.append(min_p7)
  770. #%%
  771. n170_p8_peaks = np.array(n170_p8_peaks)
  772. n170_p7_peaks = np.array(n170_p7_peaks)
  773. # Simple difference
  774. li = n170_p8_peaks - n170_p7_peaks
  775. li = li*1e6
  776. #%% Cluster-based permutation test on the diff wave
  777. from mne.stats import permutation_cluster_1samp_test
  778. times = erp_face_TD[0].times
  779. plot_tmin = -0.1
  780. plot_tmax = 0.6
  781. # Find the index range corresponding to the desired plot time window
  782. idx_min = np.searchsorted(times, plot_tmin)
  783. idx_max = np.searchsorted(times, plot_tmax)
  784. # Slice the ERP data, SEM, and time points for the plot time window
  785. times_plot = times[idx_min:idx_max]
  786. # Compute the difference per subject
  787. X_diff = diff_erp_subjects[:,idx_min:idx_max]
  788. # Run one-sample cluster permutation test (null: mean = 0)
  789. T_obs, clusters, cluster_p_values, H0 = permutation_cluster_1samp_test(
  790. X_diff, n_permutations=1000, tail=0, threshold=None, out_type='mask', verbose=True
  791. )
  792. # Plot mean difference with significant clusters
  793. plt.figure(figsize=(10, 5))
  794. mean_diff = X_diff.mean(axis=0)
  795. plt.plot(times_plot, mean_diff, label='P100 - P33', color='black')
  796. # Highlight significant clusters
  797. for i, cluster in enumerate(clusters):
  798. if cluster_p_values[i] < 0.05:
  799. cluster_inds = cluster
  800. plt.axvspan(times_plot[cluster_inds][0], times_plot[cluster_inds][-1],
  801. color='gray', alpha=0.3)
  802. plt.axhline(0, color='gray', linestyle='--')
  803. plt.legend()
  804. plt.xlabel('Time (s)')
  805. plt.ylabel('Alpha Power Difference (a.u.)')
  806. plt.title('Alpha Power: Difference P100 - P33 (Cluster Permutation, p < 0.05 shaded)')
  807. plt.tight_layout()
  808. plt.show()
  809. #%% T-test permutation test
  810. from mne.stats import permutation_t_test
  811. import numpy as np
  812. import matplotlib.pyplot as plt
  813. # Time window setup
  814. times = erp_face_TD[0].times
  815. plot_tmin = -0.1
  816. plot_tmax = 0.6
  817. idx_min = np.searchsorted(times, plot_tmin)
  818. idx_max = np.searchsorted(times, plot_tmax)
  819. times_plot = times[idx_min:idx_max]
  820. # Data: shape (n_subjects, n_times)
  821. X_diff = diff_erp_subjects[:, idx_min:idx_max]
  822. # Run tmax permutation test (one-sample against 0)
  823. p_values_tmax = permutation_t_test(
  824. X_diff, n_permutations=1000, tail=0, seed=42, n_jobs=1
  825. )
  826. # Plot
  827. plt.figure(figsize=(10, 5))
  828. mean_diff = X_diff.mean(axis=0)
  829. plt.plot(times_plot, mean_diff, label='P100 - P33', color='black')
  830. # Mark significant time points (p < 0.05)
  831. significant_timepoints = p_values_tmax[1] < 0.05
  832. plt.plot(times_plot[significant_timepoints], mean_diff[significant_timepoints],
  833. 'o', color='red', label='p < 0.05 (tmax corrected)')
  834. plt.axhline(0, color='gray', linestyle='--')
  835. plt.legend()
  836. plt.xlabel('Time (s)')
  837. plt.ylabel('Alpha Power Difference (a.u.)')
  838. plt.title('Alpha Power: Difference P100 - P33 (tmax Permutation Test)')
  839. plt.tight_layout()
  840. plt.show()
  841. #%%
  842. import numpy as np
  843. # 150–250 ms window on your existing times_plot
  844. win = (times_plot >= 0.150) & (times_plot <= 0.250)
  845. if not np.any(win):
  846. raise ValueError("No samples found between 150–250 ms in times_plot.")
  847. X_win = X_diff[:, win] # (n_subjects, n_times_in_window)
  848. t_win = times_plot[win] # (n_times_in_window,)
  849. # Per-subject minimum (peak) and latency
  850. # Use nanargmin to tolerate NaNs; if an entire row is NaNs, handle separately
  851. argmin_per_subj = np.nanargmin(X_win, axis=1)
  852. peak_per_subj = X_win[np.arange(X_win.shape[0]), argmin_per_subj] * 1e6
  853. lat_per_subj = t_win[argmin_per_subj] # seconds
  854. lat_ms_per_subj = (lat_per_subj * 1000.0) # ms
  855. # Optional: flag subjects with all-NaN in the window
  856. all_nan_rows = np.isnan(X_win).all(axis=1)
  857. # Group-mean minimum and latency (within 150–250 ms)
  858. mean_trace = np.nanmean(X_win, axis=0)
  859. idx_mean = np.nanargmin(mean_trace)
  860. group_peak = mean_trace[idx_mean]
  861. group_lat = t_win[idx_mean] # seconds
  862. group_lat_ms = group_lat * 1000.0 # ms
  863. print(f"Group minimum: {group_peak:.4f} at {group_lat_ms:.1f} ms")
  864. # peak_per_subj and lat_ms_per_subj hold per-subject values/latencies
  865. #%% Spatio-temporal cluster test for the difference wave between inverted faces and face
  866. import mne
  867. import numpy as np
  868. import matplotlib.pyplot as plt
  869. from mne.stats import spatio_temporal_cluster_1samp_test
  870. # Assuming you have lists of evoked data for each condition
  871. evoked_audio_0 = erp_face_SIB # Replace with your actual Evoked objects for condition 'Audio_0'
  872. evoked_audio_1 = erp_obj_SIB # Replace with your actual Evoked objects for condition 'Audio_1'
  873. adjacency, ch_names = mne.channels.find_ch_adjacency(erp_face_TD[0].info, ch_type='eeg')
  874. # Define the time window of interest
  875. time_min = -0.1 # Start time
  876. time_max = 0.6 # End time
  877. # Find the indices corresponding to the time window of interest
  878. time_indices = np.where((evoked_audio_0[0].times >= time_min) & (evoked_audio_0[0].times <= time_max))[0]
  879. # Extract data for the specific time window
  880. X_0 = np.array([evk.data[:, time_indices] for evk in evoked_audio_0])
  881. X_1 = np.array([evk.data[:, time_indices] for evk in evoked_audio_1])
  882. # Calculate the difference between the two conditions for each subject
  883. difference = X_0 - X_1
  884. difference = np.transpose(difference, (0, 2, 1))
  885. # Perform the cluster-based permutation test on the difference from zero
  886. # across both time and channel dimensions
  887. T_obs, clusters, cluster_p_values, H0 = spatio_temporal_cluster_1samp_test(
  888. difference, adjacency=adjacency,n_permutations=1000, tail=0, threshold=None, out_type='mask'
  889. )
  890. T_plot = T_obs.T # shape (n_channels, n_times)
  891. # Get the channel names and times
  892. ch_names = evoked_audio_0[0].info['ch_names']
  893. times = evoked_audio_0[0].times[time_indices] # Use the subset of times within the range
  894. # times = evoked_audio_0[0].times
  895. n_channels = len(ch_names)
  896. # Create a mask for significant clusters (p < 0.05)
  897. significant_mask = np.zeros(T_obs.shape, dtype=bool)
  898. for i_clu, clu_p_value in enumerate(cluster_p_values):
  899. if clu_p_value < 0.01:
  900. significant_mask[clusters[i_clu]] = True
  901. # # Plot the T-values with a mask for significant clusters
  902. # fig, ax = plt.subplots(figsize=(12, 8))
  903. # im = ax.imshow(T_plot, aspect='auto', origin='upper', cmap='RdBu_r',
  904. # extent=[times[0], times[-1], 0, n_channels],
  905. # vmin=-np.max(np.abs(T_obs)), vmax=np.max(np.abs(T_obs)))
  906. # plt.colorbar(im, ax=ax, label='T-value')
  907. # Plot the T-values with a mask for significant clusters
  908. fig, ax = plt.subplots(figsize=(12, 8))
  909. im = ax.imshow(T_plot, aspect='auto', origin='upper', cmap='RdBu_r',
  910. extent=[times[0], times[-1], 0, n_channels],
  911. vmin=-7, vmax=7)
  912. plt.colorbar(im, ax=ax, label='T-value')
  913. # Show actual channel names on the y-axis
  914. ax.set_yticks(np.arange(len(ch_names)))
  915. ax.set_yticklabels(ch_names[::-1]) # reverse the list
  916. # Optional: make labels smaller if they overlap
  917. ax.tick_params(axis='y', labelsize=5)
  918. # Highlight significant clusters
  919. ax.contour(significant_mask.T, levels=[0.5], colors='black',
  920. linewidths=0.5, origin='upper', extent=[times[0], times[-1], 0, n_channels])
  921. # Labeling the axes with 4 segments (adjusted to handle different channel counts)
  922. num_segments = 4
  923. segment_size = n_channels // num_segments
  924. yticks = np.arange(segment_size // 2, n_channels, segment_size)
  925. # Adjust yticklabels to match the actual number of yticks
  926. yticklabels = [f'{start}-{min(end, n_channels)}' for start, end in
  927. zip(range(0, n_channels, segment_size),
  928. range(segment_size, n_channels + segment_size, segment_size))]
  929. yticklabels = yticklabels[:len(yticks)] # Ensure matching lengths
  930. # Apply yticks and yticklabels to the plot
  931. ax.set_yticks(yticks)
  932. ax.set_yticklabels(yticklabels)
  933. ax.set_xlabel('Time (s)')
  934. ax.set_ylabel('Channels (Grouped)')
  935. ax.set_title('Cluster Plot of T-values with Significant Clusters Highlighted')
  936. plt.show()
  937. # # Add topomaps
  938. # for segment in range(num_segments):
  939. # start_idx = segment * segment_size
  940. # end_idx = (segment + 1) * segment_size
  941. # ax_topo = fig.add_axes([0.85, 0.1 + i * 0.2, 0.1, 0.2]) # now top-to-bottom aligns correctly
  942. # mask = np.zeros(n_channels, dtype=bool)
  943. # mask[start_idx:end_idx] = True
  944. # mask_params = dict(marker='o', markerfacecolor='r', markeredgecolor='r', linewidth=0)
  945. # mne.viz.plot_topomap(np.zeros(n_channels), evoked_audio_0[0].info, axes=ax_topo, show=False, mask=mask, mask_params=mask_params)
  946. # ax_topo.set_title(f'Channels {start_idx}-{end_idx}', fontsize=8)
  947. # plt.show()
  948. for i, segment in enumerate(reversed(range(num_segments))):
  949. start_idx = segment * segment_size
  950. end_idx = (segment + 1) * segment_size
  951. ax_topo = fig.add_axes([0.85, 0.1 + i * 0.2, 0.1, 0.2]) # now top-to-bottom aligns correctly
  952. mask = np.zeros(n_channels, dtype=bool)
  953. mask[start_idx:end_idx] = True
  954. mask_params = dict(marker='o', markerfacecolor='r', markeredgecolor='r', linewidth=0)
  955. mne.viz.plot_topomap(np.zeros(n_channels), evoked_audio_0[0].info, axes=ax_topo,
  956. show=False, mask=mask, mask_params=mask_params)
  957. ax_topo.set_title(f'Channels {start_idx}-{end_idx}', fontsize=8)
  958. #%% Plot ERP for ASD
  959. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8']
  960. # visual_channels = ['C1', 'Cz', 'C2', 'CP1', 'CPz', 'CP2']
  961. # visual_channels = ['O1','O2']
  962. visual_channels = ['P7']
  963. # visual_channels = ['Pz', 'CPz', 'P3', 'P4', 'POz']
  964. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8','P9','P7','P5','P3','P1','P2','P4','P6','P8','P10']
  965. # visual_channels = ['O1', 'PO7', 'PO3']
  966. # visual_channels = ['O2', 'PO8', 'PO4']
  967. # visual_channels = ['F1', 'Fz', 'F2']
  968. # Visual ERP: Calculate and plot the average ERP with SEM for visual stimulation ('V')
  969. avg_erp_face_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_ASD], axis=0)
  970. sem_erp_face_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_ASD])
  971. # avg_erp_face_shadow_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_shadow_ASD], axis=0)
  972. # sem_erp_face_shadow_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_shadow_ASD])
  973. avg_erp_U_face_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_U_ASD], axis=0)
  974. sem_erp_U_face_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_U_ASD])
  975. avg_erp_shadow_U_face_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_U_shadow_ASD], axis=0)
  976. sem_erp_shadow_U_face_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_U_shadow_ASD])
  977. avg_erp_obj_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_ASD], axis=0)
  978. sem_erp_obj_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_ASD])
  979. avg_erp_obj_shadow_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_shadow_ASD], axis=0)
  980. sem_erp_obj_shadow_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_shadow_ASD])
  981. avg_erp_U_obj_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_ASD], axis=0)
  982. sem_erp_U_obj_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_ASD])
  983. avg_erp_shadow_U_obj_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_shadow_ASD], axis=0)
  984. sem_erp_shadow_U_obj_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_shadow_ASD])
  985. # # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  986. # plot_avg_erp_with_sem(avg_erp_face_ASD, sem_erp_face_ASD, avg_erp_U_face_ASD, sem_erp_U_face_ASD,
  987. # avg_erp_obj_ASD, sem_erp_obj_ASD, avg_erp_U_obj_ASD, sem_erp_U_obj_ASD, 'Faces',
  988. # visual_channels, filtered_epochs_lst_ASD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  989. # # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  990. # plot_avg_erp_with_sem(avg_erp_face_ASD, sem_erp_face_ASD, avg_erp_face_shadow_ASD, sem_erp_face_shadow_ASD,
  991. # avg_erp_obj_ASD, sem_erp_obj_ASD, avg_erp_obj_shadow_ASD, sem_erp_obj_shadow_ASD, 'Faces',
  992. # visual_channels, filtered_epochs_lst_ASD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  993. plot_avg_erp_with_sem(avg_erp_face_ASD, sem_erp_face_ASD,
  994. avg_erp_face_shadow_ASD, sem_erp_face_shadow_ASD,
  995. avg_erp_U_face_ASD, sem_erp_U_face_ASD,
  996. avg_erp_shadow_U_face_ASD, sem_erp_shadow_U_face_ASD,
  997. avg_erp_obj_ASD, sem_erp_obj_ASD,
  998. avg_erp_obj_shadow_ASD, sem_erp_obj_shadow_ASD,
  999. avg_erp_U_obj_ASD, sem_erp_U_obj_ASD,
  1000. avg_erp_shadow_U_obj_ASD, sem_erp_shadow_U_obj_ASD,
  1001. 'Faces',
  1002. visual_channels, filtered_epochs_lst_ASD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  1003. #%% Diff wave
  1004. # # Compute difference wave per subject at the selected channels
  1005. diff_erp_subjects = [
  1006. average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  1007. for erp_face, erp_u_face in zip(erp_face_ASD, erp_face_U_ASD)
  1008. ]
  1009. # # Compute difference wave per subject at the selected channels
  1010. # diff_erp_subjects = [
  1011. # average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  1012. # for erp_face, erp_u_face in zip(erp_obj_ASD, erp_face_ASD)
  1013. # ]
  1014. # Compute difference wave per subject at the selected channels
  1015. # diff_erp_subjects = [
  1016. # average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  1017. # for erp_face, erp_u_face in zip(erp_obj_ASD, erp_obj_U_ASD)
  1018. # ]
  1019. import numpy as np
  1020. # Convert to array
  1021. diff_erp_subjects = np.array(diff_erp_subjects)
  1022. # Mean and SEM across subjects
  1023. avg_diff_erp = np.mean(diff_erp_subjects, axis=0)
  1024. sem_diff_erp = np.std(diff_erp_subjects, axis=0) / np.sqrt(len(diff_erp_subjects))
  1025. import matplotlib.pyplot as plt
  1026. # Time vector
  1027. sfreq = filtered_epochs_lst_TD[0].info['sfreq']
  1028. n_times = avg_diff_erp.shape[0]
  1029. # Extract time vector directly from ERP
  1030. times = erp_face_TD[0].times
  1031. # Plot
  1032. plt.figure(figsize=(10, 4))
  1033. plt.plot(times, avg_diff_erp, label='Inverted Face - Face', color='black')
  1034. plt.fill_between(times, avg_diff_erp - sem_diff_erp, avg_diff_erp + sem_diff_erp,
  1035. color='black', alpha=0.3)
  1036. plt.axhline(0, color='gray', linestyle='--')
  1037. plt.axvline(0, color='gray', linestyle='--')
  1038. plt.title(f'Difference ERP at {visual_channels}')
  1039. plt.xlabel('Time (s)')
  1040. plt.ylabel('Amplitude (µV)')
  1041. plt.legend()
  1042. plt.tight_layout()
  1043. plt.show()
  1044. plt.xlim([-0.1,0.6])
  1045. plt.ylim([-7.1e-6,4.5e-6])
  1046. #%% Plot topomap - face
  1047. Evoked_face_ASD = mne.grand_average(erp_face_ASD)
  1048. Evoked_face_U_ASD = mne.grand_average(erp_face_U_ASD)
  1049. Evoked_obj_ASD = mne.grand_average(erp_obj_ASD)
  1050. Evoked_obj_U_ASD = mne.grand_average(erp_obj_U_ASD)
  1051. times = [-0.05,0.12,0.180,0.260,0.31] # Time in seconds
  1052. Evoked_face_ASD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  1053. Evoked_face_U_ASD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  1054. Evoked_obj_ASD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  1055. Evoked_obj_U_ASD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  1056. #%% save
  1057. plt.savefig('ERP_Topomap_Face_U_ASD.svg')
  1058. #%% Plot ERP for SIB
  1059. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8']
  1060. # visual_channels = ['C1', 'Cz', 'C2', 'CP1', 'CPz', 'CP2']
  1061. visual_channels = ['P7','P8']
  1062. # visual_channels = ['P10']
  1063. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8','P9','P7','P5','P3','P1','P2','P4','P6','P8','P10']
  1064. # visual_channels = ['O1', 'PO7', 'PO3']
  1065. # visual_channels = ['O2', 'PO8', 'PO4']
  1066. # visual_channels = ['F1', 'Fz', 'F2']
  1067. # Visual ERP: Calculate and plot the average ERP with SEM for visual stimulation ('V')
  1068. avg_erp_face_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_SIB], axis=0)
  1069. sem_erp_face_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_SIB])
  1070. avg_erp_face_shadow_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_shadow_SIB], axis=0)
  1071. sem_erp_face_shadow_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_shadow_SIB])
  1072. avg_erp_U_face_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_U_SIB], axis=0)
  1073. sem_erp_U_face_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_U_SIB])
  1074. avg_erp_shadow_U_face_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_U_shadow_SIB], axis=0)
  1075. sem_erp_shadow_U_face_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_U_shadow_SIB])
  1076. avg_erp_obj_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_SIB], axis=0)
  1077. sem_erp_obj_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_SIB])
  1078. avg_erp_obj_shadow_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_shadow_SIB], axis=0)
  1079. sem_erp_obj_shadow_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_shadow_SIB])
  1080. avg_erp_U_obj_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_SIB], axis=0)
  1081. sem_erp_U_obj_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_SIB])
  1082. avg_erp_shadow_U_obj_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_shadow_SIB], axis=0)
  1083. sem_erp_shadow_U_obj_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_shadow_SIB])
  1084. # # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  1085. # plot_avg_erp_with_sem(avg_erp_face_SIB, sem_erp_face_SIB, avg_erp_U_face_SIB, sem_erp_U_face_SIB,
  1086. # avg_erp_obj_SIB, sem_erp_obj_SIB, avg_erp_U_obj_SIB, sem_erp_U_obj_SIB, 'Faces',
  1087. # visual_channels, filtered_epochs_lst_SIB[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  1088. # # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  1089. # plot_avg_erp_with_sem(avg_erp_face_SIB, sem_erp_face_SIB, avg_erp_face_shadow_SIB, sem_erp_face_shadow_SIB,
  1090. # avg_erp_obj_SIB, sem_erp_obj_SIB, avg_erp_obj_shadow_SIB, sem_erp_obj_shadow_SIB, 'Faces',
  1091. # visual_channels, filtered_epochs_lst_SIB[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  1092. plot_avg_erp_with_sem(avg_erp_face_SIB, sem_erp_face_SIB,
  1093. avg_erp_face_shadow_SIB, sem_erp_face_shadow_SIB,
  1094. avg_erp_U_face_SIB, sem_erp_U_face_SIB,
  1095. avg_erp_shadow_U_face_SIB, sem_erp_shadow_U_face_SIB,
  1096. avg_erp_obj_SIB, sem_erp_obj_SIB,
  1097. avg_erp_obj_shadow_SIB, sem_erp_obj_shadow_SIB,
  1098. avg_erp_U_obj_SIB, sem_erp_U_obj_SIB,
  1099. avg_erp_shadow_U_obj_SIB, sem_erp_shadow_U_obj_SIB,
  1100. 'Faces',
  1101. visual_channels, filtered_epochs_lst_SIB[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  1102. #%% Diff wave
  1103. # # Compute difference wave per subject at the selected channels
  1104. # diff_erp_subjects = [
  1105. # average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  1106. # for erp_face, erp_u_face in zip(erp_face_SIB, erp_face_U_SIB)
  1107. # ]
  1108. # # Compute difference wave per subject at the selected channels
  1109. # diff_erp_subjects = [
  1110. # average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  1111. # for erp_face, erp_u_face in zip(erp_obj_SIB, erp_face_SIB)
  1112. # ]
  1113. # Compute difference wave per subject at the selected channels
  1114. diff_erp_subjects = [
  1115. average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  1116. for erp_face, erp_u_face in zip(erp_obj_ASD, erp_obj_U_ASD)
  1117. ]
  1118. import numpy as np
  1119. # Convert to array
  1120. diff_erp_subjects = np.array(diff_erp_subjects)
  1121. # Mean and SEM across subjects
  1122. avg_diff_erp = np.mean(diff_erp_subjects, axis=0)
  1123. sem_diff_erp = np.std(diff_erp_subjects, axis=0) / np.sqrt(len(diff_erp_subjects))
  1124. import matplotlib.pyplot as plt
  1125. # Time vector
  1126. sfreq = filtered_epochs_lst_TD[0].info['sfreq']
  1127. n_times = avg_diff_erp.shape[0]
  1128. # Extract time vector directly from ERP
  1129. times = erp_face_TD[0].times
  1130. # Plot
  1131. plt.figure(figsize=(10, 4))
  1132. plt.plot(times, avg_diff_erp, label='Inverted Face - Face', color='black')
  1133. plt.fill_between(times, avg_diff_erp - sem_diff_erp, avg_diff_erp + sem_diff_erp,
  1134. color='black', alpha=0.3)
  1135. plt.axhline(0, color='gray', linestyle='--')
  1136. plt.axvline(0, color='gray', linestyle='--')
  1137. plt.title(f'Difference ERP at {visual_channels}')
  1138. plt.xlabel('Time (s)')
  1139. plt.ylabel('Amplitude (µV)')
  1140. plt.legend()
  1141. plt.tight_layout()
  1142. plt.show()
  1143. plt.xlim([-0.1,0.6])
  1144. plt.ylim([-7.1e-6,4.5e-6])
  1145. #%% Plot topomap - face
  1146. Evoked_face_SIB = mne.grand_average(erp_face_SIB)
  1147. Evoked_face_U_SIB = mne.grand_average(erp_face_U_SIB)
  1148. Evoked_obj_SIB = mne.grand_average(erp_obj_SIB)
  1149. Evoked_obj_U_SIB = mne.grand_average(erp_obj_U_SIB)
  1150. times = [-0.05,0.12,0.180,0.260,0.31] # Time in seconds
  1151. Evoked_face_SIB.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  1152. Evoked_face_U_SIB.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  1153. Evoked_obj_SIB.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  1154. Evoked_obj_U_SIB.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  1155. #%% save
  1156. plt.savefig('ERP_Topomap_Face_U_SIB.svg')
  1157. #%% Calculate P1 latency and amplitude for left cluster
  1158. def get_p1_peak(evoked_list, channel_names, tmin=0.07, tmax=0.20):
  1159. latencies = []
  1160. amplitudes = []
  1161. for evoked in evoked_list:
  1162. ch_indices = [evoked.ch_names.index(ch) for ch in channel_names]
  1163. time_mask = (evoked.times >= tmin) & (evoked.times <= tmax)
  1164. times = evoked.times[time_mask]
  1165. data = evoked.data[ch_indices, :][:, time_mask]
  1166. mean_data = data.mean(axis=0)
  1167. if mean_data.size > 0:
  1168. peak_idx = mean_data.argmax()
  1169. latencies.append(times[peak_idx] * 1000) # ms
  1170. amplitudes.append(mean_data[peak_idx] * 1e6) # µV
  1171. else:
  1172. latencies.append(np.nan)
  1173. amplitudes.append(np.nan)
  1174. return latencies, amplitudes
  1175. left_cluster = ['O1']
  1176. right_cluster = ['O2']
  1177. # For amplitude
  1178. # # TD Group
  1179. amp_face_TD_L = get_p1_peak(erp_face_TD, left_cluster)[1]
  1180. amp_face_TD_R = get_p1_peak(erp_face_TD, right_cluster)[1]
  1181. amp_face_U_TD_L = get_p1_peak(erp_face_U_TD, left_cluster)[1]
  1182. amp_face_U_TD_R = get_p1_peak(erp_face_U_TD, right_cluster)[1]
  1183. amp_obj_TD_L = get_p1_peak(erp_obj_TD, left_cluster)[1]
  1184. amp_obj_TD_R = get_p1_peak(erp_obj_TD, right_cluster)[1]
  1185. amp_obj_U_TD_L = get_p1_peak(erp_obj_U_TD, left_cluster)[1]
  1186. amp_obj_U_TD_R = get_p1_peak(erp_obj_U_TD, right_cluster)[1]
  1187. # ASD Group
  1188. amp_face_ASD_L = get_p1_peak(erp_face_ASD, left_cluster)[1]
  1189. amp_face_ASD_R = get_p1_peak(erp_face_ASD, right_cluster)[1]
  1190. amp_face_U_ASD_L = get_p1_peak(erp_face_U_ASD, left_cluster)[1]
  1191. amp_face_U_ASD_R = get_p1_peak(erp_face_U_ASD, right_cluster)[1]
  1192. amp_obj_ASD_L = get_p1_peak(erp_obj_ASD, left_cluster)[1]
  1193. amp_obj_ASD_R = get_p1_peak(erp_obj_ASD, right_cluster)[1]
  1194. amp_obj_U_ASD_L = get_p1_peak(erp_obj_U_ASD, left_cluster)[1]
  1195. amp_obj_U_ASD_R = get_p1_peak(erp_obj_U_ASD, right_cluster)[1]
  1196. # SIB Group
  1197. amp_face_SIB_L = get_p1_peak(erp_face_SIB, left_cluster)[1]
  1198. amp_face_SIB_R = get_p1_peak(erp_face_SIB, right_cluster)[1]
  1199. amp_face_U_SIB_L = get_p1_peak(erp_face_U_SIB, left_cluster)[1]
  1200. amp_face_U_SIB_R = get_p1_peak(erp_face_U_SIB, right_cluster)[1]
  1201. amp_obj_SIB_L = get_p1_peak(erp_obj_SIB, left_cluster)[1]
  1202. amp_obj_SIB_R = get_p1_peak(erp_obj_SIB, right_cluster)[1]
  1203. amp_obj_U_SIB_L = get_p1_peak(erp_obj_U_SIB, left_cluster)[1]
  1204. amp_obj_U_SIB_R = get_p1_peak(erp_obj_U_SIB, right_cluster)[1]
  1205. # For latency
  1206. # TD Group
  1207. amp_face_TD_L = get_p1_peak(erp_face_TD, left_cluster)[0]
  1208. amp_face_TD_R = get_p1_peak(erp_face_TD, right_cluster)[0]
  1209. amp_face_U_TD_L = get_p1_peak(erp_face_U_TD, left_cluster)[0]
  1210. amp_face_U_TD_R = get_p1_peak(erp_face_U_TD, right_cluster)[0]
  1211. amp_obj_TD_L = get_p1_peak(erp_obj_TD, left_cluster)[0]
  1212. amp_obj_TD_R = get_p1_peak(erp_obj_TD, right_cluster)[0]
  1213. amp_obj_U_TD_L = get_p1_peak(erp_obj_U_TD, left_cluster)[0]
  1214. amp_obj_U_TD_R = get_p1_peak(erp_obj_U_TD, right_cluster)[0]
  1215. # ASD Group
  1216. amp_face_ASD_L = get_p1_peak(erp_face_ASD, left_cluster)[0]
  1217. amp_face_ASD_R = get_p1_peak(erp_face_ASD, right_cluster)[0]
  1218. amp_face_U_ASD_L = get_p1_peak(erp_face_U_ASD, left_cluster)[0]
  1219. amp_face_U_ASD_R = get_p1_peak(erp_face_U_ASD, right_cluster)[0]
  1220. amp_obj_ASD_L = get_p1_peak(erp_obj_ASD, left_cluster)[0]
  1221. amp_obj_ASD_R = get_p1_peak(erp_obj_ASD, right_cluster)[0]
  1222. amp_obj_U_ASD_L = get_p1_peak(erp_obj_U_ASD, left_cluster)[0]
  1223. amp_obj_U_ASD_R = get_p1_peak(erp_obj_U_ASD, right_cluster)[0]
  1224. # SIB Group
  1225. amp_face_SIB_L = get_p1_peak(erp_face_SIB, left_cluster)[0]
  1226. amp_face_SIB_R = get_p1_peak(erp_face_SIB, right_cluster)[0]
  1227. amp_face_U_SIB_L = get_p1_peak(erp_face_U_SIB, left_cluster)[0]
  1228. amp_face_U_SIB_R = get_p1_peak(erp_face_U_SIB, right_cluster)[0]
  1229. amp_obj_SIB_L = get_p1_peak(erp_obj_SIB, left_cluster)[0]
  1230. amp_obj_SIB_R = get_p1_peak(erp_obj_SIB, right_cluster)[0]
  1231. amp_obj_U_SIB_L = get_p1_peak(erp_obj_U_SIB, left_cluster)[0]
  1232. amp_obj_U_SIB_R = get_p1_peak(erp_obj_U_SIB, right_cluster)[0]
  1233. def make_df(amplitudes, condition, cluster, group):
  1234. return pd.DataFrame({
  1235. 'Amplitude (µV)': amplitudes,
  1236. 'Condition': condition,
  1237. 'Cluster': cluster,
  1238. 'Group': group
  1239. })
  1240. # Concatenate all into one dataframe
  1241. df_all = pd.concat([
  1242. # TD
  1243. make_df(amp_face_TD_L, 'Face', 'Left', 'TD'),
  1244. make_df(amp_face_TD_R, 'Face', 'Right', 'TD'),
  1245. make_df(amp_face_U_TD_L, 'Inverted Face', 'Left', 'TD'),
  1246. make_df(amp_face_U_TD_R, 'Inverted Face', 'Right', 'TD'),
  1247. make_df(amp_obj_TD_L, 'Object', 'Left', 'TD'),
  1248. make_df(amp_obj_TD_R, 'Object', 'Right', 'TD'),
  1249. make_df(amp_obj_U_TD_L, 'Inverted Object', 'Left', 'TD'),
  1250. make_df(amp_obj_U_TD_R, 'Inverted Object', 'Right', 'TD'),
  1251. # ASD
  1252. make_df(amp_face_ASD_L, 'Face', 'Left', 'ASD'),
  1253. make_df(amp_face_ASD_R, 'Face', 'Right', 'ASD'),
  1254. make_df(amp_face_U_ASD_L, 'Inverted Face', 'Left', 'ASD'),
  1255. make_df(amp_face_U_ASD_R, 'Inverted Face', 'Right', 'ASD'),
  1256. make_df(amp_obj_ASD_L, 'Object', 'Left', 'ASD'),
  1257. make_df(amp_obj_ASD_R, 'Object', 'Right', 'ASD'),
  1258. make_df(amp_obj_U_ASD_L, 'Inverted Object', 'Left', 'ASD'),
  1259. make_df(amp_obj_U_ASD_R, 'Inverted Object', 'Right', 'ASD'),
  1260. # SIB
  1261. make_df(amp_face_SIB_L, 'Face', 'Left', 'SIB'),
  1262. make_df(amp_face_SIB_R, 'Face', 'Right', 'SIB'),
  1263. make_df(amp_face_U_SIB_L, 'Inverted Face', 'Left', 'SIB'),
  1264. make_df(amp_face_U_SIB_R, 'Inverted Face', 'Right', 'SIB'),
  1265. make_df(amp_obj_SIB_L, 'Object', 'Left', 'SIB'),
  1266. make_df(amp_obj_SIB_R, 'Object', 'Right', 'SIB'),
  1267. make_df(amp_obj_U_SIB_L, 'Inverted Object', 'Left', 'SIB'),
  1268. make_df(amp_obj_U_SIB_R, 'Inverted Object', 'Right', 'SIB'),
  1269. ], ignore_index=True)
  1270. #%%
  1271. import seaborn as sns
  1272. import matplotlib.pyplot as plt
  1273. plt.figure(figsize=(14, 6))
  1274. sns.boxplot(x='Condition', y='Amplitude (µV)', hue='Group', data=df_all, palette='Set2')
  1275. sns.despine()
  1276. plt.title('P1 Amplitudes by Condition, Hemisphere, and Group')
  1277. plt.xticks(rotation=45)
  1278. plt.tight_layout()
  1279. plt.show()
  1280. #%%
  1281. g = sns.catplot(
  1282. data=df_all,
  1283. x='Condition',
  1284. y='Amplitude (µV)',
  1285. hue='Group',
  1286. col='Cluster',
  1287. kind='box',
  1288. height=5,
  1289. aspect=1.2,
  1290. palette='Set2'
  1291. )
  1292. g.fig.suptitle('P1 Latency by Condition, Cluster, and Group', y=1.05)
  1293. g.set_xticklabels(rotation=45)
  1294. plt.tight_layout()
  1295. #%%
  1296. Stats = np.concatenate([amp_face_TD_L , amp_face_ASD_L, amp_face_SIB_L,
  1297. amp_face_U_TD_L , amp_face_U_ASD_L, amp_face_U_SIB_L,
  1298. amp_obj_TD_L , amp_obj_ASD_L, amp_obj_SIB_L,
  1299. amp_obj_U_TD_L , amp_obj_U_ASD_L, amp_obj_U_SIB_L,
  1300. amp_face_TD_R , amp_face_ASD_R, amp_face_SIB_R,
  1301. amp_face_U_TD_R , amp_face_U_ASD_R, amp_face_U_SIB_R,
  1302. amp_obj_TD_R , amp_obj_ASD_R, amp_obj_SIB_R,
  1303. amp_obj_U_TD_R , amp_obj_U_ASD_R, amp_obj_U_SIB_R])
  1304. #%% Plot one boxplot per group
  1305. import matplotlib.pyplot as plt
  1306. # Data (example for TD group)
  1307. data = [lat_face_TD, lat_face_U_TD, lat_obj_TD, lat_obj_U_TD]
  1308. labels = ['Faces', 'Inverted Faces', 'Object', 'Inverted Object']
  1309. colors = ['#b2182b', '#ef8a62', '#2166ac', '#67a9cf']
  1310. # Create the figure and axes
  1311. fig, ax = plt.subplots(figsize=(8, 4))
  1312. # Plot each box manually to apply colors
  1313. for i, (y, label, color) in enumerate(zip(data, labels, colors), start=1):
  1314. bp = ax.boxplot(y, positions=[i], patch_artist=True, widths=0.6,
  1315. boxprops=dict(facecolor=color, color=color),
  1316. capprops=dict(color=color),
  1317. whiskerprops=dict(color=color),
  1318. flierprops=dict(markerfacecolor=color, marker='o', markersize=4, linestyle='none'),
  1319. medianprops=dict(color='black'))
  1320. # Set x-axis labels and other formatting
  1321. ax.set_xticks(range(1, 5))
  1322. ax.set_xticklabels(labels)
  1323. ax.set_ylabel('P1 Latency at P8 (ms)')
  1324. ax.set_title('P1 Latency in TD Group')
  1325. plt.tight_layout()
  1326. plt.show()
  1327. #%% Plot one boxplot for all groups
  1328. import matplotlib.pyplot as plt
  1329. # --- Organize data: 6 for faces, 6 for objects ---
  1330. data = [
  1331. lat_face_TD, lat_face_ASD, lat_face_SIB, # Faces
  1332. lat_face_U_TD, lat_face_U_ASD, lat_face_U_SIB, # Inverted Faces
  1333. lat_obj_TD, lat_obj_ASD, lat_obj_SIB, # Objects
  1334. lat_obj_U_TD, lat_obj_U_ASD, lat_obj_U_SIB # Inverted Objects
  1335. ]
  1336. # Corresponding group + condition labels
  1337. x_labels = [
  1338. 'Faces\nTD', 'Faces\nASD', 'Faces\nSIB',
  1339. 'Inv. Faces\nTD', 'Inv. Faces\nASD', 'Inv. Faces\nSIB',
  1340. 'Object\nTD', 'Object\nASD', 'Object\nSIB',
  1341. 'Inv. Object\nTD', 'Inv. Object\nASD', 'Inv. Object\nSIB'
  1342. ]
  1343. # Matching colors (same color per condition across groups)
  1344. colors = [
  1345. '#b2182b', '#b2182b', '#b2182b', # Faces
  1346. '#ef8a62', '#ef8a62', '#ef8a62', # Inverted Faces
  1347. '#2166ac', '#2166ac', '#2166ac', # Object
  1348. '#67a9cf', '#67a9cf', '#67a9cf' # Inverted Object
  1349. ]
  1350. # --- Plotting ---
  1351. fig, ax = plt.subplots(figsize=(12, 5))
  1352. for i, (y, label, color) in enumerate(zip(data, x_labels, colors), start=1):
  1353. ax.boxplot(y, positions=[i], patch_artist=True, widths=0.6,
  1354. boxprops=dict(facecolor=color, color=color),
  1355. capprops=dict(color=color),
  1356. whiskerprops=dict(color=color),
  1357. flierprops=dict(markerfacecolor=color, marker='o', markersize=4, linestyle='none'),
  1358. medianprops=dict(color='black'))
  1359. # Add group labels and vertical separators
  1360. ax.set_xticks(range(1, 13))
  1361. ax.set_xticklabels(x_labels, rotation=45, ha='right')
  1362. ax.set_ylabel('P1 Latency at P8 (ms)')
  1363. ax.set_title('P1 Latency Across Conditions and Groups')
  1364. ax.axvline(6.5, color='gray', linestyle='--', lw=1) # Separator between face and object
  1365. ax.text(3.5, ax.get_ylim()[1], 'Faces', ha='center', va='bottom', fontsize=10, weight='bold')
  1366. ax.text(9.5, ax.get_ylim()[1], 'Objects', ha='center', va='bottom', fontsize=10, weight='bold')
  1367. ax.grid(axis='y')
  1368. plt.tight_layout()
  1369. plt.show()
  1370. #%% Calculate the amplitude of the N170
  1371. # With Peak-to-peak amplitude
  1372. # def get_n170_latency_and_ptp(evoked_list, channel_names=['P7', 'PO7', 'O1'],
  1373. # p1_window=(0.05, 0.18), n170_window=(0.13, 0.26)):
  1374. # n170_latencies = []
  1375. # ptp_amplitudes = []
  1376. # for evoked in evoked_list:
  1377. # ch_indices = [evoked.ch_names.index(ch) for ch in channel_names]
  1378. # times = evoked.times
  1379. # # --- P1 ---
  1380. # p1_mask = (times >= p1_window[0]) & (times <= p1_window[1])
  1381. # p1_data = evoked.data[ch_indices][:, p1_mask].mean(axis=0)
  1382. # p1_times = times[p1_mask]
  1383. # # --- N170 ---
  1384. # n170_mask = (times >= n170_window[0]) & (times <= n170_window[1])
  1385. # n170_data = evoked.data[ch_indices][:, n170_mask].mean(axis=0)
  1386. # n170_times = times[n170_mask]
  1387. # if p1_data.size > 0 and n170_data.size > 0:
  1388. # p1_idx = p1_data.argmax()
  1389. # n170_idx = n170_data.argmin()
  1390. # p1_amp = p1_data[p1_idx] * 1e6 # µV
  1391. # n170_amp = n170_data[n170_idx] * 1e6 # µV
  1392. # n170_latency = n170_times[n170_idx] * 1000 # ms
  1393. # ptp_amp = p1_amp - n170_amp # µV
  1394. # n170_latencies.append(n170_latency)
  1395. # ptp_amplitudes.append(ptp_amp)
  1396. # else:
  1397. # n170_latencies.append(np.nan)
  1398. # ptp_amplitudes.append(np.nan)
  1399. # return n170_latencies, ptp_amplitudes
  1400. # Direct calcul of N170 amplitude
  1401. def get_n170_latency_and_ptp(evoked_list, channel_names=['P7', 'PO7', 'O1'], n170_window=(0.17, 0.23)):
  1402. n170_latencies = []
  1403. n170_amplitudes = []
  1404. for evoked in evoked_list:
  1405. ch_indices = [evoked.ch_names.index(ch) for ch in channel_names]
  1406. times = evoked.times
  1407. # --- N170 ---
  1408. n170_mask = (times >= n170_window[0]) & (times <= n170_window[1])
  1409. n170_data = evoked.data[ch_indices][:, n170_mask].mean(axis=0)
  1410. n170_times = times[n170_mask]
  1411. if n170_data.size > 0:
  1412. n170_idx = n170_data.argmin()
  1413. n170_amp = n170_data[n170_idx] * 1e6 # µV
  1414. n170_latency = n170_times[n170_idx] * 1000 # ms
  1415. n170_amplitudes.append(n170_amp)
  1416. n170_latencies.append(n170_latency)
  1417. else:
  1418. n170_amplitudes.append(np.nan)
  1419. n170_latencies.append(np.nan)
  1420. return n170_latencies, n170_amplitudes
  1421. # Define clusters
  1422. left_cluster = ['P7']
  1423. right_cluster = ['P8']
  1424. # TD Group
  1425. n170_face_TD_L, ptp_face_TD_L = get_n170_latency_and_ptp(erp_face_TD, left_cluster)
  1426. n170_face_TD_R, ptp_face_TD_R = get_n170_latency_and_ptp(erp_face_TD, right_cluster)
  1427. n170_face_U_TD_L, ptp_face_U_TD_L = get_n170_latency_and_ptp(erp_face_U_TD, left_cluster)
  1428. n170_face_U_TD_R, ptp_face_U_TD_R = get_n170_latency_and_ptp(erp_face_U_TD, right_cluster)
  1429. n170_obj_TD_L, ptp_obj_TD_L = get_n170_latency_and_ptp(erp_obj_TD, left_cluster)
  1430. n170_obj_TD_R, ptp_obj_TD_R = get_n170_latency_and_ptp(erp_obj_TD, right_cluster)
  1431. n170_obj_U_TD_L, ptp_obj_U_TD_L = get_n170_latency_and_ptp(erp_obj_U_TD, left_cluster)
  1432. n170_obj_U_TD_R, ptp_obj_U_TD_R = get_n170_latency_and_ptp(erp_obj_U_TD, right_cluster)
  1433. # ASD Group
  1434. n170_face_ASD_L, ptp_face_ASD_L = get_n170_latency_and_ptp(erp_face_ASD, left_cluster)
  1435. n170_face_ASD_R, ptp_face_ASD_R = get_n170_latency_and_ptp(erp_face_ASD, right_cluster)
  1436. n170_face_U_ASD_L, ptp_face_U_ASD_L = get_n170_latency_and_ptp(erp_face_U_ASD, left_cluster)
  1437. n170_face_U_ASD_R, ptp_face_U_ASD_R = get_n170_latency_and_ptp(erp_face_U_ASD, right_cluster)
  1438. n170_obj_ASD_L, ptp_obj_ASD_L = get_n170_latency_and_ptp(erp_obj_ASD, left_cluster)
  1439. n170_obj_ASD_R, ptp_obj_ASD_R = get_n170_latency_and_ptp(erp_obj_ASD, right_cluster)
  1440. n170_obj_U_ASD_L, ptp_obj_U_ASD_L = get_n170_latency_and_ptp(erp_obj_U_ASD, left_cluster)
  1441. n170_obj_U_ASD_R, ptp_obj_U_ASD_R = get_n170_latency_and_ptp(erp_obj_U_ASD, right_cluster)
  1442. # SIB Group
  1443. n170_face_SIB_L, ptp_face_SIB_L = get_n170_latency_and_ptp(erp_face_SIB, left_cluster)
  1444. n170_face_SIB_R, ptp_face_SIB_R = get_n170_latency_and_ptp(erp_face_SIB, right_cluster)
  1445. n170_face_U_SIB_L, ptp_face_U_SIB_L = get_n170_latency_and_ptp(erp_face_U_SIB, left_cluster)
  1446. n170_face_U_SIB_R, ptp_face_U_SIB_R = get_n170_latency_and_ptp(erp_face_U_SIB, right_cluster)
  1447. n170_obj_SIB_L, ptp_obj_SIB_L = get_n170_latency_and_ptp(erp_obj_SIB, left_cluster)
  1448. n170_obj_SIB_R, ptp_obj_SIB_R = get_n170_latency_and_ptp(erp_obj_SIB, right_cluster)
  1449. n170_obj_U_SIB_L, ptp_obj_U_SIB_L = get_n170_latency_and_ptp(erp_obj_U_SIB, left_cluster)
  1450. n170_obj_U_SIB_R, ptp_obj_U_SIB_R = get_n170_latency_and_ptp(erp_obj_U_SIB, right_cluster)
  1451. #%% N170 Amplitude with shadow stimulus for right and left cortex
  1452. import numpy as np
  1453. import pandas as pd
  1454. # -------------------------------
  1455. # Function to extract N170 latency & amplitude
  1456. # -------------------------------
  1457. def get_n170_latency_and_amp(evoked_list, channel, n170_window=(0.170, 0.230)):
  1458. latencies = []
  1459. amplitudes = []
  1460. for evoked in evoked_list:
  1461. times = evoked.times
  1462. ch_idx = evoked.ch_names.index(channel)
  1463. # Time mask for N170 window
  1464. mask = (times >= n170_window[0]) & (times <= n170_window[1])
  1465. if not np.any(mask):
  1466. latencies.append(np.nan)
  1467. amplitudes.append(np.nan)
  1468. continue
  1469. data = evoked.data[ch_idx, mask]
  1470. t_window = times[mask]
  1471. # Minimum (most negative) value in the window
  1472. min_idx = np.argmin(data)
  1473. min_val = data[min_idx] * 1e6 # Convert to µV
  1474. min_time = t_window[min_idx] * 1000 # Convert to ms
  1475. latencies.append(min_time)
  1476. amplitudes.append(min_val)
  1477. return latencies, amplitudes
  1478. # -------------------------------
  1479. # N170 settings
  1480. # -------------------------------
  1481. left_chan = 'P7'
  1482. right_chan = 'P8'
  1483. n170_window = (0.170, 0.230)
  1484. # -------------------------------
  1485. # Results dictionary
  1486. # -------------------------------
  1487. results = {}
  1488. def process_condition(group, stim_type, erp_list):
  1489. condition_name = f"{stim_type}"
  1490. results[(group, condition_name)] = {
  1491. 'Left_Latency': None,
  1492. 'Left_Amplitude': None,
  1493. 'Right_Latency': None,
  1494. 'Right_Amplitude': None
  1495. }
  1496. lat_L, amp_L = get_n170_latency_and_amp(erp_list, left_chan, n170_window)
  1497. lat_R, amp_R = get_n170_latency_and_amp(erp_list, right_chan, n170_window)
  1498. results[(group, condition_name)]['Left_Latency'] = lat_L
  1499. results[(group, condition_name)]['Left_Amplitude'] = amp_L
  1500. results[(group, condition_name)]['Right_Latency'] = lat_R
  1501. results[(group, condition_name)]['Right_Amplitude'] = amp_R
  1502. # -------------------------------
  1503. # Process all groups & conditions
  1504. # -------------------------------
  1505. stim_conditions = [
  1506. ('face', erp_face_TD, erp_face_ASD, erp_face_SIB),
  1507. ('face_shadow', erp_face_shadow_TD, erp_face_shadow_ASD, erp_face_shadow_SIB),
  1508. ('face_U', erp_face_U_TD, erp_face_U_ASD, erp_face_U_SIB),
  1509. ('face_U_shadow', erp_face_U_shadow_TD, erp_face_U_shadow_ASD, erp_face_U_shadow_SIB),
  1510. ('obj', erp_obj_TD, erp_obj_ASD, erp_obj_SIB),
  1511. ('obj_shadow', erp_obj_shadow_TD, erp_obj_shadow_ASD, erp_obj_shadow_SIB),
  1512. ('obj_U', erp_obj_U_TD, erp_obj_U_ASD, erp_obj_U_SIB),
  1513. ('obj_U_shadow', erp_obj_U_shadow_TD, erp_obj_U_shadow_ASD, erp_obj_U_shadow_SIB)
  1514. ]
  1515. groups = ['TD', 'ASD', 'SIB']
  1516. for stim_type, td_list, asd_list, sib_list in stim_conditions:
  1517. process_condition('TD', stim_type, td_list)
  1518. process_condition('ASD', stim_type, asd_list)
  1519. process_condition('SIB', stim_type, sib_list)
  1520. # -------------------------------
  1521. # Convert results to DataFrame
  1522. # -------------------------------
  1523. df_rows = []
  1524. for (group, condition), vals in results.items():
  1525. n = len(vals['Left_Latency'])
  1526. for i in range(n):
  1527. df_rows.append({
  1528. 'Group': group,
  1529. 'Condition': condition,
  1530. 'Subject': i + 1,
  1531. 'Left_Latency_ms': vals['Left_Latency'][i],
  1532. 'Left_Amplitude_uV': vals['Left_Amplitude'][i],
  1533. 'Right_Latency_ms': vals['Right_Latency'][i],
  1534. 'Right_Amplitude_uV': vals['Right_Amplitude'][i]
  1535. })
  1536. df_n170 = pd.DataFrame(df_rows)
  1537. # ✅ Save to Excel
  1538. df_n170.to_excel('N170_amplitude_latency_all_conditions.xlsx', index=False)
  1539. print(df_n170.head())
  1540. #%% N170 amplitude for a cluster of channel
  1541. import numpy as np
  1542. import pandas as pd
  1543. # -------------------------------
  1544. # Function to extract N170 latency & amplitude for a cluster
  1545. # -------------------------------
  1546. def get_n170_latency_and_amp(evoked_list, channels, n170_window=(0.170, 0.230)):
  1547. latencies = []
  1548. amplitudes = []
  1549. for evoked in evoked_list:
  1550. times = evoked.times
  1551. # Get indices for the cluster channels
  1552. ch_indices = [evoked.ch_names.index(ch) for ch in channels]
  1553. # Time mask for N170 window
  1554. mask = (times >= n170_window[0]) & (times <= n170_window[1])
  1555. if not np.any(mask):
  1556. latencies.append(np.nan)
  1557. amplitudes.append(np.nan)
  1558. continue
  1559. # Average signal across cluster channels
  1560. data = evoked.data[ch_indices, :][:, mask].mean(axis=0)
  1561. t_window = times[mask]
  1562. # Minimum (most negative) value in the window
  1563. min_idx = np.argmin(data)
  1564. min_val = data[min_idx] * 1e6 # Convert to µV
  1565. min_time = t_window[min_idx] * 1000 # Convert to ms
  1566. latencies.append(min_time)
  1567. amplitudes.append(min_val)
  1568. return latencies, amplitudes
  1569. # -------------------------------
  1570. # N170 settings
  1571. # -------------------------------
  1572. cluster_channels = ['P7', 'P8'] # Cluster of interest
  1573. n170_window = (0.170, 0.230)
  1574. # -------------------------------
  1575. # Results dictionary
  1576. # -------------------------------
  1577. results = {}
  1578. def process_condition(group, stim_type, erp_list):
  1579. condition_name = f"{stim_type}"
  1580. results[(group, condition_name)] = {
  1581. 'Latency': None,
  1582. 'Amplitude': None
  1583. }
  1584. lat, amp = get_n170_latency_and_amp(erp_list, cluster_channels, n170_window)
  1585. results[(group, condition_name)]['Latency'] = lat
  1586. results[(group, condition_name)]['Amplitude'] = amp
  1587. # -------------------------------
  1588. # Process all groups & conditions
  1589. # -------------------------------
  1590. stim_conditions = [
  1591. ('face', erp_face_TD, erp_face_ASD, erp_face_SIB),
  1592. ('face_shadow', erp_face_shadow_TD, erp_face_shadow_ASD, erp_face_shadow_SIB),
  1593. ('face_U', erp_face_U_TD, erp_face_U_ASD, erp_face_U_SIB),
  1594. ('face_U_shadow', erp_face_U_shadow_TD, erp_face_U_shadow_ASD, erp_face_U_shadow_SIB),
  1595. ('obj', erp_obj_TD, erp_obj_ASD, erp_obj_SIB),
  1596. ('obj_shadow', erp_obj_shadow_TD, erp_obj_shadow_ASD, erp_obj_shadow_SIB),
  1597. ('obj_U', erp_obj_U_TD, erp_obj_U_ASD, erp_obj_U_SIB),
  1598. ('obj_U_shadow', erp_obj_U_shadow_TD, erp_obj_U_shadow_ASD, erp_obj_U_shadow_SIB)
  1599. ]
  1600. groups = ['TD', 'ASD', 'SIB']
  1601. for stim_type, td_list, asd_list, sib_list in stim_conditions:
  1602. process_condition('TD', stim_type, td_list)
  1603. process_condition('ASD', stim_type, asd_list)
  1604. process_condition('SIB', stim_type, sib_list)
  1605. # -------------------------------
  1606. # Convert results to DataFrame
  1607. # -------------------------------
  1608. df_rows = []
  1609. for (group, condition), vals in results.items():
  1610. n = len(vals['Latency'])
  1611. for i in range(n):
  1612. df_rows.append({
  1613. 'Group': group,
  1614. 'Condition': condition,
  1615. 'Subject': i + 1,
  1616. 'Cluster': 'P7-P8',
  1617. 'Latency_ms': vals['Latency'][i],
  1618. 'Amplitude_uV': vals['Amplitude'][i]
  1619. })
  1620. df_n170 = pd.DataFrame(df_rows)
  1621. # ✅ Save to Excel
  1622. df_n170.to_excel('N170_amplitude_latency_cluster_P7P8.xlsx', index=False)
  1623. print(df_n170.head())
  1624. #%% Boxplot of the result for all the conditions
  1625. import pandas as pd
  1626. import matplotlib.pyplot as plt
  1627. import seaborn as sns
  1628. # Load the dataframe (or use df_n170 from previous step)
  1629. df = df_n170
  1630. # Set up the figure
  1631. plt.figure(figsize=(12, 6))
  1632. sns.set(style="whitegrid")
  1633. # Create a boxplot
  1634. sns.boxplot(x='Condition', y='Amplitude_uV', hue='Group', data=df, palette='Set2')
  1635. # Add swarmplot for individual points (optional)
  1636. sns.stripplot(x='Condition', y='Amplitude_uV', hue='Group', data=df,
  1637. dodge=True, color='k', alpha=0.4)
  1638. # Titles and labels
  1639. plt.title('N170 Amplitude (µV) across Conditions and Groups', fontsize=14)
  1640. plt.ylabel('Amplitude (µV)', fontsize=12)
  1641. plt.xlabel('Condition', fontsize=12)
  1642. # Adjust legend
  1643. handles, labels = plt.gca().get_legend_handles_labels()
  1644. plt.legend(handles[:3], labels[:3], title='Group', loc='upper right')
  1645. plt.xticks(rotation=30)
  1646. plt.tight_layout()
  1647. plt.show()
  1648. #%% Boxplot to highlight the face effect for both clear and shadow stimuli
  1649. import pandas as pd
  1650. import matplotlib.pyplot as plt
  1651. import seaborn as sns
  1652. # Load the dataframe
  1653. df = df_n170
  1654. # ✅ Filter the conditions of interest
  1655. selected_conditions = ['obj', 'face', 'obj_shadow', 'face_shadow']
  1656. df_filtered = df[df['Condition'].isin(selected_conditions)].copy()
  1657. # ✅ Reorder conditions for plotting
  1658. condition_order = ['obj', 'face', 'obj_shadow', 'face_shadow']
  1659. df_filtered['Condition'] = pd.Categorical(df_filtered['Condition'], categories=condition_order, ordered=True)
  1660. # ✅ Create a boxplot
  1661. plt.figure(figsize=(10, 6))
  1662. sns.set(style="whitegrid")
  1663. sns.boxplot(x='Condition', y='Amplitude_uV', hue='Group', data=df_filtered, palette='Set2')
  1664. # Add individual data points for visibility
  1665. sns.stripplot(x='Condition', y='Amplitude_uV', hue='Group', data=df_filtered,
  1666. dodge=True, color='k', alpha=0.4)
  1667. # ✅ Adjust legend (remove duplicates)
  1668. handles, labels = plt.gca().get_legend_handles_labels()
  1669. plt.legend(handles[:3], labels[:3], title='Group', loc='upper right')
  1670. # ✅ Titles and labels
  1671. plt.title('N170 Amplitude (µV): Faces vs Objects (+ Shadows)', fontsize=14)
  1672. plt.ylabel('Amplitude (µV)', fontsize=12)
  1673. plt.xlabel('Condition', fontsize=12)
  1674. plt.xticks(rotation=0)
  1675. # ✅ Add space between object/face and shadow conditions
  1676. # Insert a vertical line between face and obj_shadow for visual grouping
  1677. plt.axvline(1.5, color='gray', linestyle='--', alpha=0.6)
  1678. plt.tight_layout()
  1679. plt.show()
  1680. #%% Difference wave between clear and shadow stimuli - SOCIAL
  1681. import numpy as np
  1682. import matplotlib.pyplot as plt
  1683. import mne
  1684. # --- Helper functions ---
  1685. def combine_evokeds(list1, list2):
  1686. """Average two lists of Evoked objects element-wise."""
  1687. return [mne.combine_evoked([ev1, ev2], weights='equal') for ev1, ev2 in zip(list1, list2)]
  1688. def compute_diff(clear_list, shadow_list):
  1689. """Compute difference wave (clear - shadow) for a list of subjects."""
  1690. return [mne.combine_evoked([clear, shadow], weights=[1, -1]) for clear, shadow in zip(clear_list, shadow_list)]
  1691. def extract_data(evokeds, channels):
  1692. """Extract channel data from list of Evokeds into array (n_subjects, n_channels, n_times)."""
  1693. n_subj = len(evokeds)
  1694. n_ch = len(channels)
  1695. n_times = len(evokeds[0].times)
  1696. data = np.zeros((n_subj, n_ch, n_times))
  1697. for i, ev in enumerate(evokeds):
  1698. data[i] = ev.copy().pick(channels).data
  1699. return data
  1700. # --- Combine conditions for each group ---
  1701. erp_clear_TD = combine_evokeds(erp_face_TD, erp_face_U_TD)
  1702. erp_shadow_TD = combine_evokeds(erp_face_shadow_TD, erp_face_U_shadow_TD)
  1703. erp_clear_ASD = combine_evokeds(erp_face_ASD, erp_face_U_ASD)
  1704. erp_shadow_ASD = combine_evokeds(erp_face_shadow_ASD, erp_face_U_shadow_ASD)
  1705. erp_clear_SIB = combine_evokeds(erp_face_SIB, erp_face_U_SIB)
  1706. erp_shadow_SIB = combine_evokeds(erp_face_shadow_SIB, erp_face_U_shadow_SIB)
  1707. # Compute difference waves per subject
  1708. erp_diff_TD = compute_diff(erp_clear_TD, erp_shadow_TD)
  1709. erp_diff_ASD = compute_diff(erp_clear_ASD, erp_shadow_ASD)
  1710. erp_diff_SIB = compute_diff(erp_clear_SIB, erp_shadow_SIB)
  1711. # Channels of interest
  1712. channels = ['O1','PO7', 'O2','PO8']
  1713. times = erp_diff_TD[0].times * 1000 # ms
  1714. # Extract data
  1715. data_TD = extract_data(erp_diff_TD, channels) * 1e6 # Convert to µV
  1716. data_ASD = extract_data(erp_diff_ASD, channels) * 1e6
  1717. data_SIB = extract_data(erp_diff_SIB, channels) * 1e6
  1718. # Compute mean and SEM
  1719. mean_TD = data_TD.mean(axis=0)
  1720. sem_TD = data_TD.std(axis=0) / np.sqrt(data_TD.shape[0])
  1721. mean_ASD = data_ASD.mean(axis=0)
  1722. sem_ASD = data_ASD.std(axis=0) / np.sqrt(data_ASD.shape[0])
  1723. mean_SIB = data_SIB.mean(axis=0)
  1724. sem_SIB = data_SIB.std(axis=0) / np.sqrt(data_SIB.shape[0])
  1725. # --- Plot ---
  1726. plt.figure(figsize=(10, 6))
  1727. colors = {'TD': 'blue', 'ASD': 'red', 'SIB': 'green'}
  1728. for idx, ch in enumerate(channels):
  1729. plt.subplot(2, 1, idx + 1)
  1730. # TD
  1731. plt.plot(times, mean_TD[idx], color=colors['TD'], label='TD')
  1732. plt.fill_between(times, mean_TD[idx]-sem_TD[idx], mean_TD[idx]+sem_TD[idx], color=colors['TD'], alpha=0.3)
  1733. # ASD
  1734. plt.plot(times, mean_ASD[idx], color=colors['ASD'], label='ASD')
  1735. plt.fill_between(times, mean_ASD[idx]-sem_ASD[idx], mean_ASD[idx]+sem_ASD[idx], color=colors['ASD'], alpha=0.3)
  1736. # SIB
  1737. plt.plot(times, mean_SIB[idx], color=colors['SIB'], label='SIB')
  1738. plt.fill_between(times, mean_SIB[idx]-sem_SIB[idx], mean_SIB[idx]+sem_SIB[idx], color=colors['SIB'], alpha=0.3)
  1739. plt.axvline(0, color='k', linestyle='--')
  1740. plt.axhline(0, color='k', linewidth=0.5)
  1741. plt.title(f'Difference Wave (Clear - Shadow) at {ch}')
  1742. plt.xlabel('Time (ms)')
  1743. plt.ylabel('Amplitude (µV)')
  1744. plt.xlim([-100,600])
  1745. if idx == 0:
  1746. plt.legend()
  1747. plt.grid(True)
  1748. plt.tight_layout()
  1749. plt.show()
  1750. #%% difference wave for a cluster of channel
  1751. import numpy as np
  1752. import matplotlib.pyplot as plt
  1753. import mne
  1754. # --- Helper functions ---
  1755. def combine_evokeds(list1, list2):
  1756. """Average two lists of Evoked objects element-wise."""
  1757. return [mne.combine_evoked([ev1, ev2], weights='equal') for ev1, ev2 in zip(list1, list2)]
  1758. def compute_diff(clear_list, shadow_list):
  1759. """Compute difference wave (clear - shadow) for a list of subjects."""
  1760. return [mne.combine_evoked([clear, shadow], weights=[1, -1]) for clear, shadow in zip(clear_list, shadow_list)]
  1761. def extract_data(evokeds, channels):
  1762. """Extract channel data from list of Evokeds into array (n_subjects, n_times), averaged across channels."""
  1763. n_subj = len(evokeds)
  1764. n_times = len(evokeds[0].times)
  1765. data = np.zeros((n_subj, n_times))
  1766. for i, ev in enumerate(evokeds):
  1767. data[i] = ev.copy().pick(channels).data.mean(axis=0)
  1768. return data
  1769. # --- Combine conditions for each group ---
  1770. erp_clear_TD = combine_evokeds(erp_face_TD, erp_face_U_TD)
  1771. erp_shadow_TD = combine_evokeds(erp_face_shadow_TD, erp_face_U_shadow_TD)
  1772. erp_clear_ASD = combine_evokeds(erp_face_ASD, erp_face_U_ASD)
  1773. erp_shadow_ASD = combine_evokeds(erp_face_shadow_ASD, erp_face_U_shadow_ASD)
  1774. erp_clear_SIB = combine_evokeds(erp_face_SIB, erp_face_U_SIB)
  1775. erp_shadow_SIB = combine_evokeds(erp_face_shadow_SIB, erp_face_U_shadow_SIB)
  1776. # Compute difference waves per subject
  1777. erp_diff_TD = compute_diff(erp_clear_TD, erp_shadow_TD)
  1778. erp_diff_ASD = compute_diff(erp_clear_ASD, erp_shadow_ASD)
  1779. erp_diff_SIB = compute_diff(erp_clear_SIB, erp_shadow_SIB)
  1780. # Channels of interest
  1781. channels = ['O1', 'PO7', 'O2', 'PO8']
  1782. times = erp_diff_TD[0].times * 1000 # ms
  1783. # Extract data averaged across channels
  1784. data_TD = extract_data(erp_diff_TD, channels) * 1e6 # µV
  1785. data_ASD = extract_data(erp_diff_ASD, channels) * 1e6
  1786. data_SIB = extract_data(erp_diff_SIB, channels) * 1e6
  1787. # Compute mean and SEM across subjects
  1788. mean_TD = data_TD.mean(axis=0)
  1789. sem_TD = data_TD.std(axis=0) / np.sqrt(data_TD.shape[0])
  1790. mean_ASD = data_ASD.mean(axis=0)
  1791. sem_ASD = data_ASD.std(axis=0) / np.sqrt(data_ASD.shape[0])
  1792. mean_SIB = data_SIB.mean(axis=0)
  1793. sem_SIB = data_SIB.std(axis=0) / np.sqrt(data_SIB.shape[0])
  1794. # --- Plot grand average difference wave with SEM ---
  1795. plt.figure(figsize=(10, 6))
  1796. colors = {'TD': 'blue', 'ASD': 'red', 'SIB': 'green'}
  1797. # TD
  1798. plt.plot(times, mean_TD, color=colors['TD'], label='TD')
  1799. plt.fill_between(times, mean_TD - sem_TD, mean_TD + sem_TD, color=colors['TD'], alpha=0.3)
  1800. # ASD
  1801. plt.plot(times, mean_ASD, color=colors['ASD'], label='ASD')
  1802. plt.fill_between(times, mean_ASD - sem_ASD, mean_ASD + sem_ASD, color=colors['ASD'], alpha=0.3)
  1803. # SIB
  1804. plt.plot(times, mean_SIB, color=colors['SIB'], label='SIB')
  1805. plt.fill_between(times, mean_SIB - sem_SIB, mean_SIB + sem_SIB, color=colors['SIB'], alpha=0.3)
  1806. # Reference lines
  1807. plt.axvline(0, color='k', linestyle='--')
  1808. plt.axhline(0, color='k', linewidth=0.5)
  1809. plt.xlabel('Time (ms)')
  1810. plt.ylabel('Amplitude (µV)')
  1811. plt.title('Difference Wave (Clear - Shadow), Averaged Across Channels')
  1812. plt.xlim([-100, 600])
  1813. plt.legend()
  1814. # plt.grid(True)
  1815. plt.tight_layout()
  1816. plt.show()
  1817. #%%
  1818. import numpy as np
  1819. import matplotlib.pyplot as plt
  1820. import mne
  1821. # Times of interest in ms
  1822. times_of_interest = [-50, 110, 150, 240, 350] # ms
  1823. # Compute grand-average difference wave for each group
  1824. grand_diff_TD = mne.grand_average(erp_diff_TD)
  1825. grand_diff_ASD = mne.grand_average(erp_diff_ASD)
  1826. grand_diff_SIB = mne.grand_average(erp_diff_SIB)
  1827. # Create a figure for each group
  1828. groups = {'TD': grand_diff_TD, 'ASD': grand_diff_ASD, 'SIB': grand_diff_SIB}
  1829. for group_name, evoked in groups.items():
  1830. fig = evoked.plot_topomap(
  1831. times=np.array(times_of_interest) / 1000.0, # Convert ms to s
  1832. scalings=1e6, # Convert to µV
  1833. units='µV',
  1834. time_unit='s',
  1835. cmap='RdBu_r', # Red/Blue colormap
  1836. outlines='head',
  1837. size=3,
  1838. vlim = (-6,6)
  1839. )
  1840. plt.show()
  1841. #%% save
  1842. plt.savefig('Topomap_Diff_wave_Clear_Shadow_SIB_nonsocial.svg')
  1843. #%% Difference wave between clear and shadow stimuli - NON-SOCIAL
  1844. import numpy as np
  1845. import matplotlib.pyplot as plt
  1846. import mne
  1847. # --- Combine conditions for each group ---
  1848. erp_clear_TD = combine_evokeds(erp_obj_TD, erp_obj_U_TD)
  1849. erp_shadow_TD = combine_evokeds(erp_obj_shadow_TD, erp_obj_U_shadow_TD)
  1850. erp_clear_ASD = combine_evokeds(erp_obj_ASD, erp_obj_U_ASD)
  1851. erp_shadow_ASD = combine_evokeds(erp_obj_shadow_ASD, erp_obj_U_shadow_ASD)
  1852. erp_clear_SIB = combine_evokeds(erp_obj_SIB, erp_obj_U_SIB)
  1853. erp_shadow_SIB = combine_evokeds(erp_obj_shadow_SIB, erp_obj_U_shadow_SIB)
  1854. # Compute difference waves per subject
  1855. erp_diff_TD = compute_diff(erp_clear_TD, erp_shadow_TD)
  1856. erp_diff_ASD = compute_diff(erp_clear_ASD, erp_shadow_ASD)
  1857. erp_diff_SIB = compute_diff(erp_clear_SIB, erp_shadow_SIB)
  1858. # Channels of interest
  1859. channels = ['P7', 'P8']
  1860. times = erp_diff_TD[0].times * 1000 # ms
  1861. # Extract data
  1862. data_TD = extract_data(erp_diff_TD, channels) * 1e6 # Convert to µV
  1863. data_ASD = extract_data(erp_diff_ASD, channels) * 1e6
  1864. data_SIB = extract_data(erp_diff_SIB, channels) * 1e6
  1865. # Compute mean and SEM
  1866. mean_TD = data_TD.mean(axis=0)
  1867. sem_TD = data_TD.std(axis=0) / np.sqrt(data_TD.shape[0])
  1868. mean_ASD = data_ASD.mean(axis=0)
  1869. sem_ASD = data_ASD.std(axis=0) / np.sqrt(data_ASD.shape[0])
  1870. mean_SIB = data_SIB.mean(axis=0)
  1871. sem_SIB = data_SIB.std(axis=0) / np.sqrt(data_SIB.shape[0])
  1872. # --- Plot ---
  1873. plt.figure(figsize=(10, 6))
  1874. colors = {'TD': 'blue', 'ASD': 'red', 'SIB': 'green'}
  1875. for idx, ch in enumerate(channels):
  1876. plt.subplot(2, 1, idx + 1)
  1877. # TD
  1878. plt.plot(times, mean_TD[idx], color=colors['TD'], label='TD')
  1879. plt.fill_between(times, mean_TD[idx]-sem_TD[idx], mean_TD[idx]+sem_TD[idx], color=colors['TD'], alpha=0.3)
  1880. # ASD
  1881. plt.plot(times, mean_ASD[idx], color=colors['ASD'], label='ASD')
  1882. plt.fill_between(times, mean_ASD[idx]-sem_ASD[idx], mean_ASD[idx]+sem_ASD[idx], color=colors['ASD'], alpha=0.3)
  1883. # SIB
  1884. plt.plot(times, mean_SIB[idx], color=colors['SIB'], label='SIB')
  1885. plt.fill_between(times, mean_SIB[idx]-sem_SIB[idx], mean_SIB[idx]+sem_SIB[idx], color=colors['SIB'], alpha=0.3)
  1886. plt.axvline(0, color='k', linestyle='--')
  1887. plt.axhline(0, color='k', linewidth=0.5)
  1888. plt.title(f'Difference Wave (Clear - Shadow) at {ch}')
  1889. plt.xlabel('Time (ms)')
  1890. plt.ylabel('Amplitude (µV)')
  1891. if idx == 0:
  1892. plt.legend()
  1893. plt.grid(True)
  1894. plt.tight_layout()
  1895. plt.show()
  1896. #%% difference wave for a cluster of channel
  1897. import numpy as np
  1898. import matplotlib.pyplot as plt
  1899. import mne
  1900. # --- Helper functions ---
  1901. def combine_evokeds(list1, list2):
  1902. """Average two lists of Evoked objects element-wise."""
  1903. return [mne.combine_evoked([ev1, ev2], weights='equal') for ev1, ev2 in zip(list1, list2)]
  1904. def compute_diff(clear_list, shadow_list):
  1905. """Compute difference wave (clear - shadow) for a list of subjects."""
  1906. return [mne.combine_evoked([clear, shadow], weights=[1, -1]) for clear, shadow in zip(clear_list, shadow_list)]
  1907. def extract_data(evokeds, channels):
  1908. """Extract channel data from list of Evokeds into array (n_subjects, n_times), averaged across channels."""
  1909. n_subj = len(evokeds)
  1910. n_times = len(evokeds[0].times)
  1911. data = np.zeros((n_subj, n_times))
  1912. for i, ev in enumerate(evokeds):
  1913. data[i] = ev.copy().pick(channels).data.mean(axis=0)
  1914. return data
  1915. # --- Combine conditions for each group ---
  1916. erp_clear_TD = combine_evokeds(erp_obj_TD, erp_obj_U_TD)
  1917. erp_shadow_TD = combine_evokeds(erp_obj_shadow_TD, erp_obj_U_shadow_TD)
  1918. erp_clear_ASD = combine_evokeds(erp_obj_ASD, erp_obj_U_ASD)
  1919. erp_shadow_ASD = combine_evokeds(erp_obj_shadow_ASD, erp_obj_U_shadow_ASD)
  1920. erp_clear_SIB = combine_evokeds(erp_obj_SIB, erp_obj_U_SIB)
  1921. erp_shadow_SIB = combine_evokeds(erp_obj_shadow_SIB, erp_obj_U_shadow_SIB)
  1922. # Compute difference waves per subject
  1923. erp_diff_TD = compute_diff(erp_clear_TD, erp_shadow_TD)
  1924. erp_diff_ASD = compute_diff(erp_clear_ASD, erp_shadow_ASD)
  1925. erp_diff_SIB = compute_diff(erp_clear_SIB, erp_shadow_SIB)
  1926. # Channels of interest
  1927. channels = ['O1', 'PO7', 'O2', 'PO8']
  1928. times = erp_diff_TD[0].times * 1000 # ms
  1929. # Extract data averaged across channels
  1930. data_TD = extract_data(erp_diff_TD, channels) * 1e6 # µV
  1931. data_ASD = extract_data(erp_diff_ASD, channels) * 1e6
  1932. data_SIB = extract_data(erp_diff_SIB, channels) * 1e6
  1933. # Compute mean and SEM across subjects
  1934. mean_TD = data_TD.mean(axis=0)
  1935. sem_TD = data_TD.std(axis=0) / np.sqrt(data_TD.shape[0])
  1936. mean_ASD = data_ASD.mean(axis=0)
  1937. sem_ASD = data_ASD.std(axis=0) / np.sqrt(data_ASD.shape[0])
  1938. mean_SIB = data_SIB.mean(axis=0)
  1939. sem_SIB = data_SIB.std(axis=0) / np.sqrt(data_SIB.shape[0])
  1940. # --- Plot grand average difference wave with SEM ---
  1941. plt.figure(figsize=(10, 6))
  1942. colors = {'TD': 'blue', 'ASD': 'red', 'SIB': 'green'}
  1943. # TD
  1944. plt.plot(times, mean_TD, color=colors['TD'], label='TD')
  1945. plt.fill_between(times, mean_TD - sem_TD, mean_TD + sem_TD, color=colors['TD'], alpha=0.3)
  1946. # ASD
  1947. plt.plot(times, mean_ASD, color=colors['ASD'], label='ASD')
  1948. plt.fill_between(times, mean_ASD - sem_ASD, mean_ASD + sem_ASD, color=colors['ASD'], alpha=0.3)
  1949. # SIB
  1950. plt.plot(times, mean_SIB, color=colors['SIB'], label='SIB')
  1951. plt.fill_between(times, mean_SIB - sem_SIB, mean_SIB + sem_SIB, color=colors['SIB'], alpha=0.3)
  1952. # Reference lines
  1953. plt.axvline(0, color='k', linestyle='--')
  1954. plt.axhline(0, color='k', linewidth=0.5)
  1955. plt.xlabel('Time (ms)')
  1956. plt.ylabel('Amplitude (µV)')
  1957. plt.title('Difference Wave (Clear - Shadow), Averaged Across Channels')
  1958. plt.xlim([-100, 600])
  1959. plt.legend()
  1960. # plt.grid(True)
  1961. plt.tight_layout()
  1962. plt.show()
  1963. #%%
  1964. def make_df(values, measure, condition, cluster, group):
  1965. return pd.DataFrame({
  1966. 'Value': values,
  1967. 'Measure': measure,
  1968. 'Condition': condition,
  1969. 'Cluster': cluster,
  1970. 'Group': group
  1971. })
  1972. # Combine both latency and peak-to-peak amplitude
  1973. df_n170 = pd.concat([
  1974. # TD
  1975. make_df(n170_face_TD_L, 'Latency (ms)', 'Face', 'Left', 'TD'),
  1976. make_df(n170_face_TD_R, 'Latency (ms)', 'Face', 'Right', 'TD'),
  1977. make_df(n170_face_U_TD_L, 'Latency (ms)', 'Inverted Face', 'Left', 'TD'),
  1978. make_df(n170_face_U_TD_R, 'Latency (ms)', 'Inverted Face', 'Right', 'TD'),
  1979. make_df(n170_obj_TD_L, 'Latency (ms)', 'Object', 'Left', 'TD'),
  1980. make_df(n170_obj_TD_R, 'Latency (ms)', 'Object', 'Right', 'TD'),
  1981. make_df(n170_obj_U_TD_L, 'Latency (ms)', 'Inverted Object', 'Left', 'TD'),
  1982. make_df(n170_obj_U_TD_R, 'Latency (ms)', 'Inverted Object', 'Right', 'TD'),
  1983. # ASD
  1984. make_df(n170_face_ASD_L, 'Latency (ms)', 'Face', 'Left', 'ASD'),
  1985. make_df(n170_face_ASD_R, 'Latency (ms)', 'Face', 'Right', 'ASD'),
  1986. make_df(n170_face_U_ASD_L, 'Latency (ms)', 'Inverted Face', 'Left', 'ASD'),
  1987. make_df(n170_face_U_ASD_R, 'Latency (ms)', 'Inverted Face', 'Right', 'ASD'),
  1988. make_df(n170_obj_ASD_L, 'Latency (ms)', 'Object', 'Left', 'ASD'),
  1989. make_df(n170_obj_ASD_R, 'Latency (ms)', 'Object', 'Right', 'ASD'),
  1990. make_df(n170_obj_U_ASD_L, 'Latency (ms)', 'Inverted Object', 'Left', 'ASD'),
  1991. make_df(n170_obj_U_ASD_R, 'Latency (ms)', 'Inverted Object', 'Right', 'ASD'),
  1992. # SIB
  1993. make_df(n170_face_SIB_L, 'Latency (ms)', 'Face', 'Left', 'SIB'),
  1994. make_df(n170_face_SIB_R, 'Latency (ms)', 'Face', 'Right', 'SIB'),
  1995. make_df(n170_face_U_SIB_L, 'Latency (ms)', 'Inverted Face', 'Left', 'SIB'),
  1996. make_df(n170_face_U_SIB_R, 'Latency (ms)', 'Inverted Face', 'Right', 'SIB'),
  1997. make_df(n170_obj_SIB_L, 'Latency (ms)', 'Object', 'Left', 'SIB'),
  1998. make_df(n170_obj_SIB_R, 'Latency (ms)', 'Object', 'Right', 'SIB'),
  1999. make_df(n170_obj_U_SIB_L, 'Latency (ms)', 'Inverted Object', 'Left', 'SIB'),
  2000. make_df(n170_obj_U_SIB_R, 'Latency (ms)', 'Inverted Object', 'Right', 'SIB')])
  2001. #%%
  2002. g = sns.catplot(
  2003. data=df_n170,
  2004. x='Condition',
  2005. y='Value',
  2006. hue='Group',
  2007. col='Cluster',
  2008. kind='box',
  2009. height=5,
  2010. aspect=1.2,
  2011. palette='Set2'
  2012. )
  2013. g.fig.suptitle('P1 Amplitudes by Condition, Cluster, and Group', y=1.05)
  2014. g.set_xticklabels(rotation=45)
  2015. plt.tight_layout()
  2016. #%%
  2017. def make_df(values, measure, condition, cluster, group):
  2018. return pd.DataFrame({
  2019. 'Value': values,
  2020. 'Measure': measure,
  2021. 'Condition': condition,
  2022. 'Cluster': cluster,
  2023. 'Group': group
  2024. })
  2025. # Combine both latency and peak-to-peak amplitude
  2026. df_n170 = pd.concat([
  2027. # TD
  2028. make_df(ptp_face_TD_L, 'Latency (ms)', 'Face', 'Left', 'TD'),
  2029. make_df(ptp_face_TD_R, 'Latency (ms)', 'Face', 'Right', 'TD'),
  2030. make_df(ptp_face_U_TD_L, 'Latency (ms)', 'Inverted Face', 'Left', 'TD'),
  2031. make_df(ptp_face_U_TD_R, 'Latency (ms)', 'Inverted Face', 'Right', 'TD'),
  2032. make_df(ptp_obj_TD_L, 'Latency (ms)', 'Object', 'Left', 'TD'),
  2033. make_df(ptp_obj_TD_R, 'Latency (ms)', 'Object', 'Right', 'TD'),
  2034. make_df(ptp_obj_U_TD_L, 'Latency (ms)', 'Inverted Object', 'Left', 'TD'),
  2035. make_df(ptp_obj_U_TD_R, 'Latency (ms)', 'Inverted Object', 'Right', 'TD'),
  2036. # ASD
  2037. make_df(ptp_face_ASD_L, 'Latency (ms)', 'Face', 'Left', 'ASD'),
  2038. make_df(ptp_face_ASD_R, 'Latency (ms)', 'Face', 'Right', 'ASD'),
  2039. make_df(ptp_face_U_ASD_L, 'Latency (ms)', 'Inverted Face', 'Left', 'ASD'),
  2040. make_df(ptp_face_U_ASD_R, 'Latency (ms)', 'Inverted Face', 'Right', 'ASD'),
  2041. make_df(ptp_obj_ASD_L, 'Latency (ms)', 'Object', 'Left', 'ASD'),
  2042. make_df(ptp_obj_ASD_R, 'Latency (ms)', 'Object', 'Right', 'ASD'),
  2043. make_df(ptp_obj_U_ASD_L, 'Latency (ms)', 'Inverted Object', 'Left', 'ASD'),
  2044. make_df(ptp_obj_U_ASD_R, 'Latency (ms)', 'Inverted Object', 'Right', 'ASD'),
  2045. # SIB
  2046. make_df(ptp_face_SIB_L, 'Latency (ms)', 'Face', 'Left', 'SIB'),
  2047. make_df(ptp_face_SIB_R, 'Latency (ms)', 'Face', 'Right', 'SIB'),
  2048. make_df(ptp_face_U_SIB_L, 'Latency (ms)', 'Inverted Face', 'Left', 'SIB'),
  2049. make_df(ptp_face_U_SIB_R, 'Latency (ms)', 'Inverted Face', 'Right', 'SIB'),
  2050. make_df(ptp_obj_SIB_L, 'Latency (ms)', 'Object', 'Left', 'SIB'),
  2051. make_df(ptp_obj_SIB_R, 'Latency (ms)', 'Object', 'Right', 'SIB'),
  2052. make_df(ptp_obj_U_SIB_L, 'Latency (ms)', 'Inverted Object', 'Left', 'SIB'),
  2053. make_df(ptp_obj_U_SIB_R, 'Latency (ms)', 'Inverted Object', 'Right', 'SIB')])
  2054. #%%
  2055. g = sns.catplot(
  2056. data=df_n170,
  2057. x='Condition',
  2058. y='Value',
  2059. hue='Group',
  2060. col='Cluster',
  2061. kind='box',
  2062. height=5,
  2063. aspect=1.2,
  2064. palette='Set2'
  2065. )
  2066. g.fig.suptitle('P1 Amplitudes by Condition, Cluster, and Group', y=1.05)
  2067. g.set_xticklabels(rotation=45)
  2068. plt.tight_layout()
  2069. #%%
  2070. Stats = np.concatenate([n170_face_TD_L , n170_face_ASD_L, n170_face_SIB_L,
  2071. n170_face_U_TD_L , n170_face_U_ASD_L, n170_face_U_SIB_L,
  2072. n170_obj_TD_L , n170_obj_ASD_L, n170_obj_SIB_L,
  2073. n170_obj_U_TD_L , n170_obj_U_ASD_L, n170_obj_U_SIB_L,
  2074. n170_face_TD_R , n170_face_ASD_R, n170_face_SIB_R,
  2075. n170_face_U_TD_R , n170_face_U_ASD_R, n170_face_U_SIB_R,
  2076. n170_obj_TD_R , n170_obj_ASD_R, n170_obj_SIB_R,
  2077. n170_obj_U_TD_R , n170_obj_U_ASD_R, n170_obj_U_SIB_R])
  2078. #%%
  2079. Stats = np.concatenate([ptp_face_TD_L , ptp_face_ASD_L, ptp_face_SIB_L,
  2080. ptp_face_U_TD_L , ptp_face_U_ASD_L, ptp_face_U_SIB_L,
  2081. ptp_obj_TD_L , ptp_obj_ASD_L, ptp_obj_SIB_L,
  2082. ptp_obj_U_TD_L , ptp_obj_U_ASD_L, ptp_obj_U_SIB_L,
  2083. ptp_face_TD_R , ptp_face_ASD_R, ptp_face_SIB_R,
  2084. ptp_face_U_TD_R , ptp_face_U_ASD_R, ptp_face_U_SIB_R,
  2085. ptp_obj_TD_R , ptp_obj_ASD_R, ptp_obj_SIB_R,
  2086. ptp_obj_U_TD_R , ptp_obj_U_ASD_R, ptp_obj_U_SIB_R])
  2087. #%%
  2088. from mne import EvokedArray
  2089. import numpy as np
  2090. import matplotlib.pyplot as plt
  2091. def compute_gfp(evoked):
  2092. """Compute GFP: standard deviation across channels at each time point."""
  2093. return evoked.data.std(axis=0) * 1e6 # Convert to µV
  2094. gfp_face = compute_gfp(evoked_face_TD)
  2095. gfp_face_U = compute_gfp(evoked_face_U_TD)
  2096. gfp_obj = compute_gfp(evoked_obj_TD)
  2097. gfp_obj_U = compute_gfp(evoked_obj_U_TD)
  2098. times = evoked_face_TD.times * 1000 # convert to ms
  2099. plt.figure(figsize=(10, 5))
  2100. plt.plot(times, gfp_face, label='Face')
  2101. plt.plot(times, gfp_face_U, label='Inverted Face')
  2102. plt.plot(times, gfp_obj, label='Object')
  2103. plt.plot(times, gfp_obj_U, label='Inverted Object')
  2104. plt.axvline(0, color='k', linestyle='--')
  2105. plt.xlabel('Time (ms)')
  2106. plt.ylabel('GFP (µV)')
  2107. plt.title('Global Field Power (TD Group)')
  2108. plt.legend()
  2109. plt.tight_layout()
  2110. plt.show()
  2111. #%%
  2112. ]
  2113. x_labels = [
  2114. 'Faces\nTD', 'Faces\nASD', 'Faces\nSIB',
  2115. 'Inv. Faces\nTD', 'Inv. Faces\nASD', 'Inv. Faces\nSIB',
  2116. 'Object\nTD', 'Object\nASD', 'Object\nSIB',
  2117. 'Inv. Object\nTD', 'Inv. Object\nASD', 'Inv. Object\nSIB'
  2118. ]
  2119. colors = [
  2120. '#b2182b', '#b2182b', '#b2182b',
  2121. '#ef8a62', '#ef8a62', '#ef8a62',
  2122. '#2166ac', '#2166ac', '#2166ac',
  2123. '#67a9cf', '#67a9cf', '#67a9cf'
  2124. ]
  2125. fig, ax = plt.subplots(figsize=(12, 5))
  2126. for i, (y, label, color) in enumerate(zip(data_amplitudes, x_labels, colors), start=1):
  2127. ax.boxplot(y, positions=[i], patch_artist=True, widths=0.6,
  2128. boxprops=dict(facecolor=color, color=color),
  2129. capprops=dict(color=color),
  2130. whiskerprops=dict(color=color),
  2131. flierprops=dict(markerfacecolor=color, marker='o', markersize=4, linestyle='none'),
  2132. medianprops=dict(color='black'))
  2133. ax.set_xticks(range(1, 13))
  2134. ax.set_xticklabels(x_labels, rotation=45, ha='right')
  2135. ax.set_ylabel('N170 Peak-to-Peak Amplitude (µV)')
  2136. ax.set_title('P1-N170 Amplitude Across Conditions and Groups')
  2137. ax.axvline(6.5, color='gray', linestyle='--', lw=1)
  2138. ax.text(3.5, ax.get_ylim()[1], 'Faces', ha='center', va='bottom', fontsize=10, weight='bold')
  2139. ax.text(9.5, ax.get_ylim()[1], 'Objects', ha='center', va='bottom', fontsize=10, weight='bold')
  2140. ax.grid(axis='y')
  2141. plt.tight_layout()
  2142. plt.show()
  2143. #%% Plot ERP for face
  2144. visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8']
  2145. # visual_channels = ['C1', 'Cz', 'C2', 'CP1', 'CPz', 'CP2']
  2146. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8','P9','P7','P5','P3','P1','P2','P4','P6','P8','P10']
  2147. # visual_channels = ['O1', 'PO7', 'PO3']
  2148. # visual_channels = ['O2', 'PO8', 'PO4']
  2149. # visual_channels = ['F1', 'Fz', 'F2']
  2150. # Visual ERP: Calculate and plot the average ERP with SEM for visual stimulation ('V')
  2151. avg_erp_v_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_TD], axis=0)
  2152. sem_erp_v_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_TD])
  2153. avg_erp_v_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_ASD], axis=0)
  2154. sem_erp_v_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_ASD])
  2155. avg_erp_v_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_SIB], axis=0)
  2156. sem_erp_v_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_SIB])
  2157. # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  2158. plot_avg_erp_with_sem(avg_erp_v_TD, sem_erp_v_TD, avg_erp_v_ASD, sem_erp_v_ASD, avg_erp_v_SIB, sem_erp_v_SIB, 'Faces',
  2159. visual_channels, filtered_epochs_lst_TD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  2160. #%% Plot ERP for inverted faces
  2161. visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8']
  2162. # visual_channels = ['C1', 'Cz', 'C2', 'CP1', 'CPz', 'CP2']
  2163. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8','P9','P7','P5','P3','P1','P2','P4','P6','P8','P10']
  2164. # visual_channels = ['O1', 'PO7', 'PO3']
  2165. # visual_channels = ['O2', 'PO8', 'PO4']
  2166. # visual_channels = ['F1', 'Fz', 'F2']
  2167. # Visual ERP: Calculate and plot the average ERP with SEM for visual stimulation ('V')
  2168. avg_erp_v_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_U_TD], axis=0)
  2169. sem_erp_v_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_U_TD])
  2170. avg_erp_v_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_U_ASD], axis=0)
  2171. sem_erp_v_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_U_ASD])
  2172. avg_erp_v_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_face_U_SIB], axis=0)
  2173. sem_erp_v_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_face_U_SIB])
  2174. # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  2175. plot_avg_erp_with_sem(avg_erp_v_TD, sem_erp_v_TD, avg_erp_v_ASD, sem_erp_v_ASD, avg_erp_v_SIB, sem_erp_v_SIB, 'Inverted Faces',
  2176. visual_channels, filtered_epochs_lst_TD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  2177. #%% Plot topomap - face
  2178. Evoked_face_TD = mne.grand_average(erp_face_U_TD)
  2179. Evoked_face_ASD = mne.grand_average(erp_face_U_ASD)
  2180. Evoked_face_SIB = mne.grand_average(erp_face_U_SIB)
  2181. times = [-0.05,0.12,0.170,0.250,0.31] # Time in seconds
  2182. Evoked_face_TD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  2183. Evoked_face_ASD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  2184. Evoked_face_SIB.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  2185. #%% save
  2186. plt.savefig('ERP_Topomap_Inverted_Faces_SIB.svg')
  2187. #%% plot ERP for objects
  2188. visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8']
  2189. # visual_channels = ['C1', 'Cz', 'C2', 'CP1', 'CPz', 'CP2']
  2190. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8','P9','P7','P5','P3','P1','P2','P4','P6','P8','P10']
  2191. # visual_channels = ['O1', 'PO7', 'PO3']
  2192. # visual_channels = ['O2', 'PO8', 'PO4']
  2193. # visual_channels = ['F1', 'Fz', 'F2']
  2194. # Visual ERP: Calculate and plot the average ERP with SEM for visual stimulation ('V')
  2195. avg_erp_v_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_TD], axis=0)
  2196. sem_erp_v_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_TD])
  2197. avg_erp_v_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_ASD], axis=0)
  2198. sem_erp_v_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_ASD])
  2199. avg_erp_v_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_SIB], axis=0)
  2200. sem_erp_v_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_SIB])
  2201. # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  2202. plot_avg_erp_with_sem(avg_erp_v_TD, sem_erp_v_TD, avg_erp_v_ASD, sem_erp_v_ASD, avg_erp_v_SIB, sem_erp_v_SIB, 'Objects',
  2203. visual_channels, filtered_epochs_lst_TD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  2204. #%% Plot topomap - face
  2205. Evoked_face_TD = mne.grand_average(erp_obj_TD)
  2206. Evoked_face_ASD = mne.grand_average(erp_obj_ASD)
  2207. Evoked_face_SIB = mne.grand_average(erp_obj_SIB)
  2208. times = [-0.05,0.12,0.170,0.250,0.31] # Time in seconds
  2209. Evoked_face_TD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  2210. Evoked_face_ASD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  2211. Evoked_face_SIB.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  2212. #%% save
  2213. plt.savefig('ERP_Topomap_Object_SIB.svg')
  2214. #%% Plot ERP for inverted objects
  2215. visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8']
  2216. # visual_channels = ['C1', 'Cz', 'C2', 'CP1', 'CPz', 'CP2']
  2217. # visual_channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8','P9','P7','P5','P3','P1','P2','P4','P6','P8','P10']
  2218. # visual_channels = ['O1', 'PO7', 'PO3']
  2219. # visual_channels = ['O2', 'PO8', 'PO4']
  2220. # visual_channels = ['F1', 'Fz', 'F2']
  2221. # Visual ERP: Calculate and plot the average ERP with SEM for visual stimulation ('V')
  2222. avg_erp_v_TD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_TD], axis=0)
  2223. sem_erp_v_TD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_TD])
  2224. avg_erp_v_ASD = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_ASD], axis=0)
  2225. sem_erp_v_ASD = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_ASD])
  2226. avg_erp_v_SIB = np.mean([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_SIB], axis=0)
  2227. sem_erp_v_SIB = calculate_sem([average_erp_channels(erp, visual_channels) for erp in erp_obj_U_SIB])
  2228. # Plot Visual ERP with SEM for -0.1 to 0.6 seconds
  2229. plot_avg_erp_with_sem(avg_erp_v_TD, sem_erp_v_TD, avg_erp_v_ASD, sem_erp_v_ASD, avg_erp_v_SIB, sem_erp_v_SIB, 'Inverted Objects',
  2230. visual_channels, filtered_epochs_lst_TD[0].info['sfreq'], plot_tmin=-0.1, plot_tmax=0.6)
  2231. #%% Plot topomap - face
  2232. Evoked_face_TD = mne.grand_average(erp_obj_U_TD)
  2233. Evoked_face_ASD = mne.grand_average(erp_obj_U_ASD)
  2234. Evoked_face_SIB = mne.grand_average(erp_obj_U_SIB)
  2235. times = [-0.05,0.12,0.170,0.250,0.31] # Time in seconds
  2236. Evoked_face_TD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  2237. Evoked_face_ASD.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  2238. Evoked_face_SIB.plot_topomap(times=times, size=1, vlim=(-20, 20), cmap='jet')
  2239. #%% save
  2240. plt.savefig('ERP_Topomap_Inverted_Object_SIB.svg')
  2241. #%% Plot the difference wave: inverted face - face
  2242. # Step 1: Difference waves for each subject (TD)
  2243. erp_diff_TD = [
  2244. average_erp_channels(face, visual_channels) - average_erp_channels(obj, visual_channels)
  2245. for face, obj in zip(erp_face_TD, erp_obj_TD)
  2246. ]
  2247. # ASD group
  2248. erp_diff_ASD = [
  2249. average_erp_channels(face, visual_channels) - average_erp_channels(obj, visual_channels)
  2250. for face, obj in zip(erp_face_ASD, erp_obj_ASD)
  2251. ]
  2252. # SIB group
  2253. erp_diff_SIB = [
  2254. average_erp_channels(face, visual_channels) - average_erp_channels(obj, visual_channels)
  2255. for face, obj in zip(erp_face_SIB, erp_obj_SIB)
  2256. ]
  2257. # Step 2: Compute group average and SEM
  2258. avg_erp_diff_TD = np.mean(erp_diff_TD, axis=0)
  2259. sem_erp_diff_TD = calculate_sem(erp_diff_TD)
  2260. avg_erp_diff_ASD = np.mean(erp_diff_ASD, axis=0)
  2261. sem_erp_diff_ASD = calculate_sem(erp_diff_ASD)
  2262. avg_erp_diff_SIB = np.mean(erp_diff_SIB, axis=0)
  2263. sem_erp_diff_SIB = calculate_sem(erp_diff_SIB)
  2264. # Step 3: Plot the difference waves
  2265. plot_avg_erp_with_sem(
  2266. avg_erp_diff_TD, sem_erp_diff_TD,
  2267. avg_erp_diff_ASD, sem_erp_diff_ASD,
  2268. avg_erp_diff_SIB, sem_erp_diff_SIB,
  2269. 'Face – Object ERP',
  2270. visual_channels,
  2271. filtered_epochs_lst_TD[0].info['sfreq'],
  2272. plot_tmin=-0.1,
  2273. plot_tmax=0.6
  2274. )
  2275. plt.ylim(-8e-6,4e-6)
  2276. #%% Plot the difference wave: face - object ERP
  2277. # Step 1: Difference waves for each subject (TD)
  2278. erp_diff_TD = [
  2279. average_erp_channels(face_U, visual_channels) - average_erp_channels(face, visual_channels)
  2280. for face_U, face in zip(erp_face_U_TD, erp_face_TD)
  2281. ]
  2282. # ASD group
  2283. erp_diff_ASD = [
  2284. average_erp_channels(face_U, visual_channels) - average_erp_channels(face, visual_channels)
  2285. for face_U, face in zip(erp_face_U_ASD, erp_face_ASD)
  2286. ]
  2287. # SIB group
  2288. erp_diff_SIB = [
  2289. average_erp_channels(face_U, visual_channels) - average_erp_channels(face, visual_channels)
  2290. for face_U, face in zip(erp_face_U_SIB, erp_face_SIB)
  2291. ]
  2292. # Step 2: Compute group average and SEM
  2293. avg_erp_diff_TD = np.mean(erp_diff_TD, axis=0)
  2294. sem_erp_diff_TD = calculate_sem(erp_diff_TD)
  2295. avg_erp_diff_ASD = np.mean(erp_diff_ASD, axis=0)
  2296. sem_erp_diff_ASD = calculate_sem(erp_diff_ASD)
  2297. avg_erp_diff_SIB = np.mean(erp_diff_SIB, axis=0)
  2298. sem_erp_diff_SIB = calculate_sem(erp_diff_SIB)
  2299. # Step 3: Plot the difference waves
  2300. plot_avg_erp_with_sem(
  2301. avg_erp_diff_TD, sem_erp_diff_TD,
  2302. avg_erp_diff_ASD, sem_erp_diff_ASD,
  2303. avg_erp_diff_SIB, sem_erp_diff_SIB,
  2304. 'Face – Object ERP',
  2305. visual_channels,
  2306. filtered_epochs_lst_TD[0].info['sfreq'],
  2307. plot_tmin=-0.1,
  2308. plot_tmax=0.6
  2309. )
  2310. plt.ylim(-4e-6,4e-6)
  2311. #%% Plot ERP NO PREP
  2312. # Step 2: Compute ERP for each stimulus type
  2313. ERP_face = epochs_ar_all['face'].average()
  2314. ERP_face_U = epochs_ar_all['face_U'].average()
  2315. ERP_obj = epochs_ar_all['obj'].average()
  2316. ERP_obj_U = epochs_ar_all['obj_U'].average()
  2317. # Step 3: Select cluster of occipital-parietal channels
  2318. channels = ['O1', 'O2', 'PO7', 'PO3', 'PO4', 'PO8']
  2319. picks = [ERP_face.ch_names.index(ch) for ch in channels if ch in ERP_face.ch_names]
  2320. # Step 4: Extract ERP data for selected channels and average across them
  2321. times = ERP_face.times # Time vector (same for all ERPs)
  2322. erp_face_data = ERP_face.data[picks, :].mean(axis=0)
  2323. erp_face_U_data = ERP_face_U.data[picks, :].mean(axis=0)
  2324. erp_obj_data = ERP_obj.data[picks, :].mean(axis=0)
  2325. erp_obj_U_data = ERP_obj_U.data[picks, :].mean(axis=0)
  2326. # Step 5: Plot ERPs
  2327. plt.figure(figsize=(10, 5))
  2328. plt.plot(times, erp_face_data * 1e6, label="Face", color="blue") # Convert V to µV
  2329. plt.plot(times, erp_face_U_data * 1e6, label="Face_U", color="cyan")
  2330. plt.plot(times, erp_obj_data * 1e6, label="Object", color="red")
  2331. plt.plot(times, erp_obj_U_data * 1e6, label="Object_U", color="orange")
  2332. # Step 6: Formatting
  2333. plt.axvline(x=0, color='k', linestyle='--', label="Stimulus Onset") # Mark stimulus onset
  2334. plt.xlabel("Time (s)")
  2335. plt.ylabel("Amplitude (µV)")
  2336. plt.title("ERP over Occipital-Parietal Channels")
  2337. plt.legend()
  2338. plt.grid(True)
  2339. plt.show()
  2340. #%% Calculate GFP
  2341. import numpy as np
  2342. def compute_gfp_per_subject(evoked_list):
  2343. """Return array of shape (n_subjects, n_times) with GFP for each subject."""
  2344. gfp_all = []
  2345. for evoked in evoked_list:
  2346. gfp = evoked.data.std(axis=0) * 1e6 # µV
  2347. gfp_all.append(gfp)
  2348. return np.array(gfp_all) # shape: (n_subjects, n_times)
  2349. # Compute GFP for each condition
  2350. gfp_face_TD = compute_gfp_per_subject(erp_face_TD)
  2351. gfp_face_U_TD = compute_gfp_per_subject(erp_face_U_TD)
  2352. gfp_obj_TD = compute_gfp_per_subject(erp_obj_TD)
  2353. gfp_obj_U_TD = compute_gfp_per_subject(erp_obj_U_TD)
  2354. def mean_and_sem(gfp_array):
  2355. mean = gfp_array.mean(axis=0)
  2356. sem = gfp_array.std(axis=0) / np.sqrt(gfp_array.shape[0])
  2357. return mean, sem
  2358. mean_face, sem_face = mean_and_sem(gfp_face_TD)
  2359. mean_face_U, sem_face_U = mean_and_sem(gfp_face_U_TD)
  2360. mean_obj, sem_obj = mean_and_sem(gfp_obj_TD)
  2361. mean_obj_U, sem_obj_U = mean_and_sem(gfp_obj_U_TD)
  2362. times = erp_face_TD[0].times * 1000 # convert to ms
  2363. import matplotlib.pyplot as plt
  2364. plt.figure(figsize=(12, 6))
  2365. # Plot mean ± SEM
  2366. def plot_gfp(mean, sem, label, color):
  2367. plt.plot(times, mean, label=label, color=color)
  2368. plt.fill_between(times, mean - sem, mean + sem, alpha=0.3, color=color)
  2369. plot_gfp(mean_face, sem_face, 'Face', 'tab:blue')
  2370. plot_gfp(mean_face_U, sem_face_U, 'Inverted Face', 'tab:orange')
  2371. plot_gfp(mean_obj, sem_obj, 'Object', 'tab:green')
  2372. plot_gfp(mean_obj_U, sem_obj_U, 'Inverted Object', 'tab:red')
  2373. plt.axvline(0, color='k', linestyle='--')
  2374. plt.xlabel('Time (ms)')
  2375. plt.ylabel('GFP (µV)')
  2376. plt.title('GFP in TD Group (Mean ± SEM)')
  2377. plt.legend()
  2378. plt.tight_layout()
  2379. plt.show()
  2380. #%% Calculate GFP
  2381. import numpy as np
  2382. def compute_gfp_per_subject(evoked_list):
  2383. """Return array of shape (n_subjects, n_times) with GFP for each subject."""
  2384. gfp_all = []
  2385. for evoked in evoked_list:
  2386. gfp = evoked.data.std(axis=0) * 1e6 # µV
  2387. gfp_all.append(gfp)
  2388. return np.array(gfp_all) # shape: (n_subjects, n_times)
  2389. # Compute GFP for each condition
  2390. gfp_face_ASD = compute_gfp_per_subject(erp_face_ASD)
  2391. gfp_face_U_ASD = compute_gfp_per_subject(erp_face_U_ASD)
  2392. gfp_obj_ASD = compute_gfp_per_subject(erp_obj_ASD)
  2393. gfp_obj_U_ASD = compute_gfp_per_subject(erp_obj_U_ASD)
  2394. def mean_and_sem(gfp_array):
  2395. mean = gfp_array.mean(axis=0)
  2396. sem = gfp_array.std(axis=0) / np.sqrt(gfp_array.shape[0])
  2397. return mean, sem
  2398. mean_face, sem_face = mean_and_sem(gfp_face_ASD)
  2399. mean_face_U, sem_face_U = mean_and_sem(gfp_face_U_ASD)
  2400. mean_obj, sem_obj = mean_and_sem(gfp_obj_ASD)
  2401. mean_obj_U, sem_obj_U = mean_and_sem(gfp_obj_U_ASD)
  2402. times = erp_face_ASD[0].times * 1000 # convert to ms
  2403. import matplotlib.pyplot as plt
  2404. plt.figure(figsize=(12, 6))
  2405. # Plot mean ± SEM
  2406. def plot_gfp(mean, sem, label, color):
  2407. plt.plot(times, mean, label=label, color=color)
  2408. plt.fill_between(times, mean - sem, mean + sem, alpha=0.3, color=color)
  2409. plot_gfp(mean_face, sem_face, 'Face', 'tab:blue')
  2410. plot_gfp(mean_face_U, sem_face_U, 'Inverted Face', 'tab:orange')
  2411. plot_gfp(mean_obj, sem_obj, 'Object', 'tab:green')
  2412. plot_gfp(mean_obj_U, sem_obj_U, 'Inverted Object', 'tab:red')
  2413. plt.axvline(0, color='k', linestyle='--')
  2414. plt.xlabel('Time (ms)')
  2415. plt.ylabel('GFP (µV)')
  2416. plt.title('GFP in ASD Group (Mean ± SEM)')
  2417. plt.legend()
  2418. plt.tight_layout()
  2419. plt.show()
  2420. #%%
  2421. # Compute difference wave per subject at the selected channels
  2422. diff_erp_subjects = [
  2423. average_erp_channels(erp_u_face, visual_channels) - average_erp_channels(erp_face, visual_channels)
  2424. for erp_face, erp_u_face in zip(erp_face_TD, erp_face_U_TD)
  2425. ]
  2426. import numpy as np
  2427. # Convert to array
  2428. diff_erp_subjects = np.array(diff_erp_subjects)
  2429. # Mean and SEM across subjects
  2430. avg_diff_erp = np.mean(diff_erp_subjects, axis=0)
  2431. sem_diff_erp = np.std(diff_erp_subjects, axis=0) / np.sqrt(len(diff_erp_subjects))
  2432. import matplotlib.pyplot as plt
  2433. # Time vector
  2434. sfreq = filtered_epochs_lst_TD[0].info['sfreq']
  2435. n_times = avg_diff_erp.shape[0]
  2436. # Extract time vector directly from ERP
  2437. times = erp_face_TD[0].times
  2438. # Plot
  2439. plt.figure(figsize=(10, 4))
  2440. plt.plot(times, avg_diff_erp, label='Inverted Face - Face', color='black')
  2441. plt.fill_between(times, avg_diff_erp - sem_diff_erp, avg_diff_erp + sem_diff_erp,
  2442. color='black', alpha=0.3)
  2443. plt.axhline(0, color='gray', linestyle='--')
  2444. plt.axvline(0, color='gray', linestyle='--')
  2445. plt.title(f'Difference ERP at {visual_channels}')
  2446. plt.xlabel('Time (s)')
  2447. plt.ylabel('Amplitude (µV)')
  2448. plt.legend()
  2449. plt.tight_layout()
  2450. plt.show()
  2451. plt.xlim([-0.1,0.6])
  2452. #%% Plot individual ERP for peak detection illustration
  2453. import matplotlib.pyplot as plt
  2454. def plot_peaks_per_subject(evoked_list, channel_names, p1_window=(0.07, 0.17), n170_window=(0.14, 0.23), group_name='TD', condition='Face'):
  2455. for i, evoked in enumerate(evoked_list):
  2456. ch_indices = [evoked.ch_names.index(ch) for ch in channel_names]
  2457. times = evoked.times
  2458. signal = evoked.data[ch_indices].mean(axis=0) * 1e6 # µV
  2459. # Extract windowed data
  2460. p1_mask = (times >= p1_window[0]) & (times <= p1_window[1])
  2461. n170_mask = (times >= n170_window[0]) & (times <= n170_window[1])
  2462. # P1
  2463. p1_data = signal[p1_mask]
  2464. p1_times = times[p1_mask]
  2465. p1_idx = p1_data.argmax()
  2466. p1_time = p1_times[p1_idx] * 1000
  2467. p1_amp = p1_data[p1_idx]
  2468. # N170
  2469. n170_data = signal[n170_mask]
  2470. n170_times = times[n170_mask]
  2471. n170_idx = n170_data.argmin()
  2472. n170_time = n170_times[n170_idx] * 1000
  2473. n170_amp = n170_data[n170_idx]
  2474. # Plot
  2475. plt.figure(figsize=(8, 4))
  2476. plt.plot(times * 1000, signal, label='ERP')
  2477. plt.axvline(p1_time, color='blue', linestyle='--', label='P1 peak')
  2478. plt.axvline(n170_time, color='red', linestyle='--', label='N170 peak')
  2479. plt.plot(p1_time, p1_amp, 'bo')
  2480. plt.plot(n170_time, n170_amp, 'ro')
  2481. plt.title(f'{group_name} - {condition} - Subject {i+1}')
  2482. plt.xlabel('Time (ms)')
  2483. plt.ylabel('Amplitude (µV)')
  2484. plt.axvline(0, color='k', linestyle='--')
  2485. plt.legend()
  2486. plt.tight_layout()
  2487. plt.show()
  2488. # Define clusters
  2489. left_cluster = ['P7']
  2490. # Plot ERP and detected peaks for TD group, face condition
  2491. plot_peaks_per_subject(erp_face_ASD, left_cluster, group_name='TD', condition='Face')
  2492. #%% Difference wave between face TD and face ASD
  2493. import numpy as np
  2494. import matplotlib.pyplot as plt
  2495. import mne
  2496. # --- Helper functions ---
  2497. def extract_data(evokeds, channels):
  2498. """Extract channel data from list of Evokeds into array (n_subjects, n_times), averaged across channels."""
  2499. n_subj = len(evokeds)
  2500. n_times = len(evokeds[0].times)
  2501. data = np.zeros((n_subj, n_times))
  2502. for i, ev in enumerate(evokeds):
  2503. data[i] = ev.copy().pick(channels).data.mean(axis=0)
  2504. return data
  2505. # ================================
  2506. # FACE stimuli only: TD vs ASD
  2507. # ================================
  2508. channels = ['O1', 'PO7', 'O2', 'PO8'] # Parieto-occipital cluster
  2509. times = erp_face_TD[0].times * 1000 # ms
  2510. # Extract ERP data averaged across channels
  2511. data_TD = extract_data(erp_face_TD, channels) * 1e6 # µV
  2512. data_ASD = extract_data(erp_face_ASD, channels) * 1e6
  2513. # Extract ERP data averaged across channels
  2514. # data_TD = extract_data(erp_obj_TD, channels) * 1e6 # µV
  2515. # data_ASD = extract_data(erp_obj_ASD, channels) * 1e6
  2516. # Compute group means and SEM
  2517. mean_TD = data_TD.mean(axis=0)
  2518. sem_TD = data_TD.std(axis=0) / np.sqrt(data_TD.shape[0])
  2519. mean_ASD = data_ASD.mean(axis=0)
  2520. sem_ASD = data_ASD.std(axis=0) / np.sqrt(data_ASD.shape[0])
  2521. # Difference wave TD - ASD
  2522. diff_wave = mean_TD - mean_ASD
  2523. # ================================
  2524. # Plot waveform with SEM shading
  2525. # ================================
  2526. plt.figure(figsize=(10, 6))
  2527. plt.plot(times, diff_wave, color='black', label='TD - ASD')
  2528. plt.axvline(0, color='k', linestyle='--')
  2529. plt.axhline(0, color='k', linewidth=0.5)
  2530. plt.xlabel('Time (ms)')
  2531. plt.ylabel('Amplitude Difference (µV)')
  2532. plt.title('TD - ASD Difference Wave (Faces) - Parieto-Occipital Cluster')
  2533. plt.xlim([-100, 600])
  2534. plt.ylim([-2.2,2])
  2535. plt.grid(False)
  2536. plt.legend()
  2537. plt.tight_layout()
  2538. plt.show()
  2539. # ================================
  2540. # Compute Evoked difference for topomap
  2541. # ================================
  2542. # Compute grand average per group for face condition
  2543. grand_TD_face = mne.grand_average(erp_face_TD)
  2544. grand_ASD_face = mne.grand_average(erp_face_ASD)
  2545. # Difference Evoked (TD - ASD)
  2546. diff_evoked = mne.combine_evoked([grand_TD_face, grand_ASD_face], weights=[1, -1])
  2547. # ================================
  2548. # Plot topomaps at selected times
  2549. # ================================
  2550. times_of_interest = [-0.05, 0.11, 0.15, 0.24, 0.35] # in seconds
  2551. diff_evoked.plot_topomap(times=times_of_interest, ch_type='eeg', scalings=1,
  2552. time_unit='s', cmap='RdBu_r', vmin=-3, vmax=3,
  2553. title='TD - ASD Difference (Faces)')

Analysis_ERP_FAST.py at commit 3f111fe, no license · at the source

Overview

Authors: Theo Vanneau1, Chloe Brittenham1, Megan Darrell1, John J Foxe1,2, Sophie Molholm1,2
  1. The Cognitive Neurophysiology Laboratory, Departments of Pediatrics & Dominick P. Purpura Department of Neuroscience, Albert Einstein College of Medicine, Bronx, NY 10461 USA
  2. The Frederick J. and Marion A. Schindler Cognitive Neurophysiology Laboratory, The Ernest J. Del Monte Institute for Neuroscience, Department of Neuroscience, University of Rochester School of Medicine and Dentistry, Rochester, NY 14642 USA
Institutions: Albert Einstein College of Medicine (United States); University of Rochester Medicine (United States); University of Rochester (United States)
Journal: Journal of neurodevelopmental disorders, volume 18, issue 1, article 38
Dates: received 22 October 2025; accepted 1 May 2026; published online 8 May 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1186/s11689-026-09706-z · PMID 42104235 · PMCID PMC13326095 · OpenAlex W4415890447
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), human (organism), autism (population), cognitive (subfield)
Methods: Spectral & time-frequency, Statistics, Smoothing, state filtering, decompositions, Connectivity, Preprocessing, Evoked potentials, fMRI & imaging, Physiology & signal measures
Keywords: Faces processing, neural oscillations, autism, broad autism phenotype, electroencephalography, event-related potentials, face inversion effect, theta, gamma, attention
MeSH: Autistic Disorder*, Brain*, Brain Waves*, Evoked Potentials*, Facial Recognition*, Adolescent, Attention, Child, Electroencephalography, Female, Humans, Male, Photic Stimulation, Siblings (* major topic)
Topic: Face Recognition and Perception (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: NIGMS NIH HHS (K12 GM102779); NICHD NIH HHS (P50 HD105352, P50 HD103536); Simons Foundation Autism Research Initiative (SFARI Award # 874845)
Citations: cited by 2 papers (Europe PMC); 128 references in the paper

Abstract

Face processing is fundamental to social communication and has been a major focus of autism research. While event-related potential (ERPs) studies of face processing have produced mixed results, little work has examined neuro-oscillatory dynamics, which may better capture the integrity of underlying networks. To address this gap, EEG was recorded from children aged 8–13 across three groups: autistic (n = 50), non-autistic (n = 38) and siblings of autistic children (n = 26), during a visual oddball task. In a blocked design, participants viewed faces and objects, presented upright and inverted (non-targets), to assess the face inversion effect (the FIE; a larger or delayed N170 to inverted than upright faces), and responded to infrequent shadow versions (targets). Analyses using permutation statistics and linear mixed models focused on non-target stimuli, quantifying face-related ERPs (P1, N170) and oscillatory activity associated with sensory and attentional processing (theta, alpha, gamma). Across groups, faces elicited earlier P1 and larger N170 amplitudes than objects, and showed a FIE. Furthermore, the rightward lateralization of the FIE was reduced for autistic participants. Analyses in the frequency domain revealed greater induced theta for inverted versus upright stimuli and for faces versus objects, revealing face specific effects, and stronger theta for inverted faces for the autistic and sibling groups, suggesting greater cognitive effort in processing these social stimuli. Gamma-band inter-trial phase coherence exhibited face selectivity only in the non-autistic group, pointing to differences in early network synchronization in autistic children relative to their non-autistic peers, whereas alpha event-related desynchronization did not vary by group or category. Altogether, these findings support altered neural synchronization/efficiency for autistic participants and siblings of autistic children, that is specific to face stimuli and seen despite largely typical sensory driven encoding. These data suggest that neural oscillatory assays are more sensitive to face processing differences in autism than broadband ERPs and that these oscillatory assays may be endophenotypic.

Supplementary Information: The online version contains supplementary material available at 10.1186/s11689-026-09706-z.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 3 matches between paragraphs and lines of code.

tvanneau/SFARI-FAST

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 3f111fe617505c9480d7513be530fafc44fbf2e4, 4 May 2026
Languages: Python (4)
Size: 5 files, 4 scripts
Software Heritage: not archived
Found in: the text, “EEG recordings & preprocessing”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (4 files), MNE-Python (4 files), NumPy (4 files), pandas (3 files), PyPREP (2 files), seaborn (2 files), autoreject (1 file), SciPy (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
5 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 4 scripts, each with its path and the digest of its content;
  • 3 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data availability

The dataset supporting the conclusions of this article is available in the ‘SFARI_EEG_multi-paradigm dataset’ (‘FAST’ paradigm) repository (BIDS format), [doi:10.18112/openneuro.ds006780.v1.0.0](https:/doi.org/10.18112/openneuro.ds006780.v1.0.0) . The scripts used for preprocessing and analyses of the data are available on GitHub: https://github.com/tvanneau/SFARI-FAST.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Authors: added Chloe Brittenham (0000-0002-1867-2660); John J Foxe (0000-0002-4300-3098); Sophie Molholm (0000-0002-0094-4015); removed Chloe Brittenham; John J Foxe; Sophie Molholm

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 10 keywords, 14 MeSH terms, 3 funders, 126 references.

Cite

This paper

Vanneau, T., Brittenham, C., Darrell, M., Foxe, J. J., & Molholm, S. (2026). Neural oscillatory dynamics reveal altered top-down and integrative mechanisms during face processing in autistic children and unaffected siblings of autistic children. Journal of neurodevelopmental disorders, 18(1), 38. https://doi.org/10.1186/s11689-026-09706-z

BibTeX

@article{vanneau2026neural,
author = {Vanneau, Theo and Brittenham, Chloe and Darrell, Megan and Foxe, John J and Molholm, Sophie},
title = {{Neural oscillatory dynamics reveal altered top-down and integrative mechanisms during face processing in autistic children and unaffected siblings of autistic children}},
journal = {Journal of neurodevelopmental disorders},
year = {2026},
month = may,
volume = {18},
number = {1},
pages = {38},
publisher = {BMC},
issn = {1866-1947},
doi = {10.1186/s11689-026-09706-z},
url = {https://doi.org/10.1186/s11689-026-09706-z},
pmid = {42104235},
pmcid = {PMC13326095}
}

RIS

TY - JOUR
AU - Vanneau, Theo
AU - Brittenham, Chloe
AU - Darrell, Megan
AU - Foxe, John J
AU - Molholm, Sophie
TI - Neural oscillatory dynamics reveal altered top-down and integrative mechanisms during face processing in autistic children and unaffected siblings of autistic children
T2 - Journal of neurodevelopmental disorders
J2 - J Neurodev Disord
PY - 2026
DA - 2026/05/08
VL - 18
IS - 1
SP - 38
SN - 1866-1947
PB - BMC
DO - 10.1186/s11689-026-09706-z
UR - https://doi.org/10.1186/s11689-026-09706-z
LA - en
ER -

CSL-JSON

{
"id": "10.1186/s11689-026-09706-z",
"type": "article-journal",
"title": "Neural oscillatory dynamics reveal altered top-down and integrative mechanisms during face processing in autistic children and unaffected siblings of autistic children",
"container-title": "Journal of neurodevelopmental disorders",
"author": [
{
"family": "Vanneau",
"given": "Theo"
},
{
"family": "Brittenham",
"given": "Chloe"
},
{
"family": "Darrell",
"given": "Megan"
},
{
"family": "Foxe",
"given": "John J"
},
{
"family": "Molholm",
"given": "Sophie"
}
],
"container-title-short": "J Neurodev Disord",
"volume": "18",
"issue": "1",
"page": "38",
"DOI": "10.1186/s11689-026-09706-z",
"PMID": "42104235",
"PMCID": "PMC13326095",
"ISSN": "1866-1947",
"publisher": "BMC",
"URL": "https://doi.org/10.1186/s11689-026-09706-z",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
8
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1093/nc/niag029 [code]
A data-driven approach to identifying and evaluating connectivity-based neural correlates of conscious visual perception.
Journal: Neuroscience of consciousness
In common: PyPREP, autoreject, MNE-Python, 5 other tools, cognitive, 2 references
[2] doi:10.1038/s41597-025-05174-7 [code]
A large-scale MEG and EEG dataset for object recognition in naturalistic scenes
Journal: n/a
In common: PyPREP, MNE-Python, seaborn, 4 other tools, EEG, 3 references
[3] doi:10.1038/s41597-026-07350-9 [code]
An open multi-center MEG-EEG dataset for studying conscious visual perception.
Journal: Scientific data
In common: PyPREP, autoreject, MNE-Python, 5 other tools, EEG, 1 reference
[4] doi:10.1093/cercor/bhag113 [code]
Long-term reliability and stability of parameterized resting state EEG: evidence from a five-year follow-up.
Journal: Cerebral cortex (New York, N.Y. : 1991)
In common: PyPREP, MNE-Python, seaborn, 4 other tools, EEG, 3 references
[5] doi:10.1097/j.pain.0000000000004044 [code]
No effect of rhythmic visual stimulation on experimental pain perception.
Journal: Pain
In common: PyPREP, MNE-Python, seaborn, 4 other tools, EEG, cognitive, 2 references
[6] doi:10.3389/fncom.2026.1786996 [code]
Schumann-anchored golden ratio organization of human neural oscillations.
Journal: Frontiers in computational neuroscience
In common: MNE-Python, seaborn, pandas, 3 other tools, EEG, 4 references
[7] doi:10.1038/s41597-026-07377-y [code]
An open-access multi-site fMRI dataset for investigating conscious visual perception.
Journal: Scientific data
In common: PyPREP, autoreject, MNE-Python, 5 other tools
[8] doi:10.3390/s26134019 [code]
NeuroStat: An Open-Source EEG Connectivity Platform for Randomised Controlled Trials.
Journal: Sensors (Basel, Switzerland)
In common: MNE-Python, pandas, SciPy, 2 other tools, EEG, 5 references
[9] doi:10.1002/hbm.70368 [code]
The Mismatch Negativity Compared: EEG, SQUID‐MEG, and Novel 4 Helium‐OPMs
Journal: n/a
In common: autoreject, MNE-Python, seaborn, 3 other tools, EEG, cognitive, 2 references
[10] doi:10.1162/imag.a.1229 [code]
40 Hz audiovisual stimulation improves sustained attention and related brain oscillations.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: seaborn, pandas, SciPy, 2 other tools, EEG, 4 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.