EEG dataset preprocessing pipeline

  • MNE version: 1.13.2
  • operating system: Windows 11

Hello,

I am working with this data EEG Motor Movement/Imagery Dataset v1.0.0 , and my ultimate goal is to compare left vs right hand conditions in all 109 participants. Right now, my goal is to work on the following preprocessing steps reccomended by another user here a few months ago:

  1. Confirm the Run 3 event mapping and use the same epoch and baseline settings for every participant
  2. Inspect the raw traces and PSD for persistently flat or clearly noisy channels. Mark those channels as bad before average referencing, ICA, and TFR calculation.
  3. Use ICA only for components that are clearly supported as artifacts by their topography and time course, plus EOG/ECG signals if available. Do not remove a component because doing so improves the final statistical result
  4. Reject only epochs with clear gross artifacts, using the same predefined rule for all participants. Do not copy the MEG rejection threshold from the tutorial; there is no universal EEG threshold that is appropriate for every dataset.
  5. . Record the retained T1/T2 epoch counts, bad channels, excluded ICA components, and drop reasons for each participant.

For now, I am running my code across 4 participants to ensure the pipeline works. So far I’m on the second step. I have tried these two methods to identify bad channels:

Method 1: The manual way

for subject, raw_resampled in all_data.items():#all subjects in trial 3
#raw_resampled = raws[0]# this calls the first element of raws, which is subject 1
#print(subject, raw.info[“sfreq”])
#print(type(raw_resampled))

```for subject, raw_resampled in all_data.items():#all subjects in trial 3
    

 
    events_resampled, event_id = mne.events_from_annotations(raw_resampled)#raw resampled is a tuple. but events from annotations expects single object
    epochs = mne.Epochs(raw_resampled, events_resampled, event_id, tmin=-1.5, tmax=2, baseline=None, preload=True)#we have to get a bigger range for alpha because it has longer wavelengths

    left_epochs = epochs["T1"]
    right_epochs = epochs["T2"]


    psd_left = left_epochs.compute_psd(fmin=1, fmax=40) # for each subject plot run 3 voltage over time
    psd_left.plot(show=False)```

   

This shows all the PSD plots for all 4 participant in the left hand condition. I approached this one condition at a time to make this simpler. Then, I noticed visually that participant 4 had one electrode that deviates significantly from the other, so I manually marked it as bad. The plots below show before and after bad channel removal and detection.

Method 2: The automatic way through Pyprep. This might be more effective for working with multiple participants

```%pip install --upgrade pyprep
#here i used an automated way of identifying bad channels, using pyprep class
from pyprep import NoisyChannels

for subject, raw_resampled in all_data.items():#all subjects in trial 3




noisy = NoisyChannels(raw_resampled, ransac=False) #disable Ransac for now. You must first flag channels with flat signals, extreme deviations, or high global noise (e.g., bad_by_deviation, bad_by_correlation). If noisy channels are not excluded first, RANSAC will use those bad channels to predict other channels, destroying its accuracy
noisy.find_all_bads()

# All unique channels flagged by the checks
bad_channels = noisy.get_bads()


results = noisy.get_bads(as_dict=True)

for method, channels in results.items():
    print(f"{method}: {channels}")```

Then I get a outputs like these for 4 participants:

```bad_by_nan: []
bad_by_flat: []
bad_by_deviation: ['T10']
bad_by_hf_noise: ['T8', 'T10', 'Tp8']
bad_by_correlation: ['T10']
bad_by_SNR: ['T10']
bad_by_dropout: []
bad_by_psd: ['Fp1', 'T8', 'O1', 'Oz', 'O2', 'Iz']
bad_by_ransac: []
bad_by_manual: []
bad_all: ['Fp1', 'T10', 'Oz', 'Iz', 'Tp8', 'T8', 'O2', 'O1']```

Please let me know if I need to fix anything so far, and any suggestions what I should do next considering the preprocessing pipeline I outlined earlier

Looks like pyprep may throw out a bit too many channels, but I cannot say without looking at the data myself. For participant 4, your manual selection of just kicking out Ft8 seems appropriate to me. Does pyprep find the same channel (and not others) for participant 4?

Yes, pyprep marks Ft8 for only participant 4 which suggests it works. It is marked as bad_by_deviation, which makes sense because it deviates significantly by the rest on the PSD plot. When looking at bad_by_deviation channels for other participants, a similar pattern shows. Therefore, it looks like only bad_by_deviation channels can be caught through visual inspection alone. pyprep on the other hand marks bad channels based on other criteria, such as bad by correlation, etc. but again, as you said, it may throw away good channels.

Do you have any suggestion to go about this? Do you think manual selection is appropriate for this analysis, and if so, is there any efficient way to do this across 100 participants? Please let me know.