Specify calibration, validation, and drift correction individually using IOHub with eyelink II

Hi there,

Windows 7
Psychopy 1.84.2 Coder

We have successfully implemented eyelink II eyetracking (code at the bottom). All of the calibration, validation, and drift correction happens through the tracker.runSetupProcedure() command, which essentially brings up a menu of things that can be done.

What I would like is, rather than specify the full menu of things, to separately specify just a calibration procedure, or just a validation procedure, or just a drift correction, without having to bring up the full menu.

Something like:

tracker.runSetupProcedure() #initial procedure

while Experiment == running

   #Do some trials of an experiment

   tracker.doValidation() #validate after some trials to make sure things still okay
   if validation == poor:
      tracker.runSetupProcedure() #if bad validation, do another full calibration
   elif validation == fine:
      tracker.doDriftCorrection() #if validation is fine, just do a drift correction

What commands would allow something like this? My current eyetracking code is below, along with commented-out attempts to implement something like the above.

Thank you in advance for any guidance!

'''
tracking with ioHub framework.
Simplification of https://github.com/psychopy/psychopy/blob/master/psychopy/demos/coder/iohub/eyetracking/simple.py
'''
from psychopy import visual,event
import math
from psychopy.iohub.client import launchHubServer
#import pylink
#from EyeLinkCoreGraphicsPsychopy import EyeLinkCoreGraphicsPsychopy
#from eyetracker.hw.sr_research.eyelink.EyeTracker import _doDriftCorrect, _applyDriftCorrect
 
#iohub configuration
iohub_tracker_class_path = 'eyetracker.hw.sr_research.eyelink.EyeTracker'
eyetracker_config = dict()
eyetracker_config['name'] = 'tracker'
eyetracker_config['model_name'] = 'EYELINK 2'
eyetracker_config['runtime_settings'] = dict(enable= True,name= "tracker",monitor_event_types= "[BinocularEyeSampleEvent, ]")
io = launchHubServer(**{iohub_tracker_class_path: eyetracker_config})
keyboard = io.devices.keyboard
display = io.devices.display
tracker = io.devices.tracker


#tk = pylink.EyeLink('100.1.1.1') 

r = tracker.runSetupProcedure()


#tracker.sendCommand(['d'])
#r = tracker._addCommandFunctions()
#r= tracker.doTrackerSetup()
#r=tk._doDriftCorrect(640, 450, 1, 1) 
#drift= tracker._addCommandFunctions(self)


tracker.setRecordingState(True)# Start Recording Eye Data

window = visual.Window(fullscr=True,monitor='myScreen',size=(1280,900),units='pix', allowGUI=False)

gazeDot =visual.GratingStim(window,tex=None, mask="gauss",
                             pos=(0,0 ),size=(66,66),color='green',
                                                units='pix')


run_trial=True 
while run_trial :
    gpos=tracker.getPosition() #sample eye position

    if type(gpos) in [tuple,list]: #if possible, draw the eye position
        dist = math.sqrt((gpos[0] - fixation.pos[0])**2 + (gpos[1] - fixation.pos[1])**2)
        gazeDot.setPos([gpos[0],gpos[1]])
        gazeDot.draw()
        
    window.flip()
    
    keys=event.getKeys()
    if keys !=[]:# press any keys to quit
        run_trial=False
        
        
tracker.setConnectionState(False)
io.quit()

As a general note, a psychopy experiment can use either iohub or the pylink module directly to interface with an eyelink, but both can not be used at the same time.

For eyelink, the iohub runSetupProcedure puts it into camera setup mode, which allows calibration / validation by using the ‘c’ and ‘v’ keys.

The iohub eyelink class also has a sendCommand method, which you might be able to use to do online / runtime DC if there is a text command equivalent to the pylink._doDriftCorrect (which I do not recall from memory right now).

Thanks.

Hi Sol,
I am currently working in Psychopy 1.84.x Coder, and I have a follow up question on recalibration during the experiment.

