diff --git a/Gemfile b/Gemfile index df9cbac..cac8a67 100644 --- a/Gemfile +++ b/Gemfile @@ -26,3 +26,9 @@ end gem "webrick", "~> 1.7" + +# Required for Ruby 3.4+ (removed from default gems) +gem "csv" +gem "logger" +gem "base64" +gem "bigdecimal" diff --git a/assets/images/emg-bids-dialogbox-shot.png b/assets/images/emg-bids-dialogbox-shot.png new file mode 100644 index 0000000..dd9c05e Binary files /dev/null and b/assets/images/emg-bids-dialogbox-shot.png differ diff --git a/assets/images/emg-bidsimport-shot.png b/assets/images/emg-bidsimport-shot.png new file mode 100644 index 0000000..6ffb87a Binary files /dev/null and b/assets/images/emg-bidsimport-shot.png differ diff --git a/assets/images/emg-eeglabview-shot.png b/assets/images/emg-eeglabview-shot.png new file mode 100644 index 0000000..86153ee Binary files /dev/null and b/assets/images/emg-eeglabview-shot.png differ diff --git a/assets/images/emg-rawdata-shot.png b/assets/images/emg-rawdata-shot.png new file mode 100644 index 0000000..98271a8 Binary files /dev/null and b/assets/images/emg-rawdata-shot.png differ diff --git a/assets/images/emg-spectopo-shot.png b/assets/images/emg-spectopo-shot.png new file mode 100644 index 0000000..bffdb28 Binary files /dev/null and b/assets/images/emg-spectopo-shot.png differ diff --git a/assets/images/emg_all_channels.png b/assets/images/emg_all_channels.png new file mode 100644 index 0000000..989989f Binary files /dev/null and b/assets/images/emg_all_channels.png differ diff --git a/assets/images/emg_envelope_computation.png b/assets/images/emg_envelope_computation.png new file mode 100644 index 0000000..883edde Binary files /dev/null and b/assets/images/emg_envelope_computation.png differ diff --git a/tutorials/misc/EEGLAB_and_EMG_data.md b/tutorials/misc/EEGLAB_and_EMG_data.md new file mode 100644 index 0000000..c561a08 --- /dev/null +++ b/tutorials/misc/EEGLAB_and_EMG_data.md @@ -0,0 +1,720 @@ +--- +layout: default +title: EEGLAB and EMG data +long_title: EEGLAB and EMG data +parent: Reference Topics +grand_parent: Tutorials +--- +EEGLAB and EMG data +==================== + +EEGLAB supports processing electromyography (EMG) data in addition to EEG and MEG data. This tutorial demonstrates how to analyze EMG data in BIDS format, compute EMG event-related potentials (EMG-ERPs), and compare muscle activation patterns across different conditions. + +We will use the [emg2qwerty dataset](https://nemar.org/dataexplorer/detail?dataset_id=nm000104) available on NEMAR, which contains surface EMG recordings from forearm muscles during touch typing on a QWERTY keyboard. + +## Dataset Overview + +The emg2qwerty dataset includes: +- 32 bipolar EMG channels (16 per forearm) +- Surface EMG recordings from wristband sensors +- Keystroke events with precise timing +- Sampling rate: ~2000 Hz +- Task: Touch typing prompted text + +This dataset is ideal for demonstrating EMG-ERP analysis because: +1. Events (keystrokes) have precise timing +2. Different keys activate different muscle patterns +3. Left vs right hand comparisons are possible +4. High trial counts for common keys + +## Importing EMG BIDS data + +EEGLAB can import BIDS-formatted EMG data using the [bids-matlab-tools](https://github.com/sccn/bids-matlab-tools) plugin. After starting EEGLAB, use menu item File > Import data > From BIDS folder structure. + +BIDS Import Dialog + +Alternatively, you can import programmatically: + +```matlab +% Start EEGLAB +addpath('/path/to/eeglab'); +eeglab nogui; + +% Define BIDS root directory +bids_root = '/path/to/nm000104'; +subject_id = 'sub-03734552'; +session_id = 'ses-1620588853'; + +% Import using pop_importbids +[STUDY, ALLEEG] = pop_importbids(bids_root, ... + 'bidstask', 'typing', ... + 'subjects', subject_id(5:end)); % Remove 'sub-' prefix + +EEG = ALLEEG(1); +``` + +After import, EEGLAB shows the dataset information: + +![BIDS Import Result](/assets/images/emg-bidsimport-shot.png) + +EEGLAB Interface + +You can also load the BDF files directly: + +```matlab +% Load BDF file +emg_file = fullfile(bids_root, subject_id, session_id, 'emg', ... + sprintf('%s_%s_task-typing_emg.bdf', subject_id, session_id)); +EEG = pop_biosig(emg_file); + +% Load events from TSV file +events_file = fullfile(bids_root, subject_id, session_id, 'emg', ... + sprintf('%s_%s_task-typing_events.tsv', subject_id, session_id)); +events_table = readtable(events_file, 'FileType', 'text', 'Delimiter', '\t'); + +% Convert to EEGLAB event structure +for i = 1:height(events_table) + EEG.event(i).latency = events_table.onset(i) * EEG.srate; + EEG.event(i).type = char(events_table.value(i)); + if ~strcmp(events_table.key{i}, 'n/a') + EEG.event(i).key = char(events_table.key(i)); + end +end + +% Set data type +EEG.etc.datatype = 'emg'; +EEG = eeg_checkset(EEG); +``` + +## Sanity checks: Visualizing the data + +Before preprocessing, it's important to verify the data quality and structure using EEGLAB's visualization tools. + +### Plotting raw EMG data + +Use EEGLAB's scrolling data viewer to inspect raw EMG signals and events: + +```matlab +% Use EEGLAB menu: Plot > Channel data (scroll) +% Or from command line: +pop_eegplot(EEG, 1, 1, 1); +``` + +This opens an interactive window where you can: +- Scroll through the continuous EMG data +- See keystroke events marked with vertical lines +- Identify noisy channels or artifacts +- Adjust the display scale and time window + +![Raw EMG Data Viewer](/assets/images/emg-rawdata-shot.png) + +**Note for EMG:** The scrolling viewer works the same as for EEG, but expect: +- Higher amplitude signals (µV to mV range) +- High-frequency oscillations (20-250 Hz) +- Bursts of activity aligned with keystroke events + +### Inspecting event structure + +Use EEGLAB's event viewer to check event timing and types: + +```matlab +% View events: Edit > Event values +% Or list event types from command line: +unique_events = unique({EEG.event.type}); +fprintf('Found %d event types\n', length(unique_events)); +fprintf('Total events: %d\n', length(EEG.event)); + +% Count specific keystroke types +keystroke_idx = find(contains({EEG.event.type}, 'keystroke_')); +fprintf('Keystroke events: %d\n', length(keystroke_idx)); +``` + +### Computing power spectral density (PSD) + +Use EEGLAB's spectral plotting function to verify EMG frequency content: + +```matlab +% Use EEGLAB menu: Plot > Channel spectra and maps +% Or from command line: +% Note: 'freq', [] disables topoplots which are NOT meaningful for EMG +figure; pop_spectopo(EEG, 1, [0 EEG.xmax*1000], 'EEG', ... + 'freqrange', [0 500], ... + 'freq', []); +``` + +This displays: +- Power spectral density for all channels overlaid +- Frequency range up to 500 Hz (appropriate for EMG) +- **Important**: We set `'freq', []` to disable topoplots - they are NOT meaningful for EMG data since electrodes are on forearm muscles, not scalp + +![EMG Power Spectrum](/assets/images/emg-spectopo-shot.png) + +**Expected pattern:** Most EMG power should be concentrated in the 20-250 Hz range, with peaks around 50-150 Hz for muscle activity. + +## EMG preprocessing + +EMG signals require different preprocessing than EEG: + +### Frequency ranges +EMG contains higher frequencies than EEG: +- **EEG**: Typically 1-50 Hz +- **EMG**: Typically 20-450 Hz (motor tasks: 20-250 Hz) + +### Bandpass filtering + +```matlab +% Apply bandpass filter for EMG +lowcut = 20; % Hz - removes slow drifts +highcut = 250; % Hz - removes high-frequency noise + +EEG = pop_eegfiltnew(EEG, 'locutoff', lowcut, 'hicutoff', highcut); +``` + +### Channel quality check + +For EMG, check channels with unusual variance: + +```matlab +% Calculate variance for each channel +channel_vars = var(EEG.data, 0, 2); +mean_var = mean(channel_vars); +std_var = std(channel_vars); + +% Flag channels outside 3 standard deviations +bad_chan_idx = find(channel_vars < mean_var - 3*std_var | ... + channel_vars > mean_var + 3*std_var); +``` + +## Data cleaning with clean_rawdata + +**Important:** EMG data can have extremely noisy channels or bad segments. These must be cleaned BEFORE computing the envelope, otherwise artifacts will be preserved in the ERP. + +### Why cleaning is critical for EMG + +- **Electrode displacement**: Wristband movement creates large amplitude artifacts +- **Bad channels**: Some channels may have poor contact throughout the session +- **Motion artifacts**: Arm/wrist movement during typing +- **Session variability**: Some sessions have much worse quality than others + +### Installing clean_rawdata plugin + +If not already installed: + +```matlab +% From EEGLAB menu: File > Manage EEGLAB extensions +% Search for 'clean_rawdata' and install + +% Or from command line: +plugin_askinstall('clean_rawdata'); +``` + +### Using ASR (Artifact Subspace Reconstruction) + +ASR removes bad data portions while preserving good segments: + +```matlab +% Apply clean_rawdata with ASR +% Parameters tuned for EMG data (more conservative than EEG defaults) +EEG = clean_rawdata(EEG, ... + 'FlatlineCriterion', 5, ... % Remove channels flat for >5 seconds + 'ChannelCriterion', 0.8, ... % Remove channels with <0.8 correlation to robust estimate + 'LineNoiseCriterion', 4, ... % Line noise threshold + 'Highpass', [0.25 0.75], ... % Already filtered, set to preserve + 'BurstCriterion', 20, ... % ASR burst criterion (higher = more aggressive) + 'WindowCriterion', 0.25, ... % Remove windows with >25% bad channels + 'BurstRejection', 'on', ... % Enable ASR burst correction + 'Distance', 'Euclidean', ... % Distance metric + 'WindowCriterionTolerances', [-Inf 7]); % Window tolerance + +EEG = eeg_checkset(EEG); +``` + +### Understanding ASR parameters for EMG + +**Key parameters to adjust:** + +1. **BurstCriterion** (default: 20 for EMG, 5 for EEG) + - Higher values = less aggressive cleaning + - EMG has naturally higher amplitudes than EEG + - Too aggressive (low value) removes genuine muscle activity + - Recommended: 15-25 for EMG + +2. **ChannelCriterion** (0.8 recommended) + - Removes channels that don't correlate well with neighbors + - Important for wristband arrays where channels should be similar + +3. **WindowCriterion** (0.25) + - Removes time windows where >25% of channels are bad + - Useful for movement artifacts affecting multiple channels + +### Visual inspection after cleaning + +Always visualize the data after cleaning: + +```matlab +% Plot scrolling data to inspect cleaning results +pop_eegplot(EEG, 1, 1, 1); + +% Check which channels were removed +if isfield(EEG.etc, 'clean_channel_mask') + removed_chans = find(~EEG.etc.clean_channel_mask); + fprintf('Removed %d channels: ', length(removed_chans)); + for i = 1:length(removed_chans) + fprintf('%s ', EEG.chanlocs(removed_chans(i)).labels); + end + fprintf('\n'); +end + +% Check how much data was removed +if isfield(EEG.etc, 'clean_sample_mask') + samples_removed = sum(~EEG.etc.clean_sample_mask); + pct_removed = 100 * samples_removed / length(EEG.etc.clean_sample_mask); + fprintf('Removed %.1f%% of data samples\n', pct_removed); +end +``` + +### Alternative: Manual bad channel removal + +If you prefer manual control: + +```matlab +% Mark bad channels +bad_channels = [5, 12, 23]; % Example indices + +% Remove bad channels +EEG = pop_select(EEG, 'nochannel', bad_channels); + +% Or interpolate (if you have spatial information) +% EEG = pop_interp(EEG, bad_channels, 'spherical'); +``` + +### Session quality metrics + +Before cleaning, compute quality metrics to decide if a session should be excluded: + +```matlab +% Compute RMS per channel +channel_rms = sqrt(mean(EEG.data.^2, 2)); + +% Identify outlier sessions (>3 SD from mean) +if mean(channel_rms) > subject_mean_rms + 3*subject_std_rms + fprintf('WARNING: This session may be an outlier (high RMS)\n'); + fprintf('Consider excluding from group analysis\n'); +end + +% Check percentage of extreme values +pct_extreme = 100 * sum(abs(EEG.data(:)) > 1000) / numel(EEG.data); +if pct_extreme > 1 + fprintf('WARNING: %.2f%% of samples exceed 1000 µV\n', pct_extreme); +end +``` + +### Cleaning workflow summary + +**Recommended cleaning order:** +1. Bandpass filter (20-250 Hz) - removes low/high frequency noise +2. Identify obviously bad channels (visual inspection) +3. Apply ASR with conservative parameters (BurstCriterion=20) +4. Visual inspection of cleaned data +5. Compute session quality metrics +6. Decide whether to keep or exclude session + +**When to exclude entire sessions:** +- >30% of data removed by ASR +- >50% of channels removed +- Extreme amplitude artifacts persisting after cleaning +- Subject-level RMS >3 SD from mean + +### Important notes + +- **Clean BEFORE envelope**: Artifacts in filtered data will be preserved in the envelope +- **Conservative for EMG**: EMG has higher amplitudes than EEG, don't over-clean +- **Session-level decisions**: Some sessions may be too noisy to salvage +- **Document exclusions**: Keep track of which sessions/channels were excluded + +## Computing the linear envelope (CRITICAL for EMG-ERP) + +**Important:** Standard ERP analysis does NOT work well for raw EMG data. Here's why: + +### Why raw EMG ERPs fail + +EMG is a high-frequency oscillatory signal (20-250 Hz). When you average raw EMG epochs together: +- Positive phases of the oscillation cancel out negative phases +- Result: Weak, noisy ERPs that don't reflect true muscle activation +- The averaging destroys the amplitude information we care about + +### Solution: The linear envelope + +The **linear envelope** preserves amplitude information while removing the problematic oscillations: + +1. **Rectification**: Take the absolute value (makes all values positive) +2. **Low-pass filtering**: Smooth the rectified signal (10-20 Hz) + +```matlab +% Step 1: Rectify the filtered EMG +EEG.data = abs(EEG.data); + +% Step 2: Low-pass filter to create envelope +envelope_cutoff = 20; % Hz - 20 Hz provides faster response for typing tasks +EEG = pop_eegfiltnew(EEG, 'hicutoff', envelope_cutoff); + +% Clip any negative values from zero-phase filtering +EEG.data(EEG.data < 0) = 0; + +% Mark as envelope data +EEG.etc.is_envelope = true; +EEG.etc.envelope_cutoff = envelope_cutoff; +``` + +### Visualization: Linear envelope computation + +![Envelope Computation](/assets/images/emg_envelope_computation.png) + +This figure shows the four processing stages for two representative channels (left and right wristband). The red dashed line marks a keystroke event: + +1. **Raw signal** (blue): Unfiltered EMG with baseline noise and drift +2. **Band-pass filter** (green): 20-250 Hz filtered EMG - removes low-frequency drift and high-frequency noise +3. **Rectified** (purple): Absolute value of filtered signal - all values positive +4. **Low-pass filter / linear envelope** (magenta): Smooth envelope (20 Hz cutoff) capturing muscle activation amplitude + +### Parameters for envelope computation + +**Envelope low-pass cutoff frequency:** +- **5-10 Hz**: Maximum smoothing, for slow movements +- **20 Hz**: Standard for fast tasks like typing (recommended) +- **30-40 Hz**: Minimal smoothing, for very fast movements + +For typing tasks, **20 Hz** is recommended as it provides faster response to capture rapid finger movements while still smoothing the high-frequency oscillations. + +## Epoching EMG data + +**Important**: Epoch the **envelope data**, not the raw filtered EMG! + +Extract epochs around keystroke events: + +```matlab +% Define epoch window +epoch_start = -0.5; % seconds before keystroke +epoch_end = 1.0; % seconds after keystroke + +% Find keystroke events +keystroke_types = {}; +for i = 1:length(EEG.event) + if contains(EEG.event(i).type, 'keystroke_') + keystroke_types{end+1} = EEG.event(i).type; + end +end +unique_keystrokes = unique(keystroke_types); + +% Extract epochs +EEG = pop_epoch(EEG, unique_keystrokes, [epoch_start epoch_end], 'epochinfo', 'yes'); +``` + +### Baseline correction + +Remove baseline to focus on event-related activity: + +```matlab +% Define baseline window (avoid 100ms immediately before keystroke) +baseline_window = [-0.5, -0.1]; % seconds + +% Remove baseline +EEG = pop_rmbase(EEG, baseline_window * 1000); % Convert to ms +``` + +## Balancing trial counts for fair comparison + +**Important:** When comparing ERPs across conditions, unequal trial counts can bias results due to different signal-to-noise ratios. + +### Why trial balancing matters + +In typing data, key occurrence varies dramatically: +- Common keys (e, t, a): 200-500 occurrences +- Rare keys (z, q, x): 5-20 occurrences + +**Problems with unbalanced comparisons:** +- High-trial-count condition has better SNR (lower noise) +- Statistical comparisons are biased +- Visual differences may reflect SNR, not true effects + +### Strategy 1: Select keys with similar counts + +```matlab +% Count occurrences for each keystroke type +keystroke_counts = struct(); + +for i = 1:length(EEG.event) + if contains(EEG.event(i).type, 'keystroke_') + key_type = EEG.event(i).type; + if ~isfield(keystroke_counts, key_type) + keystroke_counts.(key_type) = 0; + end + keystroke_counts.(key_type) = keystroke_counts.(key_type) + 1; + end +end + +% Display sorted by count +key_names = fieldnames(keystroke_counts); +key_values = cellfun(@(x) keystroke_counts.(x), key_names); +[sorted_values, sort_idx] = sort(key_values, 'descend'); + +fprintf('Keystroke occurrence counts:\n'); +for i = 1:length(key_names) + fprintf(' %s: %d\n', key_names{sort_idx(i)}, sorted_values(i)); +end + +% Select keys with similar counts for comparison +% Example: Compare 'a' (left hand) vs 'k' (right hand) +% Only if they have similar trial counts (within 20%) +count_a = keystroke_counts.keystroke_a; +count_k = keystroke_counts.keystroke_k; + +if abs(count_a - count_k) / mean([count_a, count_k]) < 0.2 + fprintf('\nKeys "a" and "k" have similar trial counts - good for comparison\n'); +else + fprintf('\nWarning: Keys "a" and "k" have different trial counts\n'); + fprintf('Consider subsampling the higher-count condition\n'); +end +``` + +### Strategy 2: Subsample high-count condition + +```matlab +% Match trial counts by random subsampling +% Example: Balance key 'e' (high count) with key 'x' (low count) + +% Extract epochs +EEG_e = pop_epoch(EEG, {'keystroke_e'}, [epoch_start epoch_end]); +EEG_x = pop_epoch(EEG, {'keystroke_x'}, [epoch_start epoch_end]); + +fprintf('Before balancing:\n'); +fprintf(' Key "e": %d trials\n', EEG_e.trials); +fprintf(' Key "x": %d trials\n', EEG_x.trials); + +% Determine minimum trial count +min_trials = min(EEG_e.trials, EEG_x.trials); + +% Randomly select trials to match +if EEG_e.trials > min_trials + % Subsample key 'e' + rng(42); % Set seed for reproducibility + selected_trials = randperm(EEG_e.trials, min_trials); + EEG_e = pop_select(EEG_e, 'trial', selected_trials); +end + +if EEG_x.trials > min_trials + % Subsample key 'x' + rng(42); + selected_trials = randperm(EEG_x.trials, min_trials); + EEG_x = pop_select(EEG_x, 'trial', selected_trials); +end + +fprintf('After balancing:\n'); +fprintf(' Key "e": %d trials\n', EEG_e.trials); +fprintf(' Key "x": %d trials\n', EEG_x.trials); + +% Now compute ERPs with equal SNR +ERP_e = mean(EEG_e.data, 3); +ERP_x = mean(EEG_x.data, 3); +``` + +### Strategy 3: Bootstrap confidence intervals + +For unequal trial counts, use bootstrap to estimate uncertainty: + +```matlab +% Compute ERP with confidence intervals using bootstrap +n_bootstrap = 1000; + +% Example: Key 'a' with many trials +EEG_a = pop_epoch(EEG, {'keystroke_a'}, [epoch_start epoch_end]); +n_trials = EEG_a.trials; +n_timepoints = EEG_a.pnts; + +% Select channel and initialize +chan_idx = 1; +bootstrap_erps = zeros(n_bootstrap, n_timepoints); + +% Bootstrap resampling +rng(42); +for i = 1:n_bootstrap + % Resample trials with replacement + trial_indices = randi(n_trials, n_trials, 1); + bootstrap_erps(i, :) = mean(squeeze(EEG_a.data(chan_idx, :, trial_indices)), 2); +end + +% Compute mean and confidence intervals +erp_mean = mean(bootstrap_erps, 1); +erp_ci_lower = prctile(bootstrap_erps, 2.5, 1); % 95% CI +erp_ci_upper = prctile(bootstrap_erps, 97.5, 1); + +% Plot with confidence intervals +time_vec = EEG_a.times / 1000; +figure; +fill([time_vec, fliplr(time_vec)], ... + [erp_ci_lower, fliplr(erp_ci_upper)], ... + 'b', 'FaceAlpha', 0.2, 'EdgeColor', 'none'); +hold on; +plot(time_vec, erp_mean, 'b', 'LineWidth', 2); +plot([0, 0], ylim, 'r--', 'LineWidth', 1.5); +xlabel('Time (s)'); +ylabel('Amplitude (\muV)'); +title('ERP with 95% Bootstrap Confidence Intervals'); +grid on; +``` + +### Recommendations + +For ERP comparisons: + +1. **Same-hand comparisons** (e.g., different fingers): Use keys with similar occurrence + - Example: 'a', 'e', 't' all have high counts for left hand + +2. **Left vs right hand**: Balance trial counts by subsampling + - Example: Match 'a' (left) with 'k' (right) by subsampling + +3. **Statistical testing**: Always report trial counts + - Use bootstrap or permutation tests for significance + - Account for unequal variance if trial counts differ + +4. **Minimum trial count**: Aim for at least 30 trials per condition + - Fewer trials = noisy ERPs + - More trials = better SNR but diminishing returns beyond ~100 + +## Computing EMG-ERPs + +EMG-ERPs are computed by averaging **envelope data** across trials: + +```matlab +% Identify channel groups +% NOTE: Verified empirically - adjust based on your hardware setup +left_wristband = 1:16; % LEFT hand (EMG0-EMG15) +right_wristband = 17:32; % RIGHT hand (EMG16-EMG31) + +% NOTE: EEG should contain envelope data from step 2b! +% Extract epochs for specific keys +EEG_a = pop_epoch(EEG, {'keystroke_a'}, [epoch_start epoch_end]); +EEG_a = pop_rmbase(EEG_a, baseline_window * 1000); + +EEG_k = pop_epoch(EEG, {'keystroke_k'}, [epoch_start epoch_end]); +EEG_k = pop_rmbase(EEG_k, baseline_window * 1000); + +% Compute ERPs from envelope (average across trials) +ERP_a_left = mean(EEG_a.data(left_wristband, :, :), 3); +ERP_k_right = mean(EEG_k.data(right_wristband, :, :), 3); +``` + +### Visualizing EMG-ERPs with EEGLAB + +Use EEGLAB's ERP plotting functions to visualize event-related muscle activation: + +```matlab +% Use EEGLAB menu: Plot > Channel ERPs > In rectangular array +% Or from command line: +figure; pop_plottopo(EEG_a, [1:16], 'EMG-ERP for key "a" (left channels)', ... + 0, 'ydir', 1); +``` + +This figure shows EMG-ERPs for all burst-initial keystrokes, separated by hand (left vs right) and wristband. All 32 channels are displayed across four panels, using envelope data from Step 2b. + +![Individual Channel ERPs](/assets/images/emg_all_channels.png) + +**Trial selection**: Only burst-initial keystrokes (preceded by >500ms pause) are included to avoid epoch overlap from rapid typing. From approximately 4,000 total keystrokes in this recording session, the burst-initial criterion selects ~200-400 epochs per hand—sufficient for reliable EMG-ERPs while ensuring clean baselines. The exact epoch counts are shown in the figure title. + +Note the **contralateral activation pattern**: left-hand keys (a, s, d, e, r, ...) show stronger activation in the left wristband (top-left), while right-hand keys (k, l, j, i, o, ...) show stronger activation in the right wristband (bottom-right). When comparing conditions with different trial counts, use random subsampling as described in the [Balancing trial counts](#balancing-trial-counts-for-fair-comparison) section above. + +**Important for EMG:** +- EEGLAB's topoplot (scalp maps) is NOT meaningful for EMG data +- Focus on channel ERPs and time-course plots +- Compare ERPs across channels on the same limb + +## Comparing conditions with EEGLAB + +### Comparing multiple conditions + +To compare ERPs across different conditions (e.g., different keys or hands), use EEGLAB's comparison tools: + +**Method 1: Overlay plots for selected channels** + +```matlab +% Compare key 'a' (left hand) vs key 'k' (right hand) for specific channels +% Use EEGLAB menu: Plot > Channel ERPs > With scalp maps +% Then select specific channels and conditions + +% Or from command line: +% First, select left-hand channels for key 'a' +figure; pop_plotdata(EEG_a, 1, [1 5 9], 'Key "a" - Left hand channels'); + +% Then, select right-hand channels for key 'k' +figure; pop_plotdata(EEG_k, 1, [17 21 25], 'Key "k" - Right hand channels'); +``` + +**Method 2: Compare using EEGLAB's STUDY framework** + +For systematic comparison across multiple subjects or conditions: + +```matlab +% Create a STUDY structure with multiple datasets +% Use EEGLAB menu: File > Create study > Browse for datasets +% Then use: Study > Precompute channel measures +% Finally: Study > Plot channel measures + +% This allows statistical comparison across conditions +``` + +**Expected patterns:** +- **Contralateral dominance**: Stronger activation in the hand performing the keystroke +- **Timing differences**: Peak latency may differ between hands +- **Amplitude differences**: May vary based on finger position and force + +## Key differences: EMG vs EEG + +| Aspect | EEG | EMG | +|--------|-----|-----| +| **Frequency range** | 1-50 Hz | 20-450 Hz | +| **Amplitude** | Microvolts | Microvolts to millivolts | +| **Spatial resolution** | Brain regions | Specific muscles | +| **Common artifacts** | Eye blinks, muscle tension | Motion, electrode displacement | +| **Baseline** | Pre-stimulus period | Pre-movement period | +| **ERP computation** | Direct averaging works | Requires linear envelope first! | +| **Summary measures** | ERP amplitude/latency | RMS, integrated EMG, envelope amplitude | + +## Tips for EMG-ERP analysis with EEGLAB + +1. **ALWAYS use the linear envelope**: Direct averaging of raw EMG does not work! Rectify and low-pass filter (~20 Hz for typing) before epoching and averaging. + +2. **Use EEGLAB visualization tools**: + - `pop_eegplot()` for scrolling raw data inspection + - `pop_spectopo()` for frequency analysis (extend range to 500 Hz for EMG) + - `pop_plottopo()` or `pop_plotdata()` for ERP visualization + - Avoid EEGLAB's topographic maps (topoplot) - not meaningful for EMG + +3. **Choose appropriate filters**: Use higher cutoff frequencies than EEG (20-250 Hz for motor tasks) via `pop_eegfiltnew()`. + +4. **Baseline correction is critical**: Use `pop_rmbase()` to remove pre-event baseline. The envelope should have near-zero baseline. + +5. **Sanity checks with EEGLAB**: + - Plot raw data with `pop_eegplot(EEG, 1, 1, 1)` + - Check PSD with `pop_spectopo()` - verify power in 20-250 Hz + - Inspect events via menu: Edit > Event values + +6. **Account for trial count**: More trials = better SNR. Use `pop_epoch()` and check `.trials` field. Balance conditions using `pop_select()`. + +7. **Check lateralization**: Expect stronger activation in the hand performing the movement. + +8. **Visualize individual trials**: Use `pop_eegplot(EEG_epoched, 0, 1, 1)` to inspect trial-by-trial variability. + +9. **For multi-subject analysis**: Use EEGLAB's STUDY framework (menu: File > Create study) for group-level statistics. + +10. **Compare envelope parameters**: If ERPs look noisy, try adjusting the envelope cutoff (5-20 Hz) and re-epoch. + +## Additional resources + +- **Dataset**: [emg2qwerty on NEMAR](https://nemar.org/dataexplorer/detail?dataset_id=nm000104) +- **Paper**: [emg2qwerty at NeurIPS 2024](https://arxiv.org/abs/2410.20081) + +## Related tutorials + +- [EEGLAB and MEG data](EEGLAB_and_MEG_data.html) +- [Automated processing pipelines](../11_Scripting/automated_pipeline.html) +- [Analyzing EEG BIDS data in EEGLAB](../11_Scripting/Analyzing_EEG_BIDS_data_in_EEGLAB.html) diff --git a/tutorials/misc/EEGLAB_and_iEEG_data.md b/tutorials/misc/EEGLAB_and_iEEG_data.md index 0785bdf..0deda51 100644 --- a/tutorials/misc/EEGLAB_and_iEEG_data.md +++ b/tutorials/misc/EEGLAB_and_iEEG_data.md @@ -11,7 +11,7 @@ EEGLAB and iEEG, sEEG, or ECoG data EEGLAB supports reading most iEEG data formats (EDF, MEF3, NWB) through native code or plugins. The BIDS-Matlab-tools EEGLAB plugin also supports importing BIDS-formatted MEG data. You may install plugins from the EEGLAB plugin manager (menu item File > Manage EEGLAB extensions). - +Gre ## Importing data For example, after installing the MEF3 and BIDS-Matlab-tools plugins, you may import the