diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..620a056 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..827db76 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Ignore data folders +ds000117_pruned/ +eeglab/ +ds002718/ + +# Ignore slides folder completely +slides/ + +# Keep all .m files tracked +!*.m \ No newline at end of file diff --git a/script_01_import_data.m b/script_01_import_data.m index 603c0d0..d253a7e 100644 --- a/script_01_import_data.m +++ b/script_01_import_data.m @@ -1,85 +1,140 @@ % Wakeman & Henson Data analysis: Import raw data, rename events and channels, adjust latencies. % % Authors: Arnaud Delorme, Ramon Martinez-Cancino, Johanna Wagner, Romain Grandchamp +% +% This script imports raw brain recording data (EEG or MEG) and prepares it for analysis. +% It loads the data file, sets up channel locations, fixes event markers, and saves the result. % script folder, this must be updated to the files on your enviroment. clear; % clearing all is recommended to avoid variable not being erased between calls -% Comment one of the two lines below to process EEG or MEG data -%chantype = { 'megmag' }; % process MEG megmag channels -%chantype = { 'megplanar' }; % process MEG megplanar channels -chantype = { 'eeg' }; % process EEG - -% Paths below must be updated to the files on your enviroment. -path2data = fullfile(pwd,'ds000117_pruned', 'derivatives', 'meg_derivatives', 'sub-01', 'ses-meg/', 'meg/'); % Path to data +% Choose which type of brain recording to process: +% - EEG: electrical activity from the brain (electrodes on scalp) +% - MEG: magnetic activity from the brain (magnetometers) +% Uncomment (remove the %) from ONE of the lines below to process that type +%chantype = { 'megmag' }; % Process MEG magnetometer channels +%chantype = { 'megplanar' }; % Process MEG planar gradiometer channels +chantype = { 'eeg' }; % Process EEG channels (currently selected) + +% Set the path to where your data files are located +% The path below points to the data folder. Update this if your files are in a different location. +path2data = fullfile(pwd,'ds000117_pruned', 'derivatives', 'meg_derivatives', 'sub-01', 'ses-meg/', 'meg/'); filename = 'sub-01_ses-meg_task-facerecognition_run-01_proc-sss_meg.fif'; -[ALLEEG, EEG, CURRENTSET] = eeglab; % start EEGLAB +% Start EEGLAB - this opens the EEGLAB interface and initializes the workspace +[ALLEEG, EEG, CURRENTSET] = eeglab; %% IMPORTING THE DATA -% Step 1: Importing data with FileIO +% Step 1: Load the raw data file into EEGLAB +% This reads the brain recording file (in .fif format) and loads it into MATLAB EEG = pop_fileio(fullfile(path2data, filename)); -% Adjust some fields +% Step 2: Set basic information about the dataset EEG.filename = 'sub-01_ses-meg_task-facerecognition_run-01_proc-sss_meg.fif'; EEG.setname = 'sub-01_ses-meg_task-facerecognition_run-01_proc-sss_meg'; EEG.subject = 'sub-01'; -% Step 2: Adding fiducials and rotating montage. Note:The channel location from this points were extracted from the sub-01_ses-meg_coordsystem.json -% files (see below) and written down here. The reason is that File-IO does not import these coordinates. +%% SETTING UP CHANNEL LOCATIONS AND TYPES + +% Step 3: Add fiducial points (anatomical landmarks) to help locate channels in 3D space +% Fiducials are reference points on the head: LPA (left ear), RPA (right ear), and Nz (nose) +% These coordinates were extracted from the dataset's coordinate system file +% Note: The channel locations from these points were extracted from the sub-01_ses-meg_coordsystem.json +% file and written here because File-IO doesn't automatically import these coordinates. n = length(EEG.chanlocs)+1; -EEG=pop_chanedit(EEG, 'changefield',{n+0,'labels','LPA'},'changefield',{n+0,'X','0'}, 'changefield',{n+0,'Y','7.1'},'changefield',{n+0,'Z','0'},... +EEG = pop_chanedit(EEG, 'changefield',{n+0,'labels','LPA'},'changefield',{n+0,'X','0'}, 'changefield',{n+0,'Y','7.1'},'changefield',{n+0,'Z','0'},... 'changefield',{n+1,'labels','RPA'},'changefield',{n+1,'X','0'}, 'changefield',{n+1,'Y','-7.756'},'changefield',{n+1,'Z','0'},... 'changefield',{n+2,'labels','Nz'} ,'changefield',{n+2,'Y','0'},'changefield',{n+2,'X','10.636'},'changefield',{n+2,'Z','0'}); EEG = eeg_checkset(EEG); -% Changing Channel types and removing channel locations for channels 61-64 (Raw data types are incorrect) +% Step 4: Fix incorrect channel types for eye and heart monitoring channels +% These channels record eye movements (HEOG, VEOG) and heart activity (EKG), not brain activity +% The raw data had incorrect labels, so we're correcting them and removing their 3D locations +% (since they're not brain channels, they don't need spatial coordinates) EEG = pop_chanedit(EEG,'changefield',{367 'type' 'HEOG' 'X' [] 'Y' [] 'Z' [] 'theta' [] 'radius' [] 'sph_theta' [] 'sph_phi' [] 'sph_radius' []}); EEG = pop_chanedit(EEG,'changefield',{368 'type' 'VEOG' 'X' [] 'Y' [] 'Z' [] 'theta' [] 'radius' [] 'sph_theta' [] 'sph_phi' [] 'sph_radius' []}); EEG = pop_chanedit(EEG,'changefield',{369 'type' 'EKG' 'X' [] 'Y' [] 'Z' [] 'theta' [] 'radius' [] 'sph_theta' [] 'sph_phi' [] 'sph_radius' []}); EEG = pop_chanedit(EEG,'changefield',{370 'type' 'EKG' 'X' [] 'Y' [] 'Z' [] 'theta' [] 'radius' [] 'sph_theta' [] 'sph_phi' [] 'sph_radius' []}); -% Step 3: Re-import events from STI101 channel (the original ones are incorect) +%% FIXING EVENT MARKERS + +% Step 5: Re-extract event markers from the STI101 channel +% Events mark when things happened during the experiment (like when a face was shown) +% The original events in the file were incorrect, so we're reading them from a special channel +% that recorded the actual event codes. We extract the first 5 bits of the signal. edgelenval = 1; -EEG = pop_chanevent(EEG, 381,'edge','leading','edgelen',edgelenval,'delevent','on','delchan','off','oper','double(bitand(int32(X),31))'); % first 5 bits +EEG = pop_chanevent(EEG, 381,'edge','leading','edgelen',edgelenval,'delevent','on','delchan','off','oper','double(bitand(int32(X),31))'); -% Step 4: Selecting EEG or MEG data +%% SELECTING CHANNEL TYPE + +% Step 6: Keep only the channels we want to analyze (EEG or MEG) +% This removes all other channel types, keeping only the ones specified in 'chantype' above EEG = pop_select(EEG, 'chantype', chantype); EEG.chaninfo = rmfield(EEG.chaninfo, 'topoplot'); EEG.chaninfo = rmfield(EEG.chaninfo, 'originalnosedir'); -% Step 5: Recomputing head center (for display only) Optional +%% VISUALIZING CHANNEL LOCATIONS + +% Step 7: Recalculate the center of the head (for visualization purposes only) +% This helps display the channel locations correctly on a head diagram +% Optional: This step is just for making nice plots, not required for analysis EEG = pop_chanedit(EEG, 'eval','chans = pop_chancenter( chans, [],[])'); figure; topoplot([],EEG.chanlocs, 'style', 'blank', 'electrodes', 'labelpoint', 'chaninfo', EEG.chaninfo); -% Step 6: Cleaning artefactual events (keep only valid event codes) ( -% NOT BE NECCESARY FOR US -EEG = pop_selectevent( EEG, 'type',[5 6 7 13 14 15 17 18 19] ,'deleteevents','on'); - -% Step 7: Fix button press info -EEG.event(74).type = 256; % Artifact; Overlapping of 256 and 4096 - -% Step 8: Renaming button press events -EEG = pop_selectevent( EEG, 'type',256, 'renametype', 'left_nonsym','deleteevents','off'); % Event type : 'left_nonsym' -EEG = pop_selectevent( EEG, 'type',4096,'renametype', 'right_sym','deleteevents','off'); % Event type : 'right_sym' - -% Step 9: Rename face presentation events (information provided by authors) -EEG = pop_selectevent( EEG, 'type',5,'renametype','Famous','deleteevents','off'); % famous_new -EEG = pop_selectevent( EEG, 'type',6,'renametype','Famous','deleteevents','off'); % famous_second_early -EEG = pop_selectevent( EEG, 'type',7,'renametype','Famous','deleteevents','off'); % famous_second_late - -EEG = pop_selectevent( EEG, 'type',13,'renametype','Unfamiliar','deleteevents','off'); % unfamiliar_new -EEG = pop_selectevent( EEG, 'type',14,'renametype','Unfamiliar','deleteevents','off'); % unfamiliar_second_early -EEG = pop_selectevent( EEG, 'type',15,'renametype','Unfamiliar','deleteevents','off'); % unfamiliar_second_late +%% CLEANING AND RENAMING EVENTS -EEG = pop_selectevent( EEG, 'type',17,'renametype','Scrambled','deleteevents','off'); % scrambled_new -EEG = pop_selectevent( EEG, 'type',18,'renametype','Scrambled','deleteevents','off'); % scrambled_second_early -EEG = pop_selectevent( EEG, 'type',19,'renametype','Scrambled','deleteevents','off'); % scrambled_second_late +% Step 8: Remove invalid event markers +% Keep only the event codes that represent actual experimental events +% The numbers [5 6 7 13 14 15 17 18 19] are the valid event codes for this experiment +% Note: This step may not be necessary for all datasets - it depends on your data quality +EEG = pop_selectevent( EEG, 'type',[5 6 7 13 14 15 17 18 19] ,'deleteevents','on'); -% Step 10: Correcting event latencies (events have a shift of 34 ms as per the authors) +% Step 9: Fix a specific event that had overlapping codes +% One event (event #74) had two codes mixed together (256 and 4096), so we fix it manually +EEG.event(74).type = 256; % This event was an artifact where two codes overlapped + +% Step 10: Rename button press events to meaningful names +% The participant pressed buttons during the experiment. We rename the numeric codes +% to descriptive names so we can understand what happened later. +EEG = pop_selectevent( EEG, 'type',256, 'renametype', 'left_nonsym','deleteevents','off'); % Left button press for non-symmetric faces +EEG = pop_selectevent( EEG, 'type',4096,'renametype', 'right_sym','deleteevents','off'); % Right button press for symmetric faces + +% Step 11: Rename face presentation events to meaningful names +% During the experiment, different types of faces were shown. The original data just had +% numbers (5, 6, 7, etc.), but we rename them to describe what was shown: +% - Famous: faces of famous people +% - Unfamiliar: faces of people the participant didn't know +% - Scrambled: scrambled/distorted face images +% Each type has three variants: new (first time shown), second_early, second_late (shown again) +EEG = pop_selectevent( EEG, 'type',5,'renametype','Famous','deleteevents','off'); % Famous face - first presentation +EEG = pop_selectevent( EEG, 'type',6,'renametype','Famous','deleteevents','off'); % Famous face - second presentation (early) +EEG = pop_selectevent( EEG, 'type',7,'renametype','Famous','deleteevents','off'); % Famous face - second presentation (late) + +EEG = pop_selectevent( EEG, 'type',13,'renametype','Unfamiliar','deleteevents','off'); % Unfamiliar face - first presentation +EEG = pop_selectevent( EEG, 'type',14,'renametype','Unfamiliar','deleteevents','off'); % Unfamiliar face - second presentation (early) +EEG = pop_selectevent( EEG, 'type',15,'renametype','Unfamiliar','deleteevents','off'); % Unfamiliar face - second presentation (late) + +EEG = pop_selectevent( EEG, 'type',17,'renametype','Scrambled','deleteevents','off'); % Scrambled face - first presentation +EEG = pop_selectevent( EEG, 'type',18,'renametype','Scrambled','deleteevents','off'); % Scrambled face - second presentation (early) +EEG = pop_selectevent( EEG, 'type',19,'renametype','Scrambled','deleteevents','off'); % Scrambled face - second presentation (late) + +%% CORRECTING EVENT TIMING + +% Step 12: Fix the timing of events (they were shifted by 34 milliseconds) +% Sometimes event markers are recorded slightly off from when things actually happened +% The authors of this dataset found that all events need to be shifted forward by 34 ms +% to align with when the faces actually appeared on screen EEG = pop_adjustevents(EEG,'addms',34); -% Step 11: Creating folder to save data if does not exist yet +%% SAVE THE PREPARED DATASET + +% Step 13: Save the imported and cleaned dataset +% Now that we've loaded the data, fixed the channels, and corrected the events, +% we save it as a .set file (EEGLAB's standard format) for use in later analysis steps +% You can also do this via the menu: File > Save current dataset as +% The folder will be created automatically if it doesn't exist EEG = pop_saveset( EEG,'filename',['wh_S01' '_run_01' '.set'],'filepath',path2data); + +% Display a summary of all event types in the dataset (for verification) eeg_eventtypes(EEG) diff --git a/script_02_preprocess_data.m b/script_02_preprocess_data.m index 65dbac1..301cb70 100644 --- a/script_02_preprocess_data.m +++ b/script_02_preprocess_data.m @@ -1,20 +1,28 @@ % Wakeman & Henson Data analysis: Preprocess data. % % Authors: Arnaud Delorme, Ramon Martinez-Cancino, Johanna Wagner, Romain Grandchamp +% +% This script preprocesses (cleans and prepares) brain recording data for analysis. +% Preprocessing removes noise, artifacts, and unwanted signals so you can study the brain activity. +% Steps include: re-referencing, filtering, removing bad channels, and separating brain signals +% from artifacts using Independent Component Analysis (ICA). -% Clearing all is recommended to avoid variable not being erased between calls +%% +% Clearing all variables is recommended to avoid leftover variables from previous runs clear; -% Path to data below. Using relative paths so no need to update. -path2data = fullfile(pwd,'ds000117_pruned', 'derivatives', 'meg_derivatives', 'sub-01', 'ses-meg/', 'meg/'); % Path to data +% Set the path to where your data files are located +% This loads the dataset that was created by script_01_import_data.m +path2data = fullfile(pwd,'ds000117_pruned', 'derivatives', 'meg_derivatives', 'sub-01', 'ses-meg/', 'meg/'); filename = 'wh_S01_run_01.set'; -% Start EEGLAB +% Start EEGLAB - this opens the EEGLAB interface and initializes the workspace [ALLEEG, EEG, CURRENTSET] = eeglab; -%% Loading data -% use menu item File > Load existing dataset -% and select file ds000117_pruned/derivatives/meg_derivatives/sub-01/ses-meg/meg/wh_S01_run_01.set +%% LOADING THE DATA + +% Load the dataset that was prepared in script_01_import_data.m +% You can also do this via the menu: File > Load existing dataset EEG = pop_loadset('filename', filename,'filepath',path2data); %% Re-Reference @@ -33,53 +41,106 @@ EEG = pop_eegfiltnew(EEG, 1, 0); % High pass at 1Hz EEG = pop_eegfiltnew(EEG, 0, 40); % Low pass below 40 -%% Automatic rejection of bad channels -% use menu item Tools > Reject data using Clean_rawdata and ASR -% Apply clean_artifacts() to reject bad channels +% Apply filters to remove unwanted frequency components +% What is filtering? It removes certain frequencies (like very slow drifts or high-frequency noise) +% - High-pass filter at 1 Hz: Removes very slow drifts (like from sweating or breathing) +% - Low-pass filter at 40 Hz: Removes high-frequency noise (like muscle activity, line noise at 50/60 Hz) +% This keeps the brain signals we care about (typically 1-40 Hz for most EEG studies) +% You can also do this via the menu: Tools > Filter the data > Basic FIR filter +EEG = pop_eegfiltnew(EEG, 1, 0); % High-pass filter: keep frequencies above 1 Hz +EEG = pop_eegfiltnew(EEG, 0, 40); % Low-pass filter: keep frequencies below 40 Hz + +%% AUTOMATICALLY DETECTING AND REMOVING BAD CHANNELS + +% Automatically find and remove channels that are recording poorly +% What are bad channels? These are electrodes that have: +% - Poor contact with the scalp (loose electrodes) +% - Too much noise or artifacts +% - Unusual signals compared to other channels +% The algorithm compares each channel to others - if it's too different, it's marked as bad +% Different thresholds are used for MEG (0.4) vs EEG (0.9) because they have different signal characteristics +% You can also do this via the menu: Tools > Reject data using Clean_rawdata and ASR if contains(EEG.chanlocs(1).type, 'meg') - minChanCorr = 0.4; + minChanCorr = 0.4; % MEG channels need lower correlation threshold (they're naturally more variable) else - minChanCorr = 0.9; + minChanCorr = 0.9; % EEG channels should be highly correlated with neighbors end -EEG = clean_artifacts(EEG, 'Highpass', 'off',... - 'ChannelCriterion', minChanCorr,... - 'ChannelCriterionMaxBadTime', 0.4,... % not in GUI - 'LineNoiseCriterion', 4,... % line noise - 'BurstCriterion', 'off',... - 'WindowCriterion','off' ); +EEG = clean_artifacts(EEG, 'Highpass', 'off',... % Don't apply high-pass filter (we already did) + 'ChannelCriterion', minChanCorr,... % Threshold for detecting bad channels + 'ChannelCriterionMaxBadTime', 0.4,... % Max fraction of time a channel can be bad + 'LineNoiseCriterion', 4,... % Threshold for detecting line noise (50/60 Hz) + 'BurstCriterion', 'off',... % Don't detect burst artifacts yet + 'WindowCriterion','off' ); % Don't detect bad time windows yet -%% Re-Reference +%% RE-REFERENCING AGAIN (AFTER REMOVING BAD CHANNELS) + +% Re-reference again because removing bad channels changes the average +% When we remove bad channels, the average of all channels changes, so we need to recalculate EEG = pop_reref(EEG,[]); -% %% Repair bursts and reject bad portions of data -EEG = clean_artifacts( EEG, 'Highpass', 'off',... - 'ChannelCriterion', 'off',... - 'LineNoiseCriterion', 'off',... - 'BurstCriterion', 30,... - 'WindowCriterion',0.3); % not in gui - -%% run ICA -% use menu item Tools > Decompose by ICA -% Use first line for speed (but you must install the Picard plugin) -% The -1 for the number of channel is to account for matrix rank -% decrease due to average reference -if exist('picard') % faster - EEG = pop_runica( EEG , 'picard', 'maxiter', 500, 'pca', -1);% Do not forget to add 'pca',-1 in command line options to account for average reference +%% REMOVING BAD TIME PERIODS AND BURST ARTIFACTS + +% Now detect and remove bad time periods (when artifacts occurred) +% What are burst artifacts? These are sudden, large-amplitude events like: +% - Eye blinks +% - Muscle movements +% - Head movements +% - Electrical interference +% The algorithm finds time periods where the signal is too different from normal +% and marks them for removal. We'll remove them after ICA (next step) +EEG = clean_artifacts( EEG, 'Highpass', 'off',... % Don't apply high-pass filter + 'ChannelCriterion', 'off',... % Don't detect more bad channels + 'LineNoiseCriterion', 'off',... % Don't detect line noise + 'BurstCriterion', 30,... % Threshold for detecting burst artifacts + 'WindowCriterion',0.3); % Threshold for detecting bad time windows + +%% RUNNING INDEPENDENT COMPONENT ANALYSIS (ICA) + +% Separate the brain signals into independent components +% What is ICA? It's a mathematical technique that separates mixed signals into their sources. +% Think of it like separating voices in a crowded room - each component represents a different source: +% - Some components = brain activity (what we want to keep) +% - Some components = eye blinks, muscle activity, heart beats, line noise (what we want to remove) +% The '-1' accounts for the fact that re-referencing reduces the number of independent signals by 1 +% You can also do this via the menu: Tools > Decompose by ICA +if exist('picard') % Check if Picard plugin is installed (faster algorithm) + EEG = pop_runica( EEG , 'picard', 'maxiter', 500, 'pca', -1); % Use Picard if available (faster) else - EEG = pop_runica( EEG , 'runica', 'extended',1, 'pca', -1); + EEG = pop_runica( EEG , 'runica', 'extended',1, 'pca', -1); % Use standard ICA algorithm end -%% automatically classify Independent Components using IC Label -% use menu item Tools > Classify components using ICLabel > Label components -% EEG only, MEG would be possible if ICLabel is retrained with MEG -% components instead of EEG components. Here we remove the component, but -% when doing group analysis, it is better to do that at the STUDY level -if ~contains(EEG.chanlocs(1).type, 'meg') - EEG = iclabel(EEG); +%% AUTOMATICALLY IDENTIFYING AND REMOVING ARTIFACT COMPONENTS + +% Use ICLabel to automatically classify each ICA component +% What is ICLabel? It's a machine learning tool that looks at each component and classifies it as: +% - Brain activity (keep this!) +% - Eye blinks (remove) +% - Muscle activity (remove) +% - Heart activity (remove) +% - Line noise (remove) +% - Channel noise (remove) +% - Other artifacts (remove) +% Note: This only works for EEG data. For MEG, you would need to manually inspect components. +% You can also do this via the menu: Tools > Classify components using ICLabel > Label components +if ~contains(EEG.chanlocs(1).type, 'meg') % Only for EEG (not MEG) + % Classify each component using machine learning + EEG = iclabel(EEG); + + % Set thresholds for automatic removal: + % [Brain threshold; Eye threshold; Muscle threshold; Heart threshold; Line noise; Channel noise; Other] + % 0.9 means: if component is 90% likely to be that type, flag it for removal + % NaN means: don't automatically remove based on that category EEG = pop_icflag( EEG,[NaN NaN;0.9 1;0.9 1;NaN NaN;NaN NaN;NaN NaN;NaN NaN]); - EEG = pop_subcomp(EEG, [], 0); % remove pre-flagged bad components + + % Remove the components that were flagged as artifacts (eye, muscle, etc.) + % This subtracts the artifact components from the data, leaving clean brain signals + EEG = pop_subcomp(EEG, [], 0); end -%% Save dataset -% use menu item File > Save current dataset as +%% SAVE THE PREPROCESSED DATASET + +% Save the cleaned and preprocessed dataset +% Now that we've removed bad channels, filtered noise, and removed artifacts, +% we save the clean data for further analysis (like creating epochs and computing ERPs) +% You can also do this via the menu: File > Save current dataset as EEG = pop_saveset( EEG,'filename', 'wh_S01_run_01_preprocessing_data_session_1_out.set','filepath',path2data); diff --git a/script_03_epochs_and_erp.m b/script_03_epochs_and_erp.m index c495e45..249909f 100644 --- a/script_03_epochs_and_erp.m +++ b/script_03_epochs_and_erp.m @@ -1,121 +1,188 @@ % Wakeman & Henson Data analysis: Epochs and ERP analysis. % % Authors: Arnaud Delorme, Ramon Martinez-Cancino, Johanna Wagner, Romain Grandchamp - -% Clearing all is recommended to avoid variable not being erased between calls +% +% This script creates epochs (time windows around events) and computes Event-Related Potentials (ERPs). +% What are epochs? They are time windows (e.g., -1 to +2 seconds) around each event (like when a face was shown). +% What are ERPs? They are the average brain response across many trials. By averaging, we can see +% the brain's consistent response to a stimulus, even though individual trials are noisy. +% This script analyzes brain responses to three types of faces: Famous, Unfamiliar, and Scrambled. + +%% +% Clearing all variables is recommended to avoid leftover variables from previous runs clear; -% Path to data below. Using relative paths so no need to update. -path2data = fullfile(pwd,'ds000117_pruned', 'derivatives', 'meg_derivatives', 'sub-01', 'ses-meg/', 'meg/'); % Path to data +% Set the path to where your data files are located +% This loads the preprocessed dataset that was created by script_02_preprocess_data.m +path2data = fullfile(pwd,'ds000117_pruned', 'derivatives', 'meg_derivatives', 'sub-01', 'ses-meg/', 'meg/'); filename = 'wh_S01_run_01_preprocessing_data_session_1_out.set'; -% Start EEGLAB +% Start EEGLAB - this opens the EEGLAB interface and initializes the workspace [ALLEEG, EEG, CURRENTSET] = eeglab; -% Loading data -EEG = pop_loadset('filename', filename,'filepath',path2data) +% Load the preprocessed dataset +EEG = pop_loadset('filename', filename,'filepath',path2data); + +% Add the loaded dataset to the ALLEEG structure (which can hold multiple datasets) +[ALLEEG, EEG, CURRENTSET] = pop_newset(ALLEEG, EEG, 1); -[ALLEEG EEG CURRENTSET] = pop_newset(ALLEEG, EEG, 1); -%% ERP analysis +%% CREATING EPOCHS (TIME WINDOWS AROUND EVENTS) -%% Extract event-locked trials using events listed in 'eventlist' +% Extract time windows around each event to create epochs (trials) +% What are epochs? They are segments of data centered on an event (like when a face appeared). +% The time window [-1 2] means: 1 second BEFORE the event to 2 seconds AFTER the event. +% We create separate datasets for each condition (Famous, Unfamiliar, Scrambled) so we can +% compare how the brain responds differently to each type of face. ALLEEG(2) = pop_epoch( ALLEEG(1), {'Famous'}, [-1 2], 'newname', 'Famous Epoched', 'epochinfo', 'yes'); ALLEEG(3) = pop_epoch( ALLEEG(1), {'Unfamiliar'}, [-1 2], 'newname', 'Unfamiliar Epoched', 'epochinfo', 'yes'); ALLEEG(4) = pop_epoch( ALLEEG(1), {'Scrambled'}, [-1 2], 'newname', 'Scrambled Epoched', 'epochinfo', 'yes'); -%% Perform baseline correction +%% BASELINE CORRECTION + +% Remove the baseline (pre-stimulus activity) from each epoch +% What is baseline correction? Before the event happens, there's some baseline brain activity. +% We subtract this baseline from the entire epoch so that time 0 (when the face appears) becomes our reference. +% This makes it easier to see how the brain responds to the stimulus. +% The baseline period is [-1000 0] milliseconds (1 second before the event to the event itself). ALLEEG(2) = pop_rmbase(ALLEEG(2), [-1000 0]); ALLEEG(3) = pop_rmbase(ALLEEG(3), [-1000 0]); ALLEEG(4) = pop_rmbase(ALLEEG(4), [-1000 0]); -%% Clean data by rejecting epochs. +%% REJECTING BAD EPOCHS + +% Remove epochs (trials) that have too much noise or artifacts +% What are bad epochs? Some trials might have large artifacts (eye blinks, muscle activity, etc.) +% that weren't removed during preprocessing. We detect these by looking for epochs where +% the voltage goes outside a normal range (-400 to +400 microvolts). +% If any channel in an epoch exceeds these limits, the entire epoch is removed. [ALLEEG(2), rejindx] = pop_eegthresh(ALLEEG(2), 1, 1:ALLEEG(2).nbchan, -400, 400, ALLEEG(2).xmin, ALLEEG(2).xmax, 0, 1); [ALLEEG(3), rejindx] = pop_eegthresh(ALLEEG(3), 1, 1:ALLEEG(3).nbchan, -400, 400, ALLEEG(3).xmin, ALLEEG(3).xmax, 0, 1); [ALLEEG(4), rejindx] = pop_eegthresh(ALLEEG(4), 1, 1:ALLEEG(4).nbchan, -400, 400, ALLEEG(4).xmin, ALLEEG(4).xmax, 0, 1); -%% Save dataset +%% SAVE THE EPOCHED DATASETS + +% Save the epoched (trial-based) datasets for each condition +% These files contain all the individual trials, ready for computing ERPs EEG_famous = pop_saveset( ALLEEG(2),'filename', 'wh_S01_run_01_ERP_Analysis_Session_2_famous_out.set','filepath',path2data); EEG_unfamiliar = pop_saveset( ALLEEG(3),'filename', 'wh_S01_run_01_ERP_Analysis_Session_2_unfamiliar_out.set','filepath',path2data); EEG_scrambled = pop_saveset( ALLEEG(4),'filename', 'wh_S01_run_01_ERP_Analysis_Session_2_scrambled_out.set','filepath',path2data); -%% ---------------------- -%% BELOW IS PLOTTING ONLY -%% ---------------------- +%% ======================================================================== +%% BELOW IS VISUALIZATION AND PLOTTING ONLY +%% ======================================================================== +%% These sections create various plots to visualize the ERP results. +%% The ERPs are computed automatically when plotting (by averaging all trials). -%% plot ERP scalp distribution +%% PLOTTING ERP TIME COURSE AND SCALP DISTRIBUTION + +% Create plots showing how the ERP changes over time and where on the scalp it's strongest +% These plots show the average brain response (ERP) from -100 to 600 ms after the face appeared +% The scalp maps show the distribution of activity across the head at different time points figure; pop_timtopo(ALLEEG(2), [-100 600], [NaN], 'ERP data and scalp maps of Famous Epoched'); figure; pop_timtopo(ALLEEG(3), [-100 600], [NaN], 'ERP data and scalp maps of Unfamiliar Epoched'); figure; pop_timtopo(ALLEEG(4), [-100 600], [NaN], 'ERP data and scalp maps of Scrambled Epoched'); -%% Identify Brain ICs using IC Label classification results +%% OPTIONAL: KEEP ONLY BRAIN COMPONENTS (FOR EEG DATA) + +% If ICLabel was used during preprocessing, we can keep only the brain components +% This removes any remaining artifacts that might have been missed +% Note: This only works if the original dataset had ICA components classified if isfield(ALLEEG(1).etc, 'ic_classification') - [M,I] = max(ALLEEG(1).etc.ic_classification.ICLabel.classifications,[],2); % Use max prob for classification + % Find which ICA components were classified as "Brain" activity + [M,I] = max(ALLEEG(1).etc.ic_classification.ICLabel.classifications,[],2); Brain_comps = find(I == find(strcmp(ALLEEG(1).etc.ic_classification.ICLabel.classes, 'Brain'))); - %% Subtract artefactual components from the EEG + % Keep only the brain components, removing artifact components + % This creates cleaner ERPs by removing any remaining eye blinks, muscle activity, etc. ALLEEG(2) = pop_subcomp( ALLEEG(2), Brain_comps, 0, 1); ALLEEG(3) = pop_subcomp( ALLEEG(3), Brain_comps, 0, 1); ALLEEG(4) = pop_subcomp( ALLEEG(4), Brain_comps, 0, 1); end -%% Rename datasets +%% RENAMING DATASETS FOR CLEARER PLOTS + +% Give the datasets shorter, clearer names for the plots ALLEEG(2) = pop_editset(ALLEEG(2), 'setname', 'Famous', 'run', []); ALLEEG(3) = pop_editset(ALLEEG(3), 'setname', 'Unfamiliar', 'run', []); ALLEEG(4) = pop_editset(ALLEEG(4), 'setname', 'Scrambled', 'run', []); -%% plot ERP scalp distribution +%% PLOTTING ERP SCALP DISTRIBUTION (AFTER CLEANING) + +% Plot the ERP time course and scalp distribution again, now with cleaner data figure; pop_timtopo(ALLEEG(2), [-100 600], [NaN], 'Famous'); figure; pop_timtopo(ALLEEG(3), [-100 600], [NaN], 'Unfamiliar'); figure; pop_timtopo(ALLEEG(4), [-100 600], [NaN], 'Scrambled'); -%% plot ERP scalp distribution at each ERP peak +%% PLOTTING ERP SCALP DISTRIBUTION AT SPECIFIC TIME POINTS + +% Plot scalp maps at specific time points where ERP peaks typically occur +% The times [120 170 250] milliseconds correspond to common ERP components: +% - 120 ms: P1 (early visual response) +% - 170 ms: N170 (face-selective response) +% - 250 ms: P2 (later processing) figure; pop_timtopo(ALLEEG(2), [-100 600], [120 170 250], 'Famous'); figure; pop_timtopo(ALLEEG(3), [-100 600], [120 170 250], 'Unfamiliar'); figure; pop_timtopo(ALLEEG(4), [-100 600], [120 170 250], 'Scrambled'); -%% Visualize channel ERPs in 2D +%% PLOTTING TOPOGRAPHIC MAPS AT MULTIPLE TIME POINTS + +% Create 2D topographic maps showing activity distribution across the scalp +% These plots show how activity changes over time (every 25 ms from 25 to 300 ms) pop_topoplot(ALLEEG(2), 1, [25:25:300] ,'Famous',[3 4] ,0,'electrodes','on'); pop_topoplot(ALLEEG(3), 1, [25:25:300] ,'Unfamiliar',[3 4] ,0,'electrodes','on'); pop_topoplot(ALLEEG(4), 1, [25:25:300] ,'Scrambled',[3 4] ,0,'electrodes','on'); -%% Plot channel ERPs in topographic array +%% PLOTTING ALL CHANNELS IN A TOPOGRAPHIC ARRAY + +% Plot ERPs from all channels arranged in a topographic layout +% This shows the ERP waveform at each electrode position on the head figure; pop_plottopo(ALLEEG(2), [1:EEG.nbchan] , 'Famous', 0, 'ydir',1); figure; pop_plottopo(ALLEEG(3), [1:EEG.nbchan] , 'Unfamiliar', 0, 'ydir',1); figure; pop_plottopo(ALLEEG(4), [1:EEG.nbchan] , 'Scrambled', 0, 'ydir',1); -%% plot average ERPs for each condition with standard deviation +%% PLOTTING AVERAGE ERPs WITH ERROR BARS (STANDARD DEVIATION) + +% Plot the average ERP for each condition with shaded error bars showing variability +% This helps visualize both the average response and how consistent it is across trials -% find channel index of eeg065 +% Find which channel to plot (looking for channel 'EEG065', or use first channel if not found) Chanind = find(strcmp({ALLEEG(2).chanlocs.labels},'EEG065')); if isempty(Chanind) - Chanind = 1; + Chanind = 1; % Use first channel if EEG065 doesn't exist end -% create timevector for plotting -[val, indL] = min(abs(ALLEEG(2).times+200)); %get timepoints for -200 and 800 Latencies -[val, indU] = min(abs(ALLEEG(2).times-800)); -timevec = ALLEEG(2).times(indL:indU); % create timevector +% Create a time vector for plotting (from -200 ms to 800 ms after the event) +% This focuses on the time window where most ERP activity occurs +[val, indL] = min(abs(ALLEEG(2).times+200)); % Find time point closest to -200 ms +[val, indU] = min(abs(ALLEEG(2).times-800)); % Find time point closest to 800 ms +timevec = ALLEEG(2).times(indL:indU); % Extract time vector for this range -% create datavectors for plotting each condition -av_datavecF = mean(ALLEEG(2).data(Chanind,indL:indU,:),3); % average -std_datavecF = std(ALLEEG(2).data(Chanind,indL:indU,:),1,3); % standard deviation +% Calculate the average ERP and standard deviation for Famous faces +% The mean is computed across all trials (dimension 3) to get the average ERP +% The standard deviation shows how much individual trials vary from the average +av_datavecF = mean(ALLEEG(2).data(Chanind,indL:indU,:),3); % Average across trials +std_datavecF = std(ALLEEG(2).data(Chanind,indL:indU,:),1,3); % Standard deviation across trials +% Plot Famous faces ERP with shaded error region figure; -X2 = [[timevec],fliplr([timevec])]; %#create continuous x value array for plotting -Y2 = [av_datavecF-std_datavecF,fliplr(av_datavecF+std_datavecF)]; %#create y values for out and then back -fill(X2,Y2,[153/255 204/255 255/255]); +% Create coordinates for filled area (error bars): go forward in time, then backward +X2 = [[timevec],fliplr([timevec])]; % X coordinates: time forward, then time backward +Y2 = [av_datavecF-std_datavecF,fliplr(av_datavecF+std_datavecF)]; % Y: mean - std, then mean + std +fill(X2,Y2,[153/255 204/255 255/255]); % Fill the area with light blue (shows ±1 standard deviation) hold on -plot(timevec,av_datavecF, 'b', 'LineWidth',2) -xline(0, 'LineWidth',2) -yline(0, 'LineWidth',2) -xlabel('Latency ms') -ylabel('mu Volt') -title([ 'famous faces channel ' EEG.chanlocs(Chanind).labels ]); +plot(timevec,av_datavecF, 'b', 'LineWidth',2) % Plot the average ERP line +xline(0, 'LineWidth',2) % Vertical line at time 0 (when face appeared) +yline(0, 'LineWidth',2) % Horizontal line at 0 voltage +xlabel('Time (milliseconds)') +ylabel('Amplitude (microvolts)') +title([ 'Famous faces - Channel ' EEG.chanlocs(Chanind).labels ]); set(gca, 'FontSize', 15) -av_datavecU = mean(ALLEEG(3).data(Chanind,indL:indU,:),3); % average -std_datavecU = std(ALLEEG(3).data(Chanind,indL:indU,:),1,3); % standard deviation +% Calculate average ERP and standard deviation for Unfamiliar faces +av_datavecU = mean(ALLEEG(3).data(Chanind,indL:indU,:),3); +std_datavecU = std(ALLEEG(3).data(Chanind,indL:indU,:),1,3); +% Plot Unfamiliar faces ERP with shaded error region figure; X2 = [[timevec],fliplr([timevec])]; %#create continuous x value array for plotting Y2 = [av_datavecU-std_datavecU,fliplr(av_datavecU+std_datavecU)]; %#create y values for out and then back @@ -124,49 +191,65 @@ plot(timevec,av_datavecU, 'b', 'LineWidth',2) xline(0, 'LineWidth',2) yline(0, 'LineWidth',2) -xlabel('Latency ms') -ylabel('mu Volt') -title([ 'unfamiliar faces channel ' EEG.chanlocs(Chanind).labels ]); +xlabel('Time (milliseconds)') +ylabel('Amplitude (microvolts)') +title([ 'Unfamiliar faces - Channel ' EEG.chanlocs(Chanind).labels ]); set(gca, 'FontSize', 15) -av_datavecS = mean(ALLEEG(4).data(Chanind,indL:indU,:),3); % average -std_datavecS = std(ALLEEG(4).data(Chanind,indL:indU,:),1,3); % standard deviation +% Calculate average ERP and standard deviation for Scrambled faces +av_datavecS = mean(ALLEEG(4).data(Chanind,indL:indU,:),3); +std_datavecS = std(ALLEEG(4).data(Chanind,indL:indU,:),1,3); +% Plot Scrambled faces ERP with shaded error region figure; -X2 = [[timevec],fliplr([timevec])]; %#create continuous x value array for plotting -Y2 = [av_datavecS-std_datavecS,fliplr(av_datavecS+std_datavecS)]; %#create y values for out and then back +X2 = [[timevec],fliplr([timevec])]; +Y2 = [av_datavecS-std_datavecS,fliplr(av_datavecS+std_datavecS)]; fill(X2,Y2,[153/255 204/255 255/255]); hold on plot(timevec,av_datavecS, 'b', 'LineWidth',2) xline(0, 'LineWidth',2) yline(0, 'LineWidth',2) -xlabel('Latency ms') -ylabel('mu Volt') -title([ 'scrambled faces channel ' EEG.chanlocs(Chanind).labels ]); +xlabel('Time (milliseconds)') +ylabel('Amplitude (microvolts)') +title([ 'Scrambled faces - Channel ' EEG.chanlocs(Chanind).labels ]); set(gca, 'FontSize', 15) -%% plot superimposed ERPs +%% PLOTTING ALL CONDITIONS TOGETHER (COMPARISON PLOT) -figure;plot(timevec,av_datavecF, 'LineWidth',2, 'color', 'r'); hold on -plot(timevec,av_datavecU, 'LineWidth',2, 'color', 'b') -plot(timevec,av_datavecS, 'LineWidth',2, 'color', 'g') +% Plot all three conditions on the same graph to compare them +% This makes it easy to see differences between how the brain responds to different face types +figure; +plot(timevec,av_datavecF, 'LineWidth',2, 'color', 'r'); hold on % Famous = red +plot(timevec,av_datavecU, 'LineWidth',2, 'color', 'b') % Unfamiliar = blue +plot(timevec,av_datavecS, 'LineWidth',2, 'color', 'g') % Scrambled = green +% Add shaded error regions for each condition (semi-transparent) fillcurves(timevec,av_datavecF-std_datavecF,av_datavecF+std_datavecF, 'r', 0.2); fillcurves(timevec,av_datavecU-std_datavecU,av_datavecU+std_datavecU, 'b', 0.2); fillcurves(timevec,av_datavecS-std_datavecS,av_datavecS+std_datavecS, 'g', 0.2); -xline(0, 'LineWidth',2) -yline(0, 'LineWidth',2) -xlabel('Latency ms') -ylabel('mu Volt') -legend('famous', 'unfamiliar', 'scrambled') +xline(0, 'LineWidth',2) % Vertical line at time 0 +yline(0, 'LineWidth',2) % Horizontal line at 0 voltage +xlabel('Time (milliseconds)') +ylabel('Amplitude (microvolts)') +legend('Famous', 'Unfamiliar', 'Scrambled') set(gca, 'FontSize', 15) -title([ 'face types for channel ' EEG.chanlocs(Chanind).labels ]); +title([ 'Comparison of face types - Channel ' EEG.chanlocs(Chanind).labels ]); + +%% CREATING ERPIMAGE PLOTS -%% ERPimage +% ERPimage shows each individual trial as a row, sorted by some criterion +% What is an ERPimage? It's a visualization where: +% - Each row = one trial (epoch) +% - Color = voltage amplitude (red = positive, blue = negative) +% - Time = horizontal axis +% - The average ERP is shown at the bottom +% This helps you see trial-by-trial variability and whether the ERP is consistent figure; pop_erpimage(ALLEEG(2),1, [Chanind],[[]],EEG.chanlocs(Chanind).labels,3,1,{},[],'' ,'yerplabel','\muV','erp','on','limits',[-100 1200 NaN NaN NaN NaN NaN NaN] ,'cbar','on','topo', { [Chanind] EEG.chanlocs EEG.chaninfo } ); figure; pop_erpimage(ALLEEG(3),1, [Chanind],[[]],EEG.chanlocs(Chanind).labels,3,1,{},[],'' ,'yerplabel','\muV','erp','on','limits',[-100 1200 NaN NaN NaN NaN NaN NaN] ,'cbar','on','topo', { [Chanind] EEG.chanlocs EEG.chaninfo } ); figure; pop_erpimage(ALLEEG(4),1, [Chanind],[[]],EEG.chanlocs(Chanind).labels,3,1,{},[],'' ,'yerplabel','\muV','erp','on','limits',[-100 1200 NaN NaN NaN NaN NaN NaN] ,'cbar','on','topo', { [Chanind] EEG.chanlocs EEG.chaninfo } ); -% sort by event latency (when there are enough of left_nonsym and right_sym events +% Optional: Sort trials by button press latency (how quickly the participant responded) +% This can reveal if faster responses are associated with different brain activity +% Uncomment these lines if you want to see trials sorted by response time: % figure; pop_erpimage(ALLEEG(2),1, [Chanind],[[]],EEG.chanlocs(Chanind).labels,3,1,{ 'left_nonsym' 'right_sym'},[],'latency' ,'yerplabel','\muV','erp','on','limits',[-100 1200 NaN NaN NaN NaN NaN NaN] ,'cbar','on','topo', { [Chanind] EEG.chanlocs EEG.chaninfo } ); % figure; pop_erpimage(ALLEEG(3),1, [Chanind],[[]],EEG.chanlocs(Chanind).labels,3,1,{ 'left_nonsym' 'right_sym'},[],'latency' ,'yerplabel','\muV','erp','on','limits',[-100 1200 NaN NaN NaN NaN NaN NaN] ,'cbar','on','topo', { [Chanind] EEG.chanlocs EEG.chaninfo } ); % figure; pop_erpimage(ALLEEG(4),1, [Chanind],[[]],EEG.chanlocs(Chanind).labels,3,1,{ 'left_nonsym' 'right_sym'},[],'latency' ,'yerplabel','\muV','erp','on','limits',[-100 1200 NaN NaN NaN NaN NaN NaN] ,'cbar','on','topo', { [Chanind] EEG.chanlocs EEG.chaninfo } ); diff --git a/slides/README.md b/slides/README.md deleted file mode 100644 index 1a43ad8..0000000 --- a/slides/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This folder contains presentations. See the README.md file in the parent folder for more details. - -Make sure that the size of each presentation is below 100Mb or GitHub will reject them. diff --git a/slides/Session_1_single_subject_preprocessing/Session_1.1_overview.pptx b/slides/Session_1_single_subject_preprocessing/Session_1.1_overview.pptx deleted file mode 100644 index 34b097b..0000000 Binary files a/slides/Session_1_single_subject_preprocessing/Session_1.1_overview.pptx and /dev/null differ diff --git a/slides/Session_1_single_subject_preprocessing/Session_1.2_BIDS_and_NEMAR_data_repository.pptx b/slides/Session_1_single_subject_preprocessing/Session_1.2_BIDS_and_NEMAR_data_repository.pptx deleted file mode 100644 index 934e0f8..0000000 Binary files a/slides/Session_1_single_subject_preprocessing/Session_1.2_BIDS_and_NEMAR_data_repository.pptx and /dev/null differ diff --git a/slides/Session_1_single_subject_preprocessing/Session_1.3_preprocessing.pptx b/slides/Session_1_single_subject_preprocessing/Session_1.3_preprocessing.pptx deleted file mode 100644 index 00a52e6..0000000 Binary files a/slides/Session_1_single_subject_preprocessing/Session_1.3_preprocessing.pptx and /dev/null differ diff --git a/slides/Session_2_ERP_and_time_frequency/Session_2.1_ERP.pptx b/slides/Session_2_ERP_and_time_frequency/Session_2.1_ERP.pptx deleted file mode 100755 index d8ea5a8..0000000 Binary files a/slides/Session_2_ERP_and_time_frequency/Session_2.1_ERP.pptx and /dev/null differ diff --git a/slides/Session_2_ERP_and_time_frequency/Session_2.2_TimeFrequencyAnalysis.pptx b/slides/Session_2_ERP_and_time_frequency/Session_2.2_TimeFrequencyAnalysis.pptx deleted file mode 100755 index 2961f3c..0000000 Binary files a/slides/Session_2_ERP_and_time_frequency/Session_2.2_TimeFrequencyAnalysis.pptx and /dev/null differ diff --git a/slides/Session_3_source_localization_and_connectivity/Session_3.1_Source_Localization_ICA.pptx b/slides/Session_3_source_localization_and_connectivity/Session_3.1_Source_Localization_ICA.pptx deleted file mode 100644 index 65380d1..0000000 Binary files a/slides/Session_3_source_localization_and_connectivity/Session_3.1_Source_Localization_ICA.pptx and /dev/null differ diff --git a/slides/Session_3_source_localization_and_connectivity/Session_3.2_connectivity.pptx b/slides/Session_3_source_localization_and_connectivity/Session_3.2_connectivity.pptx deleted file mode 100644 index c041ce8..0000000 Binary files a/slides/Session_3_source_localization_and_connectivity/Session_3.2_connectivity.pptx and /dev/null differ diff --git a/slides/Session_3_source_localization_and_connectivity/Session_3.2_connectivity_brain_movie.mov b/slides/Session_3_source_localization_and_connectivity/Session_3.2_connectivity_brain_movie.mov deleted file mode 100644 index 1c9e836..0000000 Binary files a/slides/Session_3_source_localization_and_connectivity/Session_3.2_connectivity_brain_movie.mov and /dev/null differ diff --git a/slides/Session_4_independent_component_clustering/Session_4_IC_Clustering.pptx b/slides/Session_4_independent_component_clustering/Session_4_IC_Clustering.pptx deleted file mode 100755 index 4926043..0000000 Binary files a/slides/Session_4_independent_component_clustering/Session_4_IC_Clustering.pptx and /dev/null differ diff --git a/slides/Session_5_univariate_statistics/Session_5.1_univariate_statistics.pptx b/slides/Session_5_univariate_statistics/Session_5.1_univariate_statistics.pptx deleted file mode 100644 index 18d3a5f..0000000 Binary files a/slides/Session_5_univariate_statistics/Session_5.1_univariate_statistics.pptx and /dev/null differ diff --git a/slides/Session_5_univariate_statistics/Session_5.2_movie_1.mp4 b/slides/Session_5_univariate_statistics/Session_5.2_movie_1.mp4 deleted file mode 100644 index 97dd48c..0000000 Binary files a/slides/Session_5_univariate_statistics/Session_5.2_movie_1.mp4 and /dev/null differ diff --git a/slides/Session_5_univariate_statistics/Session_5.2_movie_2.mp4 b/slides/Session_5_univariate_statistics/Session_5.2_movie_2.mp4 deleted file mode 100644 index b451380..0000000 Binary files a/slides/Session_5_univariate_statistics/Session_5.2_movie_2.mp4 and /dev/null differ diff --git a/slides/Session_5_univariate_statistics/Session_5.2_univariate_statistics_practicum.pptx b/slides/Session_5_univariate_statistics/Session_5.2_univariate_statistics_practicum.pptx deleted file mode 100644 index e52409d..0000000 Binary files a/slides/Session_5_univariate_statistics/Session_5.2_univariate_statistics_practicum.pptx and /dev/null differ