I have written a few functions to modularize some of the forced fixation components of my experiment. code below…

        def recalibrate():
            '''if subject needs to recalibrate the eye tracker in the middle 
            of the experiment, then call this fucntion. 
            '''
            t= tracker.isReportingEvents()
            flip_time = win.flip()
            self.hub.sendMessageEvent("Subject has started recalibration during the experiment", sec_time = flip_time)
            if ('c' in keyboard.getPresses()):
                r = tracker.runSetupProcedure()
            flip_time = win.flip()
            self.hub.sendMessageEvent("Subject finished recalibration during the experiment", sec_time = flip_time)
            
        def quit_and_save():
            '''quits experiment if subject hits escape key'''
            if 'escape' in keyboard.getPresses():
                flip_time = win.flip()
                self.hub.sendMessageEvent("Subject hit escape button and quit the experiment",sec_time = flip_time)
                tracker.setConnectionState(False)
                df = pd.DataFrame(trial_record)
                df.to_csv(saved_data_filename)
                core.quit()
        
        def force_fix(gaze_ok_region, time, textList, drawEye = True, add_text = False, drawFix = True):
            self.hub.clearEvents('all') #clear events in iohub
            fixation_done = False #variable for while loop
            fixation_started = False #variable for internal book-keeping
            
            #START TRACKING THE EYE
            flip_time = win.flip()
            self.hub.sendMessageEvent(text = "eye tracker start recording", sec_time = flip_time)
            self.hub.sendMessageEvent(text ="force_fix function started", sec_time = flip_time)
            tracker.setRecordingState(True)
            t= tracker.isReportingEvents()
            
            #loop through forced fixation screen 
            while fixation_done is False:
                quit_and_save() #allow subject to exit if needed
                recalibrate() #allow subject to recalibrate if need be
                
                #set up variables to make sure subject is looking into the gaze okay region 
                gpos = tracker.getLastGazePosition() 
                valid_gaze_pos = isinstance(gpos, (tuple, list))
                gaze_in_region = valid_gaze_pos and gaze_ok_region.contains(gpos)
                
                #should subject eye position be drawn on screen?
                if (valid_gaze_pos is True) & (drawEye is True): 
                    gaze_dot.pos = gpos 
                    gaze_dot.draw()
                
                if gaze_in_region is True:
                   if not fixation_started:
                       fixation_started = True
                       fixation_start_time = clock.getTime()
                   elif clock.getTime() - fixation_start_time > time:
                       fixation_done = True 
                else:
                   fixation_started = False
                
                if add_text is True:
                    for t in textList:
                        t.draw()
                if drawFix is True:
                    fixation.draw()
                win.flip()
            tracker.setRecordingState(False)
            flip_time = win.flip()
            self.hub.sendMessageEvent(text = "eye tracker stop recording", sec_time = flip_time)
            self.hub.sendMessageEvent(text ="force_fix function exiting", sec_time = flip_time)

When I call the force_fix function in my script and try to recalibrate by pressing “c” on the keyboard (call the tracker.runSetupProcedure() function nested within the recalibrate() function I defined) I arrive at the camera setup mode as expected. However, the dot that is usually displayed on the screen, which indicates where subjects should fixate in order for the calibration process to continue, does not appear on the screen. Do I need to do the recalibration procedure on a different window than the window used for the experiment in order to get the desired result? Keep in mind that at the beginning of the experiment, before I even define the window, I call the tracker.runSetupProcedure() function and the calibration dot appears on the computer monitor in front of the participants.

If I understand correctly, and the issue is that the tracker.runSetupProcedure() calibration graphics are not appearing after the first time the method is called, then maybe the current psychopy window is hiding the calibration graphics window. To temporarily minimize the psychopy window so that the iohub calibration window can be seen please try code as suggested here.

Thanks.

Hi Sol,

Thanks for the timely response.
I was able to get it to work with the suggested code.

The other problem that occurs now is when I exit the calibration procedure the experiment does not continue despite the window of the experiment appearing again. I tried using the keystroke “o” to continue with the experiment after recalibration, but that key does not work. Therefore, I have to hit the ‘esc’ key to exit the calibration procedure, however, I think this turns off the eye tracker connection state and eye data is no longer communicated to the experiment process.

-Devi

Hi, I have now the same issue. How did you manage to recalibrate without turning off the eye tracker connexion ?

I wrote a custom method for a class where the user can type “c” on the keyboard and it will recalibrate the eyetracker. I think you can use some of the lines of code below for your needs (i.e., "hideWindow, “result=…”, “showWindow”.

def checkRecalibrate(self,):
    if "c" in self.k.getKeys():
        flip_time = self.w.flip()
        self.io.sendMessageEvent(text="Recalibrating event start", sec_time=flip_time)
        if self.et.isRecordingEnabled():
            self.et.setRecordingState(False)
        # Minimize the PsychoPy window if needed
        hideWindow(self.w)
        # Display calibration gfx window and run calibration.
        result = self.et.runSetupProcedure(calibration_args=constants.eyetracker_config["calibration"])
        #print("Calibration returned: ", result)
        # Maximize the PsychoPy window if needed
        showWindow(self.w)
        self.et.setRecordingState(True)
        flip_time = self.w.flip()
        self.io.sendMessageEvent(text="Recalibrating event end", sec_time=flip_time)
    self.io.clearEvents('all')
1 Like