If you have a question or issue with MNE-Python, please include the following info:
- MNE-Python version: 0.23.0
- operating system: windows
Hi!
I found out that the events shift after add_channels operations. The processing includes cropping the EEG signal according to the experimental paradigm with the following codes. The onset point is stored in the alighmentInfo. I plot the eeg_raw before and after cropping to see whether after cropping, the events still match the signals. And the answer is yes. It seems that events from annotations do not change, what changes is the eeg_raw. first_time.
# read and cut EEG #
eeg_raw = mne.io.read_raw_eeglab(eeg_fName, preload=True)
eeg_raw.plot()
eeg_raw.set_montage('standard_1020')
eeg_raw.crop(tmin=alignmentInfo.loc[(alignmentInfo['sessionIdx'] == session_idx) &
(alignmentInfo['contraction_type'] == 'iVC'),
'EEG'].values[0] / eeg_raw.info['sfreq'])
eeg_raw.plot()
events, events_id = mne.events_from_annotations(eeg_raw)
Then I would add EMG channels into the dataset. Basically, this step includes reading, cropping, and filtering the EMG signals.
# read and cut EMG
ch_types = ['emg'] * 8
emg_ch_names = ['FDS', 'FCU', 'FCR', 'ECU', 'ECRL', 'BBS', 'TBL', 'LD']
info = mne.create_info(ch_names=emg_ch_names, sfreq=sfreq_emg, ch_types=ch_types)
emg_data = pd.read_csv(emg_fName, header=None, skiprows=3,
sep=' ', usecols=np.arange(0, 8), skipfooter=0, engine='python')
emg_data = emg_data.iloc[alignmentInfo.loc[(alignmentInfo['sessionIdx'] == session_idx) &
(alignmentInfo['contraction_type'] == 'iVC'),
'EMG'].values[0]:, :].values
emg_data = emg_data.T / 1e6 # uV to V
emg_raw = mne.io.RawArray(emg_data, info)
emg_raw.filter(l_freq=10, h_freq=250, picks='all')
emg_raw.resample(sfreq=sfreq_eeg)
# data alighment
t_end = min(len(eeg_raw), len(emg_raw)) / sfreq_eeg - 1/sfreq_eeg # find the shortest length of EEG and EMG
emg_raw.crop(tmax=t_end)
eeg_raw.crop(tmax=t_end)
emg_raw.info['highpass'] = eeg_raw.info['highpass']
# In reality, EEGs are bandpassed from 1-45Hz, EMGs are bandpassed from 10-200Hz
emg_raw.info['lowpass'] = eeg_raw.info['lowpass']
eeg2merge_raw = eeg_raw.copy()
Here’s the problematic function.
aligned_raw = eeg2merge_raw.add_channels([emg_raw])
After doing add_channels, the events no longer match the signals. I look deeper into what changed. It seems that the aligned_raw.first_time is as same as eeg_raw.first_time. However, mne.events_from_annotations(aligned_raw) = mne.events_from_annotations(aligned_raw) + aligned_raw.first_time.
I think this could be misleading for the users of MNE as the changes is not presumably intended. At the end, I resolve the issue with the following codes.
# calibrate annotation
onsets = events[:, 0] / sfreq_eeg - aligned_raw.first_time
durations = events[:, 1]
descriptions = events[:, 2]
annotations = mne.Annotations(onsets, durations, descriptions)
aligned_raw.set_annotations(annotations)
I would like to know what happens when we do the add_channels operation.