MNE find events

Greetings,

I’m doing a project using MEG and I have been trying to find a reliable way to convert STIM channel signals to events to properly filter my epochs based on more than 1 event code at the same time.

I have checked the MNE tutorial about it and some posts here on the forum, but I’m still unsure how to address it due to the way that my event codes were sent to the recording machine.
Instead of using the composite channel as the reference for all event ID’s as in the example in the tutorial page, my event codes are organized in the following pattern:

STI001 to STI008: Each of the channels has a unique event recorded. All events have the same event IDs but represent different event codes.(Event ID value 5, event codes 1,2,4,8,16…) Event codes are independent for each channel.

STI009 to STI016: The events from all channels together work as a composite channel to give a single number.

Since I’m not using a single composite channel, I’m using a different way to get my event IDs (code shared below).

Using the code below, I have been having some issues with other MNE functions since I also have to adapt it for all of them.

I wonder if anybody else had the same issue. Using MNE tools, Is there a better option to filter my epochs using multiple event IDs from different channels at the same time?

Thank you.

####################################################################
# Code to read events.

events = []
for i in range(9):
    tmp = mne.find_events(raw, stim_channel='STI00'+str(i+1))
    events.append(np.delete(tmp,1,1))

for i in range(9,16):
    tmp = mne.find_events(raw, stim_channel='STI0'+str(i+1))
    events.append(np.delete(tmp,1,1))

ts = np.array([0,0])

for channel in events:
   ts=np.concatenate((ts,channel[:,0]))
    
ts=np.sort(np.unique(ts))

composite = np.zeros_like(range(17))
empty_row = np.zeros_like(range(16))

for t in ts:
    for counter,channel in enumerate(events):
        if t in channel:
            empty_row[counter]=1
    composite=np.vstack((composite,np.concatenate(([t],empty_row))))
    empty_row.fill(0)        

factor = np.array([1,2,4,8,16,32,64,128,1,2,4,8,16,32,64,128])

comp2 = np.array([0,0,0])

for row in composite:
    tmp = np.array([0,0,0])
    tmp = [row[0],np.matmul(row[1:9],factor[0:8]),np.matmul(row[9:17],factor[8:16])]
    comp2 = np.vstack((comp2,tmp))
    
del tmp, composite,channel,counter,empty_row,factor,t,ts,events,i,row

@drammock

Hello @brenorabelo, just FYI, to get proper code formatting, the code blocks need to be enclosed in triple-backticks. I’ve edited your posting accordingly.
Best wishes,
Richard

@brenorabelo I think it would be helpful if you could share some example data, otherwise it will be difficult for others to provide help

@richard Thank you for the suggestion. I have uploaded a small sample of my data.

Here is the link with it:

https://drive.google.com/drive/folders/1EGLMYYC0jWnEu-_tAHmG6UiPvw21EwaF?usp=sharing

Before I answer, a motivating remark: You cannot have coincident events in an events array. There can only be one event per sample number. So there are basically 2 things you can do to work around this constraint:

  1. to encode the presence of (say) 2 different stimuli at the same time, you create a unique event code that means “stim1 and stim2 were both present”

  2. you can generate multiple event arrays to pass in at different points of your code.

In some cases you may want to make use of both of those approaches.

Turning now to your specific data: I didn’t have time to fully follow the logic in your code 100%, but here’s a quick sketch of a possibly simpler way to do it. It will need some adjusting (I’ve tried to comment where I think that is needed), but I think it at least simplifies the step of aggregating the binary codes on channels 9-16:

import numpy as np
import pandas as pd
import mne

raw = mne.io.read_raw_fif('sampledatax_sss.fif')

# first 8 channels (each channel encodes a specific event kind)
events = np.empty((0, 3), dtype=int)
for i in range(1, 9):
    tmp = mne.find_events(raw, stim_channel=f'STI{i:03}')
    # HERE WE'RE TREATING THEM AS BINARY, IN ORDER TO LATER GET UNIQUE CODES
    # REPRESENTING EVERY POSSIBLE COMBINATION:
    tmp[:, -1] = 2 ** i
    events = np.vstack([events, tmp])
events = events[np.argsort(events[:, 0])]
# sum coincident events to create unique event codes (this is approach #1 from above)
df = pd.DataFrame(events)
events = df.groupby(0).aggregate('sum').reset_index().values

Next, do the same thing on the channels that were actually meant to be interpreted as binary:

# next 8 channels (taken together to form a binary code)
composite = np.empty((0, 3), dtype=int)
for i in range(9, 17):
    tmp = mne.find_events(raw, stim_channel=f'STI{i:03}')
    tmp[:, -1] = 2 ** (i - 9)
    composite = np.vstack([composite, tmp])
# sum coincident events to get the (intended) binary values
df = pd.DataFrame(composite)
composite = df.groupby(0).aggregate('sum').reset_index().values

Next you’ll want to combine the composite array with the events array. Be careful here, because so far nothing has been done to make sure the event codes in those 2 arrays are unique! Consider simply adding some number like 2 ** 9 to the last column of one of them, just to make sure they’re distinct. Then after vstacking the two arrays, you’ll again face the problem of coincident events, and you’ll need to decide how you want to encode those combinations.

Hopefully this at least points you in a useful direction?