# Advanced NI Data Analysis Term Projects > 2. Preprocess the Go/NoGo EEG data and write a program using wavelet or filter- Hilbert transform to find the instantaneous phase of different channels. Then, try to compute the difference between channels for finding the phase-based connectivity by computing phase vector differences and testing the non-uniformity of the differences so as to test (1) any pair(s) of channels manifesting significant connectivity differences across different proportions of Go:NoGo trials (8:2, 5:5, and 2:8) (120 points max.). ## Result ``` sig_pairs = { 'O2', 'VEOG'; 'OZ', 'VEOG'; 'PZ', 'VEOG'; 'C4', 'CZ'; 'TP8', 'F3'; 'P7', 'FP2'; 'CPZ', 'T7'; 'FC4', 'F3'; 'F4', 'F7'; 'T7', 'HEOG'; 'FP2', 'VEOG' }; ``` These pairs show significant variation in PLV (phase-locking value) between Go and NoGo conditions across task probability contexts (2:8, 5:5, 8:2), with effect sizes (Kendall’s W) above 0.3, indicating at least moderate effect strength. 1. Fronto-Ocular Interactions (VEOG Involvement) O2–VEOG, OZ–VEOG, PZ–VEOG, FP2–VEOG These suggest tight coupling between posterior visual/parietal areas and ocular movements. May reflect: Enhanced top-down visual attention during inhibitory demands. Eye-movement suppression associated with response inhibition. 2. Central-Motor Integration C4–CZ Both sites are over sensorimotor cortex. Reflects coordinated motor preparation or inhibition. 3. Fronto-Temporal Coupling TP8–F3, CPZ–T7, FC4–F3 Involves executive and auditory/attentional integration areas. TP8 and T7 (temporal-parietal junction) often support attention reorienting; F3 and FC4 (left frontal) support inhibitory control. 4. Fronto-Parietal and Prefrontal Interactions P7–FP2, F4–F7 Frontal and parietal regions are core to executive attention and response regulation. F4–F7 may reflect lateralized frontal control dynamics. FP2 (right prefrontal) is especially relevant to top-down control over attention and inhibition. 5. Lateral Fronto-Ocular Connection T7–HEOG T7 (left temporal) and HEOG (horizontal eye movement). May reflect coordinated visual scanning or inhibition of gaze shifts under differing expectations. Could also relate to speech-related motor planning under increased load. ## ANALYSIS PROCESS - [ ] s1_28 55 82 = Go:Nogo = 2:8; 5:5; 8:2 - [ ] **Sample: SUBJ 1-1(s1_28)** ### EEG Loading - s1_28 = Go:Nogo = 2:8 ![RawSignal(ch=O2)](https://hackmd.io/_uploads/r1IVEjhMlx.jpg) ![RawSignal(ch=P4)](https://hackmd.io/_uploads/r1vEVs3Gel.jpg) ![RawSignal(ch=T8)](https://hackmd.io/_uploads/BkPVEinGxe.jpg) ``` % Add EEGLAB to your MATLAB path. This line is fine. addpath(genpath('/Users/nanxi/Documents/MATLAB/eeglab2025.0.0')); % Launch EEGLAB GUI and load EEGLAB functions. This is also fine. eeglab % Load your EEG data. This line is fine. EEG = pop_loadcnt('/Users/nanxi/Desktop/!!NYCU/。課(待更新讀與筆記)/。神經科學學程/神經資訊/advanced neuro-image /GNG_EEG/prob28/s1_28_mo.cnt', 'dataformat', 'auto'); % --- NEW CODE FOR CHANNEL LOCATIONS --- % 1. Define the base path to your EEGLAB installation % Make sure this path is accurate for your setup. eeglab_base_path = '/Users/nanxi/Documents/MATLAB/eeglab2025.0.0'; % 2. Construct the full path to the standard 10-20 system channel file % This file is typically found within the 'plugins/dipfit/standard_BEM/elec/' folder chanlocs_file = fullfile(eeglab_base_path, 'plugins', 'dipfit', 'standard_BEM', 'elec', 'standard_1020.elc'); % 3. IMPORTANT: Verify the channel location file exists if ~exist(chanlocs_file, 'file') error(['Channel location file not found: ', chanlocs_file, ... newline, 'Please check if EEGLAB is correctly installed and the path is accurate.']); end % 4. Load the channel locations into your EEG dataset % 'loadfile', 'on' instructs pop_readlocs to actually load the specified file. % 'filetype', 'autodetect' helps EEGLAB recognize the file format. EEG = pop_chanedit(EEG, 'lookup', '/Users/nanxi/Documents/MATLAB/eeglab2025.0.0/sample_locs/Standard-10-20-Cap81.locs'); % Display structure field whos EEG % Key fields of interest: EEG.srate % Sampling rate EEG.nbchan % Number of channels EEG.trials % Number of trials (1 if continuous) EEG.pnts % Number of time points per trial EEG.chanlocs % Channel labels and coordinates EEG.event % Event markers figure; ch = 1; plot(EEG.times, EEG.data(ch,:)); % channel 1 xlabel('Time (ms)'); ylabel('Amplitude (μV)'); title(['Channel: ' EEG.chanlocs(ch).labels]); figure; ch = 5; plot(EEG.times, EEG.data(ch,:)); % channel 1 xlabel('Time (ms)'); ylabel('Amplitude (μV)'); title(['Channel: ' EEG.chanlocs(ch).labels]); figure; ch = 10; plot(EEG.times, EEG.data(ch,:)); % channel 1 xlabel('Time (ms)'); ylabel('Amplitude (μV)'); title(['Channel: ' EEG.chanlocs(ch).labels]); {EEG.chanlocs.labels} ``` ### Preprocessing - DOWN Sr to 250Hz - Band Passing: pop_eegfilt (eeglab) - ICA Label: erase the **red** ones; **red** >0.6 ![截屏2025-06-03 21.32.09](https://hackmd.io/_uploads/rkejNo3Gxe.png) ![PreprocessedSignal(ch=O2)](https://hackmd.io/_uploads/ryM5Nj2Gxe.jpg) ![PreprocessedSignal(ch=P4)](https://hackmd.io/_uploads/BJGqNj3zel.jpg) ![PreprocessedSignal(ch=T8)](https://hackmd.io/_uploads/rJGcVihzgg.jpg) ``` % downsampling EEG = pop_resample(EEG, 250) % bandpass EEG = pop_eegfiltnew(EEG, 3, 20, [], 0, [], 0); % FIR bandpass, zero-phase % ICA EEG = pop_runica(EEG, 'extended',1,'interupt','on'); % --- NEW: Automatic Component Detection using ICLabel --- % 1. Run ICLabel to classify components EEG = iclabel(EEG); % This adds classification probabilities to EEG.etc.ic_classification.ICLabel % 2. Flag components for rejection based on ICLabel probabilities % You set thresholds for what to reject. Common thresholds: % - Reject if 'Eye' probability > 0.6 (60%) AND 'Brain' probability < 0.4 (40%) % - Reject if 'Muscle' probability > 0.6 % - Reject if 'Heart' probability > 0.6 % - Reject if 'Line Noise' probability > 0.6 % % These thresholds are customizable. You might need to adjust them based on your data. % pop_icflag will flag components based on these criteria and store them in EEG.reject.gcompreject % % The syntax for pop_icflag is: % pop_icflag(EEG, class_names, thresholds, flags_to_reject, 'defaultreject') % flags_to_reject: 'auto' means it will reject components flagged by ICLabel. % 'defaultreject' means it will use ICLabel's recommended default thresholds. % Let's try with default settings first: EEG = pop_icflag(EEG, [NaN NaN; % Brain: no threshold for flagging brain as artifact 0.6 1; % Eye: if Eye probability > 80% (and Brain < 20% by default if not specified) 0.6 1; % Muscle: if Muscle probability > 80% 0.6 1; % Heart: if Heart probability > 80% 0.6 1; % Line Noise: if Line Noise probability > 80% 0.6 1; % Channel Noise: if Channel Noise probability > 80% NaN NaN;% Other: no threshold for flagging 'Other' as artifact ]); % 3. Review flagged components (Highly Recommended) % Although automated, it's good practice to visually review the flagged components. % pop_selectcomps will now open with the automatically flagged components already highlighted (usually in red). % You can then manually unflag any components you believe are brain activity, or flag others that ICLabel missed. EEG = pop_selectcomps(EEG, 1:EEG.nbchan); % Ensure to display all existing components (e.g., 1:32) % 4. Remove the selected components EEG = pop_subcomp(EEG, [], 0); % remove selected components (those flagged manually or by ICLabel) figure; ch = 10; plot(EEG.times, EEG.data(ch,:)); % channel 1 xlabel('Time (ms)'); ylabel('Amplitude (μV)'); title(['Channel: ' EEG.chanlocs(ch).labels]); ``` ### Event Label Checking ``` % Safely loop through event types and display them fprintf('Event types:\n'); for i = 1:length(EEG.event) t = EEG.event(i).type; if isnumeric(t) fprintf('%d\n', t); elseif ischar(t) fprintf('%s\n', t); elseif isstring(t) fprintf('%s\n', t); else disp(t); % fallback end end %Epoching for i = 1:length(EEG.event) EEG.event(i).type = num2str(EEG.event(i).type); end event_types = {EEG.event.type}; unique_types = unique(event_types); disp(unique_types); ``` > {'1'} {'11'} {'12'} {'13'} {'21'} {'22'} {'23'} : 1=GO; 2=NOGO ### Epoching - Windows = 2000ms(2s) - Base line = (-500ms,0) - Event = 0ms ![event11](https://hackmd.io/_uploads/Syty5s2zeg.jpg) ![event12](https://hackmd.io/_uploads/B1t19i3Ggl.jpg) ![event13](https://hackmd.io/_uploads/SJKkqjnzgl.jpg) ![event21](https://hackmd.io/_uploads/S1tJ5jnzex.jpg) ![event22](https://hackmd.io/_uploads/S1xKkcjhzeg.jpg) ![event23](https://hackmd.io/_uploads/H1FJqjnzlg.jpg) ``` % --- NEW CODE FOR GO/NOGO PLV MATRICES --- % 1. Identify Go and NoGo trial indices % You need to know your event codes for Go and NoGo trials. % Let's assume Go trials are code 11, 12, 13 and NoGo are 21, 22, 23 (based on your earlier code) go_event_codes = ['11' '12' '13']; nogo_event_codes = ['21' '22' '23']; go_trial_indices = []; nogo_trial_indices = []; % Loop through EEG.event to find the trial type for each epoch % Assuming your 'EEG' variable here is the one after epoching for ALL events % and 'EEG.event' contains the event information for each *epoch*. % If you epoched separately earlier, you might need to combine them first or iterate. % For simplicity, we assume EEG.event here corresponds to the current epoched EEG. for i = 1:EEG.trials current_epoch_event_type = EEG.event(i).type; % Get the event type for the current epoch if ismember(current_epoch_event_type, go_event_codes) go_trial_indices = [go_trial_indices, i]; elseif ismember(current_epoch_event_type, nogo_event_codes) nogo_trial_indices = [nogo_trial_indices, i]; end end % --- Epoching and Plotting --- % IMPORTANT: Convert event types to numeric BEFORE the loop % This should be done once on the continuous data for i = 1:length(EEG.event) if ischar(EEG.event(i).type) % Check if it's a string before converting EEG.event(i).type = str2num(EEG.event(i).type); end end % Store the original continuous EEG dataset BEFORE epoching % We will make a copy of this for each iteration ORIG_EEG = EEG; % Create a figure per event type for code = [11 12 13 21 22 23] % Create a temporary EEG dataset for each epoching operation % This ensures pop_epoch always starts from the continuous data CURRENT_EEG = EEG; % Epoch the data for the current event code CURRENT_EEG = pop_epoch(CURRENT_EEG, {num2str(code)}, [-0.5 1.5]); % Remove baseline from the epoched data CURRENT_EEG = pop_rmbase(CURRENT_EEG, [-500 0]); % Only plot if epochs were actually created for this code if ~isempty(CURRENT_EEG.epoch) figure; % Ensure pop_timtopo uses the correct time range for the epoched data pop_timtopo(CURRENT_EEG, [CURRENT_EEG.xmin*1000 CURRENT_EEG.xmax*1000], [NaN], sprintf('Event code %d', code)); sgtitle(sprintf('Event code %d - Topography over time', code)); % Add a main title to the figure else fprintf('No epochs found for event code %d. Skipping plot.\n', code); end end ``` ### Analysis #### wavelet - spectrum : a bulge is manifested on 6-8Hz -> could be theta band ![spectopo](https://hackmd.io/_uploads/Syezb3nGgg.jpg) ``` pop_spectopo(EEG, 1, [EEG.xmin*1000 EEG.xmax*1000], 'EEG','freqrange', [3 20],'electrodes', 'off'); ``` - -> theta band (3-8Hz) -> 6Hz for central freq ``` current_EEG = EEG_epoched; frequency = 6; % Hz, for theta band srate = current_EEG.srate; time = -0.5:1/srate:1.5; if length(time) ~= current_EEG.pnts warning('Time vector length does not match EEG.pnts. Adjusting time vector.'); time = linspace(current_EEG.xmin, current_EEG.xmax, current_EEG.pnts); end ``` #### instantaneous phase - event 1 = 32 channels / once ``` s = (6/(2*pi*frequency))^2; wavelet = exp(2*1i*pi*frequency.*time) .* exp(-time.^2./(2*s)/frequency); n_wavelet = length(wavelet)+1; n_data = current_EEG.pnts; % Number of time points in each epoch n_convolution = n_wavelet + n_data - 1; % Initialize phase_data for ALL trials in EEG_epoched phase_data = zeros(current_EEG.nbchan, n_data, current_EEG.trials); % Loop over ALL trials (Go and NoGo) and channels to get phase data for trial = 1:current_EEG.trials for chan = 1:current_EEG.nbchan eeg_data = squeeze(current_EEG.data(chan,:,trial)); fft_data = fft(eeg_data, n_convolution); fft_wavelet = fft(wavelet, n_convolution); conv_result = ifft(fft_wavelet .* fft_data, n_convolution); % Removed *sqrt(s) here, it's often not in definition half_wave = floor(n_wavelet/2); conv_result = conv_result(half_wave + 1 : end - half_wave); phase_data(chan,:,trial) = angle(conv_result); % Instantaneous phase end end ``` #### PLV(phase locking value) - evaluate connectivity : 32 x 32 Matrics - Calculate PLV for Go trials and NoGo trials ![GoPLV](https://hackmd.io/_uploads/SyblHh2zee.jpg) ![NoGoPLV](https://hackmd.io/_uploads/S1-xH32Mxg.jpg) ``` % --- Identify Go and NoGo trial indices from the COMBINED epoched dataset --- go_event_codes = [11 12 13]; nogo_event_codes = [21 22 23]; go_trial_indices = []; nogo_trial_indices = []; for i = 1:current_EEG.trials current_epoch_event_type = current_EEG.event(i).type; if ismember(current_epoch_event_type, go_event_codes) go_trial_indices = [go_trial_indices, i]; elseif ismember(current_epoch_event_type, nogo_event_codes) nogo_trial_indices = [nogo_trial_indices, i]; end end % Extract phase data for Go and NoGo trials phase_data_go = phase_data(:,:,go_trial_indices); phase_data_nogo = phase_data(:,:,nogo_trial_indices); num_go_trials = length(go_trial_indices); num_nogo_trials = length(nogo_trial_indices); disp(['Number of Go trials: ', num2str(num_go_trials)]); disp(['Number of NoGo trials: ', num2str(num_nogo_trials)]); % 2. Calculate PLV for Go trials PLV_Go = zeros(current_EEG.nbchan, current_EEG.nbchan); for ch1 = 1:current_EEG.nbchan for ch2 = 1:current_EEG.nbchan if ch1 == ch2 PLV_Go(ch1, ch2) = 1; % Self-coherence is always 1 continue; end instant_phase_diff_rad_go = squeeze(phase_data_go(ch1,:,:) - phase_data_go(ch2,:,:)); complex_phase_vectors_go = exp(1i * instant_phase_diff_rad_go); all_complex_phase_vectors_go = complex_phase_vectors_go(:); PLV_Go(ch1,ch2) = abs(mean(all_complex_phase_vectors_go)); end end figure; imagesc(PLV_Go); colorbar; title('Go PLV'); xlabel('Channels'); ylabel('Channels'); % 3. Calculate PLV for NoGo trials PLV_NoGo = zeros(current_EEG.nbchan, current_EEG.nbchan); for ch1 = 1:current_EEG.nbchan for ch2 = 1:current_EEG.nbchan if ch1 == ch2 PLV_NoGo(ch1, ch2) = 1; % Self-coherence is always 1 continue; end instant_phase_diff_rad_nogo = squeeze(phase_data_nogo(ch1,:,:) - phase_data_nogo(ch2,:,:)); complex_phase_vectors_nogo = exp(1i * instant_phase_diff_rad_nogo); all_complex_phase_vectors_nogo = complex_phase_vectors_nogo(:); PLV_NoGo(ch1,ch2) = abs(mean(all_complex_phase_vectors_nogo)); end end figure; imagesc(PLV_NoGo); colorbar; title('NoGo PLV'); xlabel('Channels'); ylabel('Channels'); ``` ### Statistic - no significant under fdr - without fdr, pairs of channels with p<0.05 and W>0.3(effect size) ``` % Statistical test % Parameters nSubjects = 5; nChannels = size(plv_diff_2_8, 1); alpha = 0.05; % Store results p_friedman = NaN(nChannels, nChannels); effect_size_W = NaN(nChannels, nChannels); posthoc_p = NaN(nChannels, nChannels, 3); % [5v5, 2v8, 5v8] % Loop through upper triangle (channel pairs) for ch1 = 1:nChannels for ch2 = ch1+1:nChannels % Extract PLV diffs for each condition vals_2_8 = squeeze(plv_diff_2_8(ch1, ch2, :)); vals_5_5 = squeeze(plv_diff_5_5(ch1, ch2, :)); vals_8_2 = squeeze(plv_diff_8_2(ch1, ch2, :)); % Combine into subjects × conditions matrix data = [vals_2_8, vals_5_5, vals_8_2]; % Friedman test p = friedman(data, 1, 'off'); p_friedman(ch1, ch2) = p; % Effect size: Kendall's W ranked_data = tiedrank(data); % rank each row (subject) across conditions SS_total = sum((mean(ranked_data, 2) - mean(ranked_data(:))).^2); W = 12 * SS_total / (nSubjects^2 * (3^2 - 1)); effect_size_W(ch1, ch2) = W; % Post-hoc Wilcoxon signed-rank tests [~, p1] = signrank(vals_2_8, vals_5_5); % 2:8 vs 5:5 [~, p2] = signrank(vals_2_8, vals_8_2); % 2:8 vs 8:2 [~, p3] = signrank(vals_5_5, vals_8_2); % 5:5 vs 8:2 posthoc_p(ch1, ch2, :) = [p1, p2, p3]; end end % Flatten p-values for correction all_p = p_friedman(~isnan(p_friedman)); [~, ~, adj_p_friedman] = fdr_bh(all_p, alpha, 'pdep', 'yes'); % Reinsert into matrix form p_friedman_fdr = NaN(nChannels, nChannels); mask = ~isnan(p_friedman); p_friedman_fdr(mask) = adj_p_friedman; % Optional: also FDR-correct post-hoc posthoc_p_flat = posthoc_p(~isnan(posthoc_p)); [~, ~, adj_posthoc_p] = fdr_bh(posthoc_p_flat, alpha, 'pdep', 'yes'); posthoc_p_adj = NaN(size(posthoc_p)); posthoc_p_adj(~isnan(posthoc_p)) = adj_posthoc_p; % Save results save('PLV_stats_results.mat', 'p_friedman_fdr', 'effect_size_W', 'posthoc_p_adj'); % Thresholds alpha = 0.05; effect_size_thresh = 0.3; % Initialize results array results = []; % Loop through upper triangle of matrices for ch1 = 1:nChannels for ch2 = ch1+1:nChannels if p_friedman(ch1, ch2) < alpha && effect_size_W(ch1, ch2) > effect_size_thresh %it should be p_friedman_fdr, however, there will be no significant results. % Extract post-hoc p-values p1 = posthoc_p_adj(ch1, ch2, 1); p2 = posthoc_p_adj(ch1, ch2, 2); p3 = posthoc_p_adj(ch1, ch2, 3); % Append to results results = [results; ch1, ch2, ... p_friedman(ch1, ch2), ... effect_size_W(ch1, ch2), ... p1, p2, p3]; end end end % Check if results are empty if ~isempty(results) % Create readable table results_table = array2table(results, ... 'VariableNames', {'Chan1', 'Chan2', ... 'Friedman_p', 'EffectSize_W', ... 'Posthoc_p_28_vs_55', ... 'Posthoc_p_28_vs_82', ... 'Posthoc_p_55_vs_82'}); disp(results_table); writetable(results_table, 'Significant_Channel_Pairs_no_fdr.csv'); else disp('⚠️ No significant channel pairs found with current thresholds.'); end chanlocs = EEG.chanlocs; figure; topoplot([], chanlocs, 'style', 'blank', 'electrodes', 'labelpoint'); for i = 1:size(results, 1) ch1 = results(i,1); ch2 = results(i,2); disp(chanlocs(ch1).labels) disp(chanlocs(ch2).labels) end ``` ## Result see top