KBB MMN & CPT Triggers used in Zambia - LeoLedesma237/LeoWebsite GitHub Wiki

For a more in-depth view of how trigger codes work when programming them in PsychoPy and how they are read by BrainVision Recorder please read KBB CPT Triggers (PsychoPy). This contains information from a pilot study done in the United States. One key difference there is the trigger box cable used contained 8 functional trigger bits, with one nonfunctional trigger bit (#6). The trigger box cables in Zambia contain three nonfunctional trigger bits (#5-7). Thus, we will need to modify the trigger codes for both the MMN & CPT Tasks and the 'Digital Port Settings' to make this functional.

In theory, we could write trigger code in PsychoPy for the tasks that are functional for two separate Digital Port Settings. However, since these settings in BrainVision Recorder have to be changed manually- and any small mistake would lead no triggers being saved, then it would be best to consider trigger codes for both tasks so the Digit Port Settings remain consistent.

Digital Port Settings

Open BrainVision Recorder and click on 'Amplifier'. Here we can see the number of trigger bits that are available for assigning a trigger type. There are two trigger types, 'Stimulus' and 'Response'. We will set the first three bits as 'Stimulus', since we have a variety of different stimuli for both tasks (Four for the MMN and Six for the CPT).

Digital Port Settings

Deciding on which trigger codes to use

Reviewing what we know about binary numbers and trigger bits, we can create an excel sheet to give us an idea of which values to designate as trigger codes to create the desired 'Stimulus' or 'Response' triggers in the EEG data.

MMN

The MMN has been shortened from its original development, now incorporating only the tones and syllable version- the words version has been removed. Because of this only four stimuli triggers are needed. Therefore, the code for this task will remain completely unchanged.

1) Tone Task

Tab: Begin Experiment

This is the tab where the name of the serial port goes.

# Import the necessary libraries
import serial

# Open a connection to the serial port (replace 'COM5' with your actual port)
ser = serial.Serial('COM5', baudrate=115200)

Tab: Begin Routine

stimulus_pulse_started = False
stimulus_pulse_ended = False

Tab: Each Frame

This is the chunk of code where most of the modifications for each researcher's study design can be written in. For starters, Tone_Task represents the name of the Sound Component in our routine. Therefore, you would want to change this to the name of your Component while keeping the .status that follows (ex: Image_Task.status). The if statements below this correspond to the name of the triggers you want to send to actiChamp. They must be numeric. We are telling PsychoPy specifically that we want our deviant stimuli to be coded in the EEG as the number 1 and our standard stimuli to be coded as the number 2. We can do this by using information already available in the excel sheet created to run the MMN task. One of these columns is named Trial_Type and contains the values Deviant or Standard for each cell. Thus, the information from that variable can be used to send the triggers we want to the EEG for later analysis. The remaining portion of the code should be left as is.

if Tone_Task.status == STARTED and not stimulus_pulse_started:

    # Check if the current stimulus is the deviant or standard
    if Trial_Type == 'Deviant':
        trigger_code = '1'  # If it's deviant
    else:
        trigger_code = '2'  # If its standard
    
    # Send the trigger signal
    win.callOnFlip(ser.write, str.encode(trigger_code))
    
    # Record the start time of the trigger pulse
    stimulus_pulse_start_time = globalClock.getTime()
    
    # Mark that the trigger pulse has started
    stimulus_pulse_started = True

if stimulus_pulse_started and not stimulus_pulse_ended:
    if globalClock.getTime() - stimulus_pulse_start_time >= 5:
        # Send the trigger signal to end the pulse
        win.callOnFlip(ser.write, str.encode('0'))
        
        # Mark that the trigger pulse has ended
        stimulus_pulse_ended = True

tab: End Experiment

port.close()

2) Speech Task

Tab: Begin Experiment

Leave this blank since we already coded this information in the previous task


Tab: Begin Routine

stimulus_pulse_started = False
stimulus_pulse_ended = False

Tab: Each Frame

For this task, the Sound Component is named Speech_Task, thus that was used for the first if statement in the code. The excel file that codes for the rows for this task also contains a column named Trial_Type that contains the values Deviant or Standard in each cell. Just like before, if the cell contains a Deviant value, then the trigger code will send the number 3. If not then the trigger code will send the number 4.

if Speech_Task.status == STARTED and not stimulus_pulse_started:

    # Check if the current stimulus is the deviant or standard
    if Trial_Type == 'Deviant':
        trigger_code = '3'  # If it's deviant
    else:
        trigger_code = '4'  # If its standard
    
    # Send the trigger signal
    win.callOnFlip(ser.write, str.encode(trigger_code))
    
    # Record the start time of the trigger pulse
    stimulus_pulse_start_time = globalClock.getTime()
    
    # Mark that the trigger pulse has started
    stimulus_pulse_started = True

if stimulus_pulse_started and not stimulus_pulse_ended:
    if globalClock.getTime() - stimulus_pulse_start_time >= 5:
        # Send the trigger signal to end the pulse
        win.callOnFlip(ser.write, str.encode('0'))
        
        # Mark that the trigger pulse has ended
        stimulus_pulse_ended = True

CPT

The CPT is the same as it was during the pilot study. It will have six stimuli codes because there are two types of stimuli (target vs non-target) and three blocks (ISI: 1 sec, 2 sec, or 4 sec). Additionally, there needs to be two types of responses coded for, one for when the spacebar is pressed during a trial and the other for when it is not. These are not correct vs incorrect

Tab: Begin Experiment

# Import the necessary libraries
import serial

# Open a connection to the serial port (replace 'COM5' with your actual port)
ser = serial.Serial('COM5', baudrate=115200)

Tab: Begin Routine

stimulus_pulse_started = False
stimulus_pulse_ended = False

response_pulse_started = False
response_pulse_ended = False

Tab: Each Frame

This section of the code relates to the triggers being sent. The first section is specifically towards the stimulus. There are six different stimuli trigger codes. Those that are even correspond to target trials and those that are odd correspond to even trials. The number size for even and odd triggers relate to the ISI of the trial.

The response triggers make up the bottom portion of the code. If the spacebar is pressed during the trial, then the trigger value 8 is sent. If the spacebar is not pressed then the trigger value 16 is sent. These values are saved as R1 and R2 in the .vmrk file. However, for some reason at the exact end of each trial there is an R2 response code that is saved- I cannot figure out how to remove it. Therefore each trial will contain a Stimulus code (1-6), followed by R1 or R2 and then ending with R2.

if image_trial.status == STARTED and not stimulus_pulse_started:

    # Check if the current stimulus is the deviant or standard
    if Trial_Type_Spec == 'Target_1':
        trigger_code = '2' 
        
    elif Trial_Type_Spec == 'Target_2':
        trigger_code = '4' 
        
    elif Trial_Type_Spec == 'Target_4':
        trigger_code = '6' 
    
    elif Trial_Type_Spec == 'Non-target_1':
        trigger_code = '1' 
    
    elif Trial_Type_Spec == 'Non-target_2':
        trigger_code = '3'
    
    else:
        trigger_code = '5'
        
    # Send the trigger signal
    win.callOnFlip(ser.write, str.encode(trigger_code))
    
    # Record the start time of the trigger pulse
    stimulus_pulse_start_time = globalClock.getTime()
    
    # Mark that the trigger pulse has started
    stimulus_pulse_started = True

if stimulus_pulse_started and not stimulus_pulse_ended:
    if globalClock.getTime() - stimulus_pulse_start_time >= 5:
        # Send the trigger signal to end the pulse
        win.callOnFlip(ser.write, str.encode('0'))
        
        # Mark that the trigger pulse has ended
        stimulus_pulse_ended = True


# code to send the trigger for a response
if image_trial_response.keys == 'space' and not response_pulse_started:
    
    # Mark that the trigger pulse has started
    response_pulse_started = True
    
    # Send the response code
    ser.write(bytes([8]))

if CPT_ISI.status == FINISHED and not response_pulse_started:
    
    # Mark that the trigger pulse has started
    response_pulse_started = True
    
    # Send the response code
    ser.write(bytes([16]))

Tab: End Experiment

port.close()

Are the triggers sending?

MMN

The point of the trigger code is to populate the inside of .vmrk files with trigger information. This includes the type of trigger send (Stimuli vs Response) and a latency measure in the sampling rate units. Below we will have three pictures that should all tell the same information. Beginning with the excel file that programs the MMN task.

Programming Excel

Every participant will receive the same order of stimuli as seen below. Starting with a standard sound that will continue to play until the '5' trial where a deviant appears. The next deviant appears in trial '17'. From the trigger codes in the previous sections, we know that the standard tone is coded as S2 and the deviant tone is coded as S1.

MMN first 20 trials

.VMRK file

Once an EEG recording is made a .vmrk file is also saved. It can be opened through NotePad and its contents hold information about the trial types, their responses, and the latencies of both. This trigger information must match the information from what was programmed above. We first skip the first two lines for they are not related to the triggers of the task. The 'R 2' is noise in the data, we do not care for it and can ignore it. What we are interested is in seeing if S1 is present for trial 5 and 17 as coded above. To verify this, we take these numbers, multiple them by 2 (to take into account the R 2 taking up a row number in each trial) and then adding 1 (to take into account the first two rows not being related to triggers). Thus, there should be an S1 for row 11 and row 35.

Screenshot 2024-08-08 091349

EEG Recording

When we open the .vmrk file in EEGLAB, we can use a GUI to plot the EEG data. This plot also includes all markers that were recorded during the session. The first deviant sound occurs at '6608' seconds * sampling rate. To convert this value into seconds, we just divide it by the sampling rate, which for this recording was 500, which will equal 13.21 seconds from when the recording started.

MMN First deviant in the EEG

Lastly, using the pop_squeezevents(EEG) function from ERPLAB we can see the number of events in our data. We can see that we have 500 standard trials and 50 deviant trials as coded for. (*Disclaimer: * It should show the frequency of event markers for the syllable MMN condition, but the recording was abruptly ended before it started).

All Events in MMN EEG data

CPT

Programming Excel

CPT First 18 Trials

.VMRK file

CPT first 18 Trials  vmrk

EEG Recording