Uncategorized

Adjusting for lag

One thing I hate is heisenbugs. One recent one is that some of the triggers being sent by E-prime to our EEG systems have been incorrect. For instance, the first four triggers should be 1, 5, 6, 2. But in two of the last five participants, they've been 1, 5, 7, 3.

There are no methods that I know of that would lead E-prime to send anything other that the trigger codes that I provide it. No changes were made to the E-prime program, and I confirmed that the timing files generated by E-prime show that the correct codes should have been sent.

I dug in a little more, and confirmed that in these unusual participants, it was even-numbered trials that were being altered in the EEG log, each one increased by 1. The triggers are sent via parallel port, which suggested to me that perhaps the 1 pin was stuck in the on position - six:0110 -> seven:0111, or two:0010 -> three:0011. Further evidence comes from extraneous 1 triggers appearing the EEG log file. Double-checking that the parallel cable was firming inserted in the the stimulus presentation computer and the EEG amp seems to have stopped the problem (fingers-crossed).

However, that leaves the problem of what to do with the triggers that were actually sent. I have scripts that re-label events in the EEG marker file based on participants' previous responses in a different task. However, these expect specific trigger codes. With different codes, those scripts fail. Moreover, if E-prime was supposed to send the sequence 8, 9, 12, 1, the trigger labeling renders this: 9, 9, 13, 1. When two identical triggers are sequential, E-prime omits the second one, meaning that some events were not logged at all.

The solution I devised was to use E-prime's timing information to update the recorded triggers and create a corrected marker file. This requires matching the clocks for the amp and E-prime. First, there's a constant offset between when the EEG file started and when the E-prime task started. That's relatively easy: Find the first trial in the E-prime log and the first trial in the EEG (using order to confirm which trigger corresponds to the trial). Then just subtract the difference.

One caveat is that the E-prime log is in milliseconds and the EEG marker file is in sample points. We sample at 2,000 Hz, so 1 sample point = 0.5 ms. In other words, the E-prime timestamps must be multiplied by 2 to make them on the same time scale as the EEG. That makes for some simple python code:

def eprime2eeg(timestamp, offset):
    epnt = (2 * timestamp) + offset
    return(epnt)

However, the problem is slightly more complicated. Not only do the E-prime and EEG clocks start at different times, but they also run at different rates. The drift is not tremendous, but it does add up to ~50ms over the course of the study, more than enough to alter findings. Here, I made the assumption that the drift was linear. All I needed then was the drift factor to apply to each E-prime timepoint to correctly convert it into a sample point. Identifying the last trigger from the E-prime file and it's marker in the marker file provides a second point that can then be plugged into the point-slope form.

The final python code uses two functions. The first one takes two (E-prime, EEG timepoint) pairs and creates a function to convert and E-prime ms timepoint into an EEG sample point. The second one takes an E-prime ms timepoint and a conversion function and returns the EEG sample point:

def create_fx(point1, point2):
    """ 
    Returns a function for converting E-prime timestamps to EEG pnts.
    Usage: f_x = create_fx(point1, point2)
    point1 and point2 are lists of length 2 which correspond to 
    (E-prime, EEG) timestamp pairs
    """
    
    # m (slope) = rise / run
    m = (point2[1] - point1[1]) / (point2[0] - point2[1])
    # use y = mx + b to solve for b
    b = point[1] - (m * point1[0])

    # create conversion function
    def f_x(x, m0 = m, b0 = b):
        y = (m0 * x) + b
        return(y)

    return(f_x)

def eprime2pnt(timestamp, f_x):
    """
    Return the E-prime timestamp in sample points using the given f_x
    """
    # change from ms to pnt
    epnt = float(timestamp) * 2
    epnt = f_x(epnt)
    return(epnt)