Correcting eye movement artifacts using ICA

  • MNE version: e.g. 1.0.3
  • operating system: Windows 10

Hi there,
I’m trying to correct EOG artifacts using ICA and I’m having a problem. The data that I’m using is these PhysioNet Polysomnography files that can be accessed using this function:
mne.datasets.sleep_physionet.age.fetch_data

I have two EEG channels and one horizontal EOG channel and also some other channels that I don’t need like temperature recordings. I’m trying to use the EOG channel to remove the artifacts from the two EEG channels but it seems that the independent components that are made are almost identical to the original EEG channels which means that the EOG data is probably not used for some reason. I would like to get help.

This is my code:

import os
import numpy as np
import mne
import matplotlib.pyplot as plt
from mne.preprocessing import (ICA, create_eog_epochs, create_ecg_epochs,                                corrmap)
import sklearn
import pandas

The next block of code works perfectly. It crops the data, ignores some channels and sets annotations. My problem is in the last block.

paths = fetch_data(subjects=[4], recording=[1]) # getting the data path
patient=0 # number of the patient
raw_train = mne.io.read_raw_edf(paths[patient][0], stim_channel='Event marker', misc=['Temp rectal'])
raw_train.pick_channels(['EEG Fpz-Cz', 'EEG Pz-Oz','EOG horizontal']) # ignoring the other channels

annot_train = mne.read_annotations(paths[patient][1])
raw_train.set_annotations(annot_train, emit_warning=False)
annotation_desc_2_event_id = {'Sleep stage W': 1,
                              'Sleep stage 1': 2,
                              'Sleep stage 2': 3,
                              'Sleep stage 3': 4,
                              'Sleep stage 4': 4,
                              'Sleep stage R': 5} # annotations dictionary
annot_train.crop(annot_train[1]['onset'] - 60*3, annot_train[-2]['onset'] + 30) # cropping the data
raw_train.set_annotations(annot_train, emit_warning=False)
events_train, _ = mne.events_from_annotations(
    raw_train, event_id=annotation_desc_2_event_id, chunk_duration=30.)

Here I tried to run some code that I copied from this tutorial:
https://mne.tools/stable/auto_tutorials/preprocessing/40_artifact_correction_ica.html

raw_train.set_channel_types({'EOG horizontal': 'eog'}) # needed for create_eog_epochs() to find en EOG channel because it wasn't originally named "eog"
eog_evoked = create_eog_epochs(raw_train).average()
eog_evoked.apply_baseline(baseline=(None, -0.2))
raw_train.load_data()
filt_raw = raw_train.copy().filter(l_freq=1., h_freq=None) # just a filter. Nothing special
ica = ICA(n_components=2, max_iter='auto', random_state=97) # choosing the maximum number of components which is 2
ica.fit(filt_raw) # running the ICA decomposition on the data
ica.exclude = [0] # choosing an independent component to exclude
reconst_raw = raw_train.copy() # copying so it doesn't change the original data
ica.apply(reconst_raw) # applying the ICA on the data
raw_train.plot(start=0, duration=20,
         show_scrollbars=False,scalings=dict(eeg=0.1*1e-3))
reconst_raw.plot(start=0, duration=20,
                 show_scrollbars=False,scalings=dict(eeg=0.1*1e-3))
del reconst_raw # deleting the copy
ica

That’s what you get from the last line:

Method fastica
Fit 8 iterations on raw data (7710000 samples)
ICA components 2
Explained variance 100.0 %
Available PCA components 2
Channel types eeg
ICA components marked for exclusion ICA000

I don’t get any errors. The problem is that the independent components that I get are almost identical to the original EEG channels which means that the EOG channel wasn’t used for the ICA, right? Am I missing something?

You can see here the original data:

And here are the independent components:

Hello @DVa2022 and welcome to the forum!

Yes.

I think there’s some sort of misunderstanding here. When you use ica.plot() you get the traces for all components, without anything being “cleaned”.

You don’t want to compare the original data to the ICA components; you want to compare the original data to the one where you’d ica.apply()'d your ICA cleaning.

Another thing is that I believe (but I might be mistaken) that 2 components simply isn’t going to work for ICA. I would bet that if you remove one of two components, you’ll be throwing away a ton of physiological signal.

I tried using SSP to get a feel for this:

eog_projs, eog_events = mne.preprocessing.compute_proj_eog(
    filt_raw, n_grad=0, n_mag=0, n_eeg=1,
)
filt_raw.add_proj(eog_projs)
filt_raw.plot()

And here’s the original data:

And here with the SSP projection applied:

One EEG channel lost basically all of its signal. I don’t think this is a good sign, and I believe you’ll have the same issue with ICA.

Why do you rely on the HEOG channel? I always thought it’s usually VEOG that captures most “relevant” artifacts – but I might be mistaken.

Best wishes,
Richard

2 Likes

Oh, yeah, seems the peak finder doesn’t work with HEOG signals anyway, so you cannot use the HEOG channel here.

eog_projs, eog_events = mne.preprocessing.compute_proj_eog(
    filt_raw, n_grad=0, n_mag=0, n_eeg=1,
)
filt_raw.add_proj(eog_projs)
filt_raw.plot(events=eog_events)

Adding to what @richard has already said (you need at least around 30 channels to do ICA for artifact rejection), if you want to include EOG channels in your decomposition, those channels need to share the same reference with the EEG channels. Therefore, you cannot include bipolar EOG channels with your EEG channels to perform ICA.

3 Likes

Thank you, guys.
I now understand my problem thanks to you.

Richard, I used an HEOG because it’s the only EOG channel that I have.
In addition, you said “I believe you’ll have the same issue with ICA”. You’re right, I had the same issue (an EEG channel lost its signal).

cbrnr, I don’t have 30 channels or more so I guess that I won’t be using ICA.

Do you guys know if there’s another way to correct EOG artifacts without having VEOG and without having as many channels as cbrnr said?

I have two articles on my website on how to do that with ICA and regression: https://cbrnr.github.io/ You could try regression, which might work with only one channel. However, I suspect that it will not be able to remove everything.