def wait_for_fixation_start(self): """Returns starting time and position when a simulated fixation is started""" # function assumes a 'fixation' has started when 'gaze' position remains reasonably # stable for five samples in a row (same as saccade end) maxerr = 3 # pixels # wait for reasonably stable position xl = [] # list for last five samples (x coordinate) yl = [] # list for last five samples (y coordinate) moving = True while moving: npos = self.sample() xl.append(npos[0]) # add newest sample yl.append(npos[1]) # add newest sample if len(xl) == 5: # check if deviation is small enough if max(xl) - min(xl) < maxerr and max(yl) - min(yl) < maxerr: moving = False # remove oldest sample xl.pop(0) yl.pop(0) # wait for a bit, to avoid immediately returning (runs go faster than mouse moves) clock.pause(10) return clock.get_time(), (xl[len(xl) - 1], yl[len(yl) - 1])
def wait_for_saccade_end(self): """Returns ending time, starting and end position when a simulated saccade is ended""" # function assumes that a 'saccade' has ended when 'gaze' position remains reasonably # (i.e.: within maxerr) stable for five samples # for saccade start algorithm, see wait_for_fixation_start stime, spos = self.wait_for_saccade_start() maxerr = 3 # pixels # wait for reasonably stable position xl = [] # list for last five samples (x coordinate) yl = [] # list for last five samples (y coordinate) moving = True while moving: # check positions npos = self.sample() xl.append(npos[0]) # add newest sample yl.append(npos[1]) # add newest sample if len(xl) == 5: # check if deviation is small enough if max(xl)-min(xl) < maxerr and max(yl)-min(yl) < maxerr: moving = False # remove oldest sample xl.pop(0); yl.pop(0) # wait for a bit, to avoid immediately returning (runs go faster than mouse moves) clock.pause(10) return clock.get_time(), spos, (xl[len(xl)-1],yl[len(yl)-1])
def wait_for_saccade_end(self): """Returns ending time, starting and end position when a simulated saccade is ended""" # function assumes that a 'saccade' has ended when 'gaze' position remains reasonably # (i.e.: within maxerr) stable for five samples # for saccade start algorithm, see wait_for_fixation_start stime, spos = self.wait_for_saccade_start() maxerr = 3 # pixels # wait for reasonably stable position xl = [] # list for last five samples (x coordinate) yl = [] # list for last five samples (y coordinate) moving = True while moving: # check positions npos = self.sample() xl.append(npos[0]) # add newest sample yl.append(npos[1]) # add newest sample if len(xl) == 5: # check if deviation is small enough if max(xl) - min(xl) < maxerr and max(yl) - min(yl) < maxerr: moving = False # remove oldest sample xl.pop(0) yl.pop(0) # wait for a bit, to avoid immediately returning (runs go faster than mouse moves) clock.pause(10) return clock.get_time(), spos, (xl[len(xl) - 1], yl[len(yl) - 1])
def wait_for_fixation_start(self): """Returns starting time and position when a simulated fixation is started""" # function assumes a 'fixation' has started when 'gaze' position remains reasonably # stable for five samples in a row (same as saccade end) maxerr = 3 # pixels # wait for reasonably stable position xl = [] # list for last five samples (x coordinate) yl = [] # list for last five samples (y coordinate) moving = True while moving: npos = self.sample() xl.append(npos[0]) # add newest sample yl.append(npos[1]) # add newest sample if len(xl) == 5: # check if deviation is small enough if max(xl)-min(xl) < maxerr and max(yl)-min(yl) < maxerr: moving = False # remove oldest sample xl.pop(0); yl.pop(0) # wait for a bit, to avoid immediately returning (runs go faster than mouse moves) clock.pause(10) return clock.get_time(), (xl[len(xl)-1],yl[len(yl)-1])
def calibrate(self): """Dummy calibration""" print("Calibration would now take place") clock.pause(1000)
def calibrate(self, animated=None, skip_bad_points=False): """Calibrates the eye tracking system arguments None keyword arguments animated -- bool. Set to True to show a parrot animation instead of calibration dots, or False to use standard points. Set to None to use default option. skip_bad_points -- bool. Intelligaze will skip difficult points when set to True. (Default = False) returns success -- returns True if calibration succeeded, or False if not; in addition a calibration log is added to the log file and some properties are updated (i.e. the thresholds for detection algorithms) """ # Process animated keyword argument. if animated is None: animated = self.animated_calibration if animated: img = "ANIMATION:PARROT" else: img = "" # Show a message. self.screen.clear() self.screen.draw_text(text="Running calibration in the foreground...", fontsize=20) self.disp.fill(self.screen) self.disp.show() # CALIBRATION # Re-run the calibration until it was approved by the user. quited = False calibration_approved = False while not calibration_approved: # Wait for the calibration to finish. status, improve = self.alea.calibrate(image=img, \ skip_bad_points=skip_bad_points) # Construct a message string. if status == 0: calib_str = "Calibration completed!" else: calib_str = "Calibration failed!" if improve: calib_str += "\n\nWARNING: IntelliGaze recommends repeating the calibration to improve accuracy." calib_str += "\n\n\nPress R to retry, or Space to continue." # Show calibration results. self.screen.clear() self.screen.draw_text(text=calib_str, fontsize=20) self.disp.fill(self.screen) self.disp.show() # Wait for user input. key = None while key not in ["r", "Space", "space", "q"]: key, keytime = self.kb.get_key(keylist=['q', 'r', 'space'], timeout=None, flush=True) # Process key press. if key in ["q", "Space", "space"]: calibration_approved = True if key == "q": quited = True # Calibration failed if the user quited. if quited: return False # NOISE CALIBRATION # Present noise calibration instructions. self.screen.clear() self.screen.draw_text( text="Noise calibration. Please look at the dot, and press any key to start.", fontsize=20, \ pos=(int(self.dispsize[0]/2),int(self.dispsize[1]*0.3))) self.screen.draw_fixation(fixtype="dot") self.disp.fill(self.screen) self.disp.show() # Wait for a keypress. key, keytime = self.kb.get_key(keylist=None, timeout=None, \ flush=True) # Start with empty lists. err = {'LX': [], 'LY': [], 'RX': [], 'RY': []} var = {'LX': [], 'LY': [], 'RX': [], 'RY': []} # Start streaming data so that samples can be obtained. self.start_recording() self.log("noise_calibration_start") # Present a central fixation. x = int(float(self.dispsize[0]) / 2.0) y = int(float(self.dispsize[1]) / 2.0) self.screen.clear() self.screen.draw_fixation(fixtype="dot", pos=(x, y)) self.disp.fill(self.screen) t0 = self.disp.show() # Collect at least 10 samples, and wait for at least 1 second. i = 0 while (i < 10) or (clock.get_time() - t0 < 1000): # Get new sample. gx, gy = self.sample() if (gx > 0) and (gy > 0): i += 1 err["LX"].append(abs(float(x) - float(gx))) err["LY"].append(abs(float(y) - float(gy))) err["RX"].append(abs(float(x) - float(gx))) err["RY"].append(abs(float(y) - float(gy))) for k in var.keys(): var[k].append(err[k][-1]**2) clock.pause(int(self.sampletime)) # Stop streaming. self.log("noise_calibration_stop") self.stop_recording() # Compute the RMS noise for the calibration points. xnoise = (math.sqrt(sum(var['LX']) / float(len(var['LX']))) + \ math.sqrt(sum(var['RX']) / float(len(var['RX'])))) / 2.0 ynoise = (math.sqrt(sum(var['LY']) / float(len(var['LY']))) + \ math.sqrt(sum(var['RY']) / float(len(var['RY'])))) / 2.0 self.pxdsttresh = (xnoise, ynoise) # AFTERMATH # store some variables pixpercm = (self.dispsize[0] / float(self.screensize[0]) + \ self.dispsize[1]/float(self.screensize[1])) / 2 screendist = settings.SCREENDIST # calculate thresholds based on tracker settings self.accuracy = ( \ (pix2deg(screendist, sum(err['LX']) / float(len(err['LX'])), pixpercm), \ pix2deg(screendist, sum(err['LY']) / float(len(err['LY'])), pixpercm)), \ (pix2deg(screendist, sum(err['RX']) / float(len(err['RX'])), pixpercm), \ pix2deg(screendist, sum(err['RY']) / float(len(err['RY'])), pixpercm))) self.pxerrdist = deg2pix(screendist, self.errdist, pixpercm) self.pxfixtresh = deg2pix(screendist, self.fixtresh, pixpercm) self.pxaccuracy = ( \ (sum(err['LX']) / float(len(err['LX'])), \ sum(err['LY']) / float(len(err['LY']))), \ (sum(err['RX']) / float(len(err['RX'])), \ sum(err['RY']) / float(len(err['RY'])))) self.pxspdtresh = deg2pix(screendist, self.spdtresh / 1000.0, pixpercm) # in pixels per millisecond self.pxacctresh = deg2pix(screendist, self.accthresh / 1000.0, pixpercm) # in pixels per millisecond**2 # calibration report self.log("pygaze calibration report start") self.log("accuracy (degrees): LX={}, LY={}, RX={}, RY={}".format( \ self.accuracy[0][0], self.accuracy[0][1], self.accuracy[1][0], \ self.accuracy[1][1])) self.log("accuracy (in pixels): LX={}, LY={}, RX={}, RY={}".format( \ self.pxaccuracy[0][0], self.pxaccuracy[0][1], \ self.pxaccuracy[1][0], self.pxaccuracy[1][1])) self.log("precision (RMS noise in pixels): X={}, Y={}".format( \ self.pxdsttresh[0],self.pxdsttresh[1])) self.log("distance between participant and display: {} cm".format( \ screendist)) self.log("fixation threshold: {} pixels".format(self.pxfixtresh)) self.log("speed threshold: {} pixels/ms".format(self.pxspdtresh)) self.log("acceleration threshold: {} pixels/ms**2".format( \ self.pxacctresh)) self.log("pygaze calibration report end") return True
def calibrate(self, calibrate=True, validate=True): """Calibrates the eye tracking system arguments None keyword arguments calibrate -- Boolean indicating if calibration should be performed (default = True) validate -- Boolean indicating if validation should be performed (default = True) returns success -- returns True if calibration succeeded, or False if not; in addition a calibration log is added to the log file and some properties are updated (i.e. the thresholds for detection algorithms) """ # TODO: # add feedback for calibration (e.g. with iV_GetAccuracyImage (struct ImageStruct * imageData) for accuracy and iV_GetEyeImage for cool eye pictures) # example: res = iViewXAPI.iV_GetEyeImage(byref(imageData)) # ImageStruct has four data fields: # imageHeight -- int vertical size (px) # imageWidth -- int horizontal size (px) # imageSize -- int image data size (byte) # imageBuffer -- pointer to image data (I have NO idea what format this is in) # configure calibration (NOT starting it) calibrationData = CCalibration( 9, 1, 0, 1, 1, 0, 127, 1, 15, b"" ) # (method (i.e.: number of points), visualization, display, speed, auto, fg, bg, shape, size, filename) # setup calibration res = iViewXAPI.iV_SetupCalibration(byref(calibrationData)) if res != 1: err = errorstring(res) raise Exception( "Error in libsmi.SMItracker.calibrate: failed to setup calibration; %s" % err) # calibrate cres = iViewXAPI.iV_Calibrate() # validate if calibration returns succes if cres == 1: cerr = None vres = iViewXAPI.iV_Validate() # handle validation errors if vres != 1: verr = errorstring(vres) else: verr = None ## # TEST # ## res = iViewXAPI.iV_GetAccuracyImage(byref(imageData)) ## self.log("IMAGEBUFFERSTART") ## self.log(imageData.imageBuffer) ## self.log("IMAGEBUFFERSTOP") ## print("Image height: %s, image width: %s, image size: %s" % (imageData.imageHeight,imageData.imageWidth, imageData.imageSize)) ## print imageData.imageBuffer ## ######## # handle calibration errors else: cerr = errorstring(cres) # return succes if cerr == None: print("libsmi.SMItracker.calibrate: calibration was succesful") if verr == None: print("libsmi.SMItracker.calibrate: validation was succesful") # present instructions self.disp.fill() # clear display self.screen.draw_text( text= "Noise calibration: please look at the dot\n\n(press space to start)", pos=(self.dispsize[0] / 2, int(self.dispsize[1] * 0.2)), center=True) self.screen.draw_fixation(fixtype='dot') self.disp.fill(self.screen) self.disp.show() self.screen.clear() # clear screen again # wait for spacepress self.kb.get_key(keylist=['space'], timeout=None) # show fixation self.disp.fill() self.screen.draw_fixation(fixtype='dot') self.disp.fill(self.screen) self.disp.show() self.screen.clear() # wait for a bit, to allow participant to fixate clock.pause(500) # get samples sl = [ self.sample() ] # samplelist, prefilled with 1 sample to prevent sl[-1] from producing an error; first sample will be ignored for RMS calculation t0 = clock.get_time() # starting time while clock.get_time() - t0 < 1000: s = self.sample() # sample if s != sl[-1] and s != (-1, -1) and s != (0, 0): sl.append(s) # calculate RMS noise Xvar = [] Yvar = [] for i in range(2, len(sl)): Xvar.append((sl[i][0] - sl[i - 1][0])**2) Yvar.append((sl[i][1] - sl[i - 1][1])**2) XRMS = (sum(Xvar) / len(Xvar))**0.5 YRMS = (sum(Yvar) / len(Yvar))**0.5 self.pxdsttresh = (XRMS, YRMS) # calculate pixels per cm pixpercm = (self.dispsize[0] / float(self.screensize[0]) + self.dispsize[1] / float(self.screensize[1])) / 2 # get accuracy res = 0 i = 0 while res != 1 and i < self.maxtries: # multiple tries, in case no (valid) sample is available res = iViewXAPI.iV_GetAccuracy( byref(accuracyData), 0) # 0 is for 'no visualization' i += 1 clock.pause(int(self.sampletime)) # wait for sampletime if res == 1: self.accuracy = ( (accuracyData.deviationLX, accuracyData.deviationLY), (accuracyData.deviationLX, accuracyData.deviationLY) ) # dsttresh = (left tuple, right tuple); tuple = (horizontal deviation, vertical deviation) in degrees of visual angle else: err = errorstring(res) print( "WARNING libsmi.SMItracker.calibrate: failed to obtain accuracy data; %s" % err) self.accuracy = ((2, 2), (2, 2)) print( "libsmi.SMItracker.calibrate: As an estimate, the intersample distance threshhold was set to it's default value of 2 degrees" ) # get distance from screen to eyes (information from tracker) res = 0 i = 0 while res != 1 and i < self.maxtries: # multiple tries, in case no (valid) sample is available res = iViewXAPI.iV_GetSample(byref(sampleData)) i += 1 clock.pause(int(self.sampletime)) # wait for sampletime if res == 1: screendist = sampleData.leftEye.eyePositionZ / 10.0 # eyePositionZ is in mm; screendist is in cm else: err = errorstring(res) print( "WARNING libsmi.SMItracker.calibrate: failed to obtain screen distance; %s" % err) screendist = settings.SCREENDIST print( "libsmi.SMItracker.calibrate: As an estimate, the screendistance was set to it's default value of 57 cm" ) # calculate thresholds based on tracker settings self.pxerrdist = deg2pix(screendist, self.errdist, pixpercm) self.pxfixtresh = deg2pix(screendist, self.fixtresh, pixpercm) self.pxaccuracy = ((deg2pix(screendist, self.accuracy[0][0], pixpercm), deg2pix(screendist, self.accuracy[0][1], pixpercm)), (deg2pix(screendist, self.accuracy[1][0], pixpercm), deg2pix(screendist, self.accuracy[1][1], pixpercm))) self.pxspdtresh = deg2pix( screendist, self.spdtresh / 1000.0, pixpercm) # in pixels per millisecond self.pxacctresh = deg2pix( screendist, self.accthresh / 1000.0, pixpercm) # in pixels per millisecond**2 # calibration report self.log("pygaze calibration report start") self.log("accuracy (degrees): LX=%s, LY=%s, RX=%s, RY=%s" % (self.accuracy[0][0], self.accuracy[0][1], self.accuracy[1][0], self.accuracy[1][1])) self.log("accuracy (in pixels): LX=%s, LY=%s, RX=%s, RY=%s" % (self.pxaccuracy[0][0], self.pxaccuracy[0][1], self.pxaccuracy[1][0], self.pxaccuracy[1][1])) self.log("precision (RMS noise in pixels): X=%s, Y=%s" % (self.pxdsttresh[0], self.pxdsttresh[1])) self.log("distance between participant and display: %s cm" % screendist) self.log("fixation threshold: %s pixels" % self.pxfixtresh) self.log("speed threshold: %s pixels/ms" % self.pxspdtresh) self.log("acceleration threshold: %s pixels/ms**2" % self.pxacctresh) self.log("pygaze calibration report end") return True # validation error else: print( "WARNING libsmi.SMItracker.calibrate: validation was unsuccesful %s" % verr) return False # calibration error else: print( "WARNING libsmi.SMItracker.calibrate: calibration was unsuccesful; %s" % cerr) return False
def calibrate(self): """See pygaze._eyetracker.baseeyetracker.BaseEyeTracker""" if self.recording: raise Exception( "Error in libeyelink.libeyelink.calibrate(): Trying to " "calibrate after recording has started!") # # # # # # EyeLink calibration and validation # attempt calibrate; confirm abort when esc pressed while True: self.eyelink_graphics.esc_pressed = False pylink.getEYELINK().doTrackerSetup() if not self.eyelink_graphics.esc_pressed: break self.confirm_abort_experiment() # If we are using the built-in EyeLink event detection, we don't need # the RMS calibration routine. if self.eventdetection == 'native': return # # # # # # RMS calibration # present instructions self.display.fill() # clear display self.scr.draw_text(text= \ "Noise calibration: please look at the dot\n\n(press space to start)", pos=(self.resolution[0]/2, int(self.resolution[1]*0.2)), center=True, fontsize=self.fontsize) self.scr.draw_fixation(fixtype='dot') self.display.fill(self.scr) self.display.show() self.scr.clear() # clear screen again # wait for spacepress self.kb.get_key(keylist=['space'], timeout=None) # start recording self.log("PYGAZE RMS CALIBRATION START") self.start_recording() # show fixation self.display.fill() self.scr.draw_fixation(fixtype='dot') self.display.fill(self.scr) self.display.show() self.scr.clear() # wait for a bit, to allow participant to fixate clock.pause(500) # get samples # samplelist, prefilled with 1 sample to prevent sl[-1] from producing # an error; first sample will be ignored for RMS calculation sl = [self.sample()] t0 = clock.get_time() # starting time while clock.get_time() - t0 < 1000: s = self.sample() # sample if s != sl[-1] and s != (-1, -1) and s != (0, 0): sl.append(s) # stop recording self.log("PYGAZE RMS CALIBRATION END") self.stop_recording() # calculate RMS noise Xvar = [] Yvar = [] for i in range(2, len(sl)): Xvar.append((sl[i][0] - sl[i - 1][0])**2) Yvar.append((sl[i][1] - sl[i - 1][1])**2) XRMS = (sum(Xvar) / len(Xvar))**0.5 YRMS = (sum(Yvar) / len(Yvar))**0.5 self.pxdsttresh = (XRMS, YRMS) # recalculate thresholds (degrees to pixels) self.pxfixtresh = deg2pix(self.screendist, self.fixtresh, self.pixpercm) self.pxspdtresh = deg2pix( self.screendist, self.spdtresh, self.pixpercm) / 1000.0 # in pixels per millisecons self.pxacctresh = deg2pix( self.screendist, self.accthresh, self.pixpercm) / 1000.0 # in pixels per millisecond**2
def calibrate(self): #self.screen.clear() #self.screen.draw_text( # text="Calibrate EyeTracker", # fontsize=20) #self.disp.fill(self.screen) #self.disp.show() if (not self._recording.is_set()): resultTracking = self.api.requestTracking(0) if (resultTracking != ELApi.ReturnStart.SUCCESS): raise Exception("unable to start eye tracker") resultCalibrate = self.api.calibrate(0) if (resultCalibrate != ELApi.ReturnCalibrate.SUCCESS): self.api.unrequestTracking() self.errorbeep.play() raise Exception("Calibration failed = {}".format(errorstringCalibrate(resultCalibrate))) self._calibrated.set() # NOISE CALIBRATION self.screen.clear() self.screen.draw_text( text="Noise calibration. Please look at the dot, and press any key to start.", fontsize=20, \ pos=(int(self.dispsize[0]/2),int(self.dispsize[1]*0.3))) x = int(float(self.dispsize[0]) / 2.0) y = int(float(self.dispsize[1]) / 2.0) self.screen.draw_fixation(fixtype="dot", pos=(x,y)) self.disp.fill(self.screen) self.disp.show() self.kb.get_key(keylist=None, timeout=None, flush=True) # wait for a bit, to allow participant to fixate clock.pause(500) # get distance to screen screendist = 0 i = 0 while screendist == 0 and i < self.maxtries: i = i+1 self.sampleLock.acquire() if (self.lastSample is not None): if self.eye_used != 1 and self.lastSample.eyePositionLeftZ != ELInvalidValue: screendist = self.lastSample.eyePositionLeftZ / 10.0 # eyePositionZ is in mm; screendist is in cm elif self.eye_used != 0 and self.lastSample.eyePositionRightZ != ELInvalidValue: screendist = self.lastSample.eyePositionRightZ / 10.0 self.sampleLock.release() clock.pause(int(self.sampleTime)) if i >= self.maxtries: self.api.unrequestTracking() self.errorbeep.play() raise Exception("unable to receive gaze data for noise calibration") # get samples sl = [self.sample()] # samplelist, prefilled with 1 sample to prevent sl[-1] from producing an error; first sample will be ignored for RMS calculation t0 = clock.get_time() # starting time while clock.get_time() - t0 < 1000: s = self.sample() # sample if s[0] != -1 and s[1] != -1 and s[0] != ELInvalidValue and s[1] != ELInvalidValue: sl.append(s) clock.pause(int(self.sampleTime)) if (len(sl) < 2): if (not self._recording.is_set()): self.api.unrequestTracking() return False # calculate RMS noise Xvar = [] Yvar = [] Xmean = 0. Ymean = 0. for i in range(2,len(sl)): Xvar.append((sl[i][0]-sl[i-1][0])**2) Yvar.append((sl[i][1]-sl[i-1][1])**2) Xmean += sl[i][0] Ymean += sl[i][1] XRMS = (sum(Xvar) / len(Xvar))**0.5 YRMS = (sum(Yvar) / len(Yvar))**0.5 Xmean = Xmean / (len(sl)-2) Ymean = Ymean / (len(sl)-2) self.pxdsttresh = (XRMS, YRMS) # calculate pixels per cm pixpercm = (self.dispsize[0]/float(self.screensize[0]) + self.dispsize[1]/float(self.screensize[1])) / 2 # get accuracy accuracyPxX = abs( Xmean - x ) accuracyPxY = abs( Ymean - y ) self.accuracy = ( pix2deg(screendist, accuracyPxX, pixpercm), \ pix2deg(screendist, accuracyPxY, pixpercm) ) # calculate thresholds based on tracker settings self.pxfixtresh = deg2pix(screendist, self.fixtresh, pixpercm) self.pxaccuracy = (accuracyPxX, accuracyPxY ) self.pxspdtresh = deg2pix(screendist, self.spdtresh/1000.0, pixpercm) # in pixels per millisecond self.pxacctresh = deg2pix(screendist, self.accthresh/1000.0, pixpercm) # in pixels per millisecond**2 ## log self.log("pygaze calibration") self.log("accuracy (degrees) = X={}, Y={}".format( \ self.accuracy[0], self.accuracy[1] )) self.log("accuracy (in pixels) = X={}, Y={}".format( \ self.pxaccuracy[0], self.pxaccuracy[1])) self.log("precision (RMS noise in pixels) = X={}, Y={}".format( \ self.pxdsttresh[0], self.pxdsttresh[1])) self.log("distance between participant and display = {} cm".format(screendist)) self.log("fixation threshold = {} pixels".format(self.pxfixtresh)) self.log("speed threshold = {} pixels/ms".format(self.pxspdtresh)) self.log("acceleration threshold = {} pixels/ms**2".format(self.pxacctresh)) if (not self._recording.is_set()): self.api.unrequestTracking() return True
def calibrate(self, calibrate=True, validate=True): """Calibrates the eye tracking system arguments None keyword arguments calibrate -- Boolean indicating if calibration should be performed (default = True) validate -- Boolean indicating if validation should be performed (default = True) returns success -- returns True if calibration succeeded, or False if not; in addition a calibration log is added to the log file and some properties are updated (i.e. the thresholds for detection algorithms) """ # TODO: # add feedback for calibration (e.g. with iV_GetAccuracyImage (struct ImageStruct * imageData) for accuracy and iV_GetEyeImage for cool eye pictures) # example: res = iViewXAPI.iV_GetEyeImage(byref(imageData)) # ImageStruct has four data fields: # imageHeight -- int vertical size (px) # imageWidth -- int horizontal size (px) # imageSize -- int image data size (byte) # imageBuffer -- pointer to image data (I have NO idea what format this is in) # configure calibration (NOT starting it) calibrationData = CCalibration(9, 1, 0, 1, 1, 0, 127, 1, 15, b"") # (method (i.e.: number of points), visualization, display, speed, auto, fg, bg, shape, size, filename) # setup calibration res = iViewXAPI.iV_SetupCalibration(byref(calibrationData)) if res != 1: err = errorstring(res) raise Exception("Error in libsmi.SMItracker.calibrate: failed to setup calibration; %s" % err) # calibrate cres = iViewXAPI.iV_Calibrate() # validate if calibration returns succes if cres == 1: cerr = None vres = iViewXAPI.iV_Validate() # handle validation errors if vres != 1: verr = errorstring(vres) else: verr = None ## # TEST # ## res = iViewXAPI.iV_GetAccuracyImage(byref(imageData)) ## self.log("IMAGEBUFFERSTART") ## self.log(imageData.imageBuffer) ## self.log("IMAGEBUFFERSTOP") ## print("Image height: %s, image width: %s, image size: %s" % (imageData.imageHeight,imageData.imageWidth, imageData.imageSize)) ## print imageData.imageBuffer ## ######## # handle calibration errors else: cerr = errorstring(cres) # return succes if cerr == None: print("libsmi.SMItracker.calibrate: calibration was succesful") if verr == None: print("libsmi.SMItracker.calibrate: validation was succesful") # present instructions self.disp.fill() # clear display self.screen.draw_text(text="Noise calibration: please look at the dot\n\n(press space to start)", pos=(self.dispsize[0]/2, int(self.dispsize[1]*0.2)), center=True) self.screen.draw_fixation(fixtype='dot') self.disp.fill(self.screen) self.disp.show() self.screen.clear() # clear screen again # wait for spacepress self.kb.get_key(keylist=['space'], timeout=None) # show fixation self.disp.fill() self.screen.draw_fixation(fixtype='dot') self.disp.fill(self.screen) self.disp.show() self.screen.clear() # wait for a bit, to allow participant to fixate clock.pause(500) # get samples sl = [self.sample()] # samplelist, prefilled with 1 sample to prevent sl[-1] from producing an error; first sample will be ignored for RMS calculation t0 = clock.get_time() # starting time while clock.get_time() - t0 < 1000: s = self.sample() # sample if s != sl[-1] and s != (-1,-1) and s != (0,0): sl.append(s) # calculate RMS noise Xvar = [] Yvar = [] for i in range(2,len(sl)): Xvar.append((sl[i][0]-sl[i-1][0])**2) Yvar.append((sl[i][1]-sl[i-1][1])**2) XRMS = (sum(Xvar) / len(Xvar))**0.5 YRMS = (sum(Yvar) / len(Yvar))**0.5 self.pxdsttresh = (XRMS, YRMS) # calculate pixels per cm pixpercm = (self.dispsize[0]/float(self.screensize[0]) + self.dispsize[1]/float(self.screensize[1])) / 2 # get accuracy res = 0; i = 0 while res != 1 and i < self.maxtries: # multiple tries, in case no (valid) sample is available res = iViewXAPI.iV_GetAccuracy(byref(accuracyData),0) # 0 is for 'no visualization' i += 1 clock.pause(int(self.sampletime)) # wait for sampletime if res == 1: self.accuracy = ((accuracyData.deviationLX,accuracyData.deviationLY), (accuracyData.deviationLX,accuracyData.deviationLY)) # dsttresh = (left tuple, right tuple); tuple = (horizontal deviation, vertical deviation) in degrees of visual angle else: err = errorstring(res) print("WARNING libsmi.SMItracker.calibrate: failed to obtain accuracy data; %s" % err) self.accuracy = ((2,2),(2,2)) print("libsmi.SMItracker.calibrate: As an estimate, the intersample distance threshhold was set to it's default value of 2 degrees") # get distance from screen to eyes (information from tracker) res = 0; i = 0 while res != 1 and i < self.maxtries: # multiple tries, in case no (valid) sample is available res = iViewXAPI.iV_GetSample(byref(sampleData)) i += 1 clock.pause(int(self.sampletime)) # wait for sampletime if res == 1: screendist = sampleData.leftEye.eyePositionZ / 10.0 # eyePositionZ is in mm; screendist is in cm else: err = errorstring(res) print("WARNING libsmi.SMItracker.calibrate: failed to obtain screen distance; %s" % err) screendist = SCREENDIST print("libsmi.SMItracker.calibrate: As an estimate, the screendistance was set to it's default value of 57 cm") # calculate thresholds based on tracker settings self.pxerrdist = deg2pix(screendist, self.errdist, pixpercm) self.pxfixtresh = deg2pix(screendist, self.fixtresh, pixpercm) self.pxaccuracy = ((deg2pix(screendist, self.accuracy[0][0], pixpercm),deg2pix(screendist, self.accuracy[0][1], pixpercm)), (deg2pix(screendist, self.accuracy[1][0], pixpercm),deg2pix(screendist, self.accuracy[1][1], pixpercm))) self.pxspdtresh = deg2pix(screendist, self.spdtresh/1000.0, pixpercm) # in pixels per millisecond self.pxacctresh = deg2pix(screendist, self.accthresh/1000.0, pixpercm) # in pixels per millisecond**2 # calibration report self.log("pygaze calibration report start") self.log("accuracy (degrees): LX=%s, LY=%s, RX=%s, RY=%s" % (self.accuracy[0][0],self.accuracy[0][1],self.accuracy[1][0],self.accuracy[1][1])) self.log("accuracy (in pixels): LX=%s, LY=%s, RX=%s, RY=%s" % (self.pxaccuracy[0][0],self.pxaccuracy[0][1],self.pxaccuracy[1][0],self.pxaccuracy[1][1])) self.log("precision (RMS noise in pixels): X=%s, Y=%s" % (self.pxdsttresh[0],self.pxdsttresh[1])) self.log("distance between participant and display: %s cm" % screendist) self.log("fixation threshold: %s pixels" % self.pxfixtresh) self.log("speed threshold: %s pixels/ms" % self.pxspdtresh) self.log("acceleration threshold: %s pixels/ms**2" % self.pxacctresh) self.log("pygaze calibration report end") return True # validation error else: print("WARNING libsmi.SMItracker.calibrate: validation was unsuccesful %s" % verr) return False # calibration error else: print("WARNING libsmi.SMItracker.calibrate: calibration was unsuccesful; %s" % cerr) return False
def calibrate(self): """Calibrates the eye tracking system arguments None keyword arguments None returns success -- returns True if calibration succeeded, or False if not; in addition a calibration log is added to the log file and some properties are updated (i.e. the thresholds for detection algorithms) """ # CALIBRATION # determine the calibration points calibpoints = [] for x in [0.1,0.5,0.9]: for y in [0.1,0.5,0.9]: calibpoints.append((int(x*self.dispsize[0]),int(y*self.dispsize[1]))) random.shuffle(calibpoints) # show a message self.screen.clear() self.screen.draw_text(text="Press Space to start the calibration or Q to quit.") self.disp.fill(self.screen) self.disp.show() # wait for keyboard input key, keytime = self.kb.get_key(keylist=['q','space'], timeout=None, flush=True) if key == 'q': quited = True else: quited = False # run until the user is statisfied, or quits calibrated = False calibresult = None while not quited and not calibrated: # start a new calibration self.eyetribe.calibration.start(pointcount=len(calibpoints)) # loop through calibration points for cpos in calibpoints: self.draw_calibration_target(cpos[0], cpos[1]) # wait for a bit to allow participant to start looking at # the calibration point (#TODO: space press?) clock.pause(1000) # start calibration of point self.eyetribe.calibration.pointstart(cpos[0],cpos[1]) # wait for a second clock.pause(1000) # stop calibration of this point result = self.eyetribe.calibration.pointend() # the final calibration point returns a dict (does it?) if type(result) == dict: calibresult = copy.deepcopy(result) # check if the Q key has been pressed if self.kb.get_key(keylist=['q'],timeout=10,flush=False)[0] == 'q': # abort calibration self.eyetribe.calibration.abort() # set quited variable and break this for loop quited = True break # retry option if the calibration was aborted if quited: # show retry message self.screen.clear() self.screen.draw_text("Calibration aborted. Press Space to restart, or 'Q' to quit.") self.disp.fill(self.screen) self.disp.show() # get input key, keytime = self.kb.get_key(keylist=['q','space'], timeout=None, flush=True) if key == 'space': # unset quited Boolean quited = False # skip further processing continue # get the calibration result if it was not obtained yet if type(calibresult) != dict: # empty display self.disp.fill() self.disp.show() # allow for a bit of calculation time clock.pause(2000) # get the result calibresult = self.eyetribe._tracker.get_calibresult() # results # clear the screen self.screen.clear() # draw results for each point if type(calibresult) == dict: for p in calibresult['calibpoints']: # only draw the point if data was obtained if p['state'] > 0: # draw the mean error self.screen.draw_circle(colour=(252,233,79), pos=(p['cpx'],p['cpy']), r=p['mepix'], pw=0, fill=True) # draw the point self.screen.draw_fixation(fixtype='dot', colour=(115,210,22), pos=(p['cpx'],p['cpy'])) # draw the estimated point self.screen.draw_fixation(fixtype='dot', colour=(32,74,135), pos=(p['mecpx'],p['mecpy'])) # annotate accuracy self.screen.draw_text(text=str(p['acd']), pos=(p['cpx']+10,p['cpy']+10), fontsize=12) # if no data was obtained, draw the point in red else: self.screen.draw_fixation(fixtype='dot', colour=(204,0,0), pos=(p['cpx'],p['cpy'])) # draw box for averages self.screen.draw_rect(colour=(238,238,236), x=int(self.dispsize[0]*0.15), y=int(self.dispsize[1]*0.2), w=400, h=200, pw=0, fill=True) # draw result if calibresult['result']: self.screen.draw_text(text="calibration is successful", colour=(115,210,22), pos=(int(self.dispsize[0]*0.25),int(self.dispsize[1]*0.25)), fontsize=12) else: self.screen.draw_text(text="calibration failed", colour=(204,0,0), pos=(int(self.dispsize[0]*0.25),int(self.dispsize[1]*0.25)), fontsize=12) # draw average accuracy self.screen.draw_text(text="average error = %.2f degrees" % (calibresult['deg']), colour=(211,215,207), pos=(int(self.dispsize[0]*0.25),int(self.dispsize[1]*0.25+20)), fontsize=12) # draw input options self.screen.draw_text(text="Press Space to continue, or 'R' to restart.", colour=(211,215,207), pos=(int(self.dispsize[0]*0.25),int(self.dispsize[1]*0.25+40)), fontsize=12) else: self.screen.draw_text(text="Calibration failed, press 'R' to try again.") # show the results self.disp.fill(self.screen) self.disp.show() # wait for input key, keytime = self.kb.get_key(keylist=['space','r'], timeout=None, flush=True) # process input if key == 'space': calibrated = True # calibration failed if the user quited if quited: return False # NOISE CALIBRATION # get all error estimates (pixels) var = [] for p in calibresult['calibpoints']: # only draw the point if data was obtained if p['state'] > 0: var.append(p['mepix']) noise = sum(var) / float(len(var)) self.pxdsttresh = (noise, noise) # AFTERMATH # store some variables pixpercm = (self.dispsize[0]/float(self.screensize[0]) + self.dispsize[1]/float(self.screensize[1])) / 2 screendist = SCREENDIST # calculate thresholds based on tracker settings self.accuracy = ((calibresult['Ldeg'],calibresult['Ldeg']), (calibresult['Rdeg'],calibresult['Rdeg'])) self.pxerrdist = deg2pix(screendist, self.errdist, pixpercm) self.pxfixtresh = deg2pix(screendist, self.fixtresh, pixpercm) self.pxaccuracy = ((deg2pix(screendist, self.accuracy[0][0], pixpercm),deg2pix(screendist, self.accuracy[0][1], pixpercm)), (deg2pix(screendist, self.accuracy[1][0], pixpercm),deg2pix(screendist, self.accuracy[1][1], pixpercm))) self.pxspdtresh = deg2pix(screendist, self.spdtresh/1000.0, pixpercm) # in pixels per millisecond self.pxacctresh = deg2pix(screendist, self.accthresh/1000.0, pixpercm) # in pixels per millisecond**2 # calibration report self.log("pygaze calibration report start") self.log("accuracy (degrees): LX=%s, LY=%s, RX=%s, RY=%s" % (self.accuracy[0][0],self.accuracy[0][1],self.accuracy[1][0],self.accuracy[1][1])) self.log("accuracy (in pixels): LX=%s, LY=%s, RX=%s, RY=%s" % (self.pxaccuracy[0][0],self.pxaccuracy[0][1],self.pxaccuracy[1][0],self.pxaccuracy[1][1])) self.log("precision (RMS noise in pixels): X=%s, Y=%s" % (self.pxdsttresh[0],self.pxdsttresh[1])) self.log("distance between participant and display: %s cm" % screendist) self.log("fixation threshold: %s pixels" % self.pxfixtresh) self.log("speed threshold: %s pixels/ms" % self.pxspdtresh) self.log("acceleration threshold: %s pixels/ms**2" % self.pxacctresh) self.log("pygaze calibration report end") return True
def calibrate(self, pre_calib_wait=500, calib_wait=1000): """Calibrates the eye tracking system returns success -- returns True if calibration succeeded, or False if not; in addition a calibration log is added to the log file and some properties are updated (i.e. the thresholds for detection algorithms) """ def get_psychopy_pos(x, y): return (x - self.dispsize[0] / 2), (y - self.dispsize[1] / 2) # CALIBRATION # determine the calibration points calibpoints = [] margin = int(self.dispsize[1] / 10) gap_x = int((self.dispsize[0] - 2 * margin) / 3) gap_y = int((self.dispsize[1] - 2 * margin) / 3) for x in range(4): for y in range(4): calibpoints.append((margin + gap_x * x, margin + gap_y * y)) random.shuffle(calibpoints) # shuffle two lists together # show a message black_bg = visual.Rect(self.presenter.window, width=2.1, height=2.1, fillColor='black') self.presenter.show_instructions( 'Press space to calibrate\n\nFollow circles with your eyes', other_stim=[black_bg], next_instr_text=None) quited = False # Pause the processing of samples during the calibration. # self.eyetribe._pause_sample_processing() # run until the user is statisfied, or quits calibrated = False calibresult = None while not quited and not calibrated: # Clear the existing calibration. if self.eyetribe._tracker.get_iscalibrated(): self.eyetribe._lock.acquire(True) self.eyetribe.calibration.clear() self.eyetribe._lock.release() # Wait for a bit. clock.pause(1500) # start a new calibration if not self.eyetribe._tracker.get_iscalibrating(): self.eyetribe._lock.acquire(True) self.eyetribe.calibration.start(pointcount=len(calibpoints)) self.eyetribe._lock.release() # loop through calibration points for cpos in calibpoints: # Check whether the calibration is already done. # (Not sure how or why, but for some reason some data # can persist between calbrations, and the tracker will # simply stop allowing further pointstart requests.) if self.eyetribe._tracker.get_iscalibrated(): break # Draw a calibration target. point = get_psychopy_pos(cpos[0], cpos[1]) self.draw_calibration_target(point[0], point[1], black_bg) # wait for a bit to allow participant to start looking at # the calibration point clock.pause(pre_calib_wait) # start calibration of point self.eyetribe._lock.acquire(True) self.eyetribe.calibration.pointstart(cpos[0], cpos[1]) self.eyetribe._lock.release() # wait for a second clock.pause(calib_wait) # stop calibration of this point self.eyetribe._lock.acquire(True) self.eyetribe.calibration.pointend() self.eyetribe._lock.release() # empty display self.presenter.draw_stimuli_for_duration([black_bg], duration=None) # allow for a bit of calculation time # (this is waaaaaay too much) clock.pause(1000)
def calibrate(self, calibrate=True, validate=True): """Calibrates the eye tracker. arguments None keyword arguments calibrate -- Boolean indicating if calibration should be performed (default = True). validate -- Boolean indicating if validation should be performed (default = True). returns success -- returns True if calibration succeeded, or False if not; in addition a calibration log is added to the log file and some properties are updated (i.e. the thresholds for detection algorithms) """ self._write_enabled = False self.start_recording() self.screen.set_background_colour(colour=(0, 0, 0)) if calibrate: origin = (int(self.disp.dispsize[0] / 4), int(self.disp.dispsize[1] / 4)) size = (int(2 * self.disp.dispsize[0] / 4), int(2 * self.disp.dispsize[1] / 4)) while not self.kb.get_key(keylist=['space'], flush=False)[0]: gaze_sample = copy.copy(self.gaze[-1]) self.screen.clear() validity_colour = (255, 0, 0) if gaze_sample['right_gaze_origin_validity'] and gaze_sample['left_gaze_origin_validity']: left_validity = 0.15 < gaze_sample['left_gaze_origin_in_trackbox_coordinate_system'][2] < 0.85 right_validity = 0.15 < gaze_sample['right_gaze_origin_in_trackbox_coordinate_system'][2] < 0.85 if left_validity and right_validity: validity_colour = (0, 255, 0) self.screen.draw_text(text="When correctly positioned press \'space\' to start the calibration.", pos=(int(self.disp.dispsize[0] / 2), int(self.disp.dispsize[1] * 0.1)), colour=(255, 255, 255), fontsize=20) self.screen.draw_line(colour=validity_colour, spos=origin, epos=(origin[0] + size[0], origin[1]), pw=1) self.screen.draw_line(colour=validity_colour, spos=origin, epos=(origin[0], origin[1] + size[1]), pw=1) self.screen.draw_line(colour=validity_colour, spos=(origin[0], origin[1] + size[1]), epos=(origin[0] + size[0], origin[1] + size[1]), pw=1) self.screen.draw_line(colour=validity_colour, spos=(origin[0] + size[0], origin[1] + size[1]), epos=(origin[0] + size[0], origin[1]), pw=1) right_eye, left_eye, distance = None, None, [] if gaze_sample['right_gaze_origin_validity']: distance.append(round(gaze_sample['right_gaze_origin_in_user_coordinate_system'][2] / 10, 1)) right_eye = ((1 - gaze_sample['right_gaze_origin_in_trackbox_coordinate_system'][0]) * size[0] + origin[0], gaze_sample['right_gaze_origin_in_trackbox_coordinate_system'][1] * size[1] + origin[1]) self.screen.draw_circle(colour=validity_colour, pos=right_eye, r=int(self.disp.dispsize[0] / 100), pw=5, fill=True) if gaze_sample['left_gaze_origin_validity']: distance.append(round(gaze_sample['left_gaze_origin_in_user_coordinate_system'][2] / 10, 1)) left_eye = ((1 - gaze_sample['left_gaze_origin_in_trackbox_coordinate_system'][0]) * size[0] + origin[0], gaze_sample['left_gaze_origin_in_trackbox_coordinate_system'][1] * size[1] + origin[1]) self.screen.draw_circle(colour=validity_colour, pos=left_eye, r=int(self.disp.dispsize[0] / 100), pw=5, fill=True) self.screen.draw_text(text="Current distance to the eye tracker: {0} cm.".format(self._mean(distance)), pos=(int(self.disp.dispsize[0] / 2), int(self.disp.dispsize[1] * 0.9)), colour=(255, 255, 255), fontsize=20) self.disp.fill(self.screen) self.disp.show() # # # # # # # # calibration if not self.eyetracker: print("WARNING! libtobii.TobiiProTracker.calibrate: no eye trackers found for the calibration!") self.stop_recording() return False calibration = tr.ScreenBasedCalibration(self.eyetracker) calibrating = True while calibrating: calibration.enter_calibration_mode() for point in self.points_to_calibrate: self.screen.clear() # CDP : Changement couleur #self.screen.draw_circle(colour='yellow', pos=point, r=int(self.disp.dispsize[0] / 100.0), pw=5, fill=True) #self.screen.draw_circle(colour=(255, 0, 0), pos=point, r=int(self.disp.dispsize[0] / 400.0), pw=5, fill=True) #self.disp.fill(self.screen) #self.disp.show() # Wait a little for user to focus. # CDP : Ajout sasie clavier #clock.pause(1000) self.ReduceBall(point,30,'yellow') pressed_key = self.kb.get_key(keylist=['space', 'r'], flush=True, timeout=None) normalized_point = self._px_2_norm(point) if calibration.collect_data(normalized_point[0], normalized_point[1]) != tr.CALIBRATION_STATUS_SUCCESS: # Try again if it didn't go well the first time. # Not all eye tracker models will fail at this point, but instead fail on ComputeAndApply. calibration.collect_data(normalized_point[0], normalized_point[1]) self.screen.clear() self.screen.draw_text("Calculating calibration result....", colour=(255, 255, 255), fontsize=20) self.disp.fill(self.screen) self.disp.show() calibration_result = calibration.compute_and_apply() calibration.leave_calibration_mode() print "Compute and apply returned {0} and collected at {1} points.".\ format(calibration_result.status, len(calibration_result.calibration_points)) if calibration_result.status != tr.CALIBRATION_STATUS_SUCCESS: self.stop_recording() print("WARNING! libtobii.TobiiProTracker.calibrate: Calibration was unsuccessful!") return False self.screen.clear() for point in calibration_result.calibration_points: self.screen.draw_circle(colour=(255, 255, 255), pos=self._norm_2_px(point.position_on_display_area), r=self.disp.dispsize[0] / 200, pw=1, fill=False) for sample in point.calibration_samples: if sample.left_eye.validity == tr.VALIDITY_VALID_AND_USED: self.screen.draw_circle(colour=(255, 0, 0), pos=self._norm_2_px(sample.left_eye.position_on_display_area), r=self.disp.dispsize[0] / 450, pw=self.disp.dispsize[0] / 450, fill=False) self.screen.draw_line(colour=(255, 0, 0), spos=self._norm_2_px(point.position_on_display_area), epos=self._norm_2_px(sample.left_eye.position_on_display_area), pw=1) if sample.right_eye.validity == tr.VALIDITY_VALID_AND_USED: self.screen.draw_circle(colour=(0, 0, 255), pos=self._norm_2_px(sample.right_eye.position_on_display_area), r=self.disp.dispsize[0] / 450, pw=self.disp.dispsize[0] / 450, fill=False) self.screen.draw_line(colour=(0, 0, 255), spos=self._norm_2_px(point.position_on_display_area), epos=self._norm_2_px(sample.right_eye.position_on_display_area), pw=1) self.screen.draw_text("Press the \'R\' key to recalibrate or \'Space\' to continue....", pos=(0.5 * self.disp.dispsize[0], 0.95 * self.disp.dispsize[1]), colour=(255, 255, 255), fontsize=20) self.screen.draw_text("Left Eye", pos=(0.5 * self.disp.dispsize[0], 0.01 * self.disp.dispsize[1]), colour=(255, 0, 0), fontsize=20) self.screen.draw_text("Right Eye", pos=(0.5 * self.disp.dispsize[0], 0.03 * self.disp.dispsize[1]), colour=(0, 0, 255), fontsize=20) self.disp.fill(self.screen) self.disp.show() pressed_key = self.kb.get_key(keylist=['space', 'r'], flush=True, timeout=None) if pressed_key[0] == 'space': calibrating = False if validate: # # # show menu self.screen.clear() self.screen.draw_text(text="Press space to start validation", colour=(255, 255, 255), fontsize=20) self.disp.fill(self.screen) self.disp.show() # # # wait for spacepress self.kb.get_key(keylist=['space'], flush=True, timeout=None) # # # # # # # # validation # # # arrays for data storage lxacc, lyacc, rxacc, ryacc = [], [], [], [] # # loop through all calibration positions for pos in self.points_to_calibrate: # show validation point self.screen.clear() self.screen.draw_fixation(fixtype='dot', pos=pos, colour=(255, 255, 255)) self.disp.fill(self.screen) self.disp.show() # allow user some time to gaze at dot clock.pause(1000) lxsamples, lysamples, rxsamples, rysamples = [], [], [], [] for sample in self.gaze: if sample["left_gaze_point_validity"]: gaze_point = self._norm_2_px(sample["left_gaze_point_on_display_area"]) lxsamples.append(abs(gaze_point[0] - pos[0])) lysamples.append(abs(gaze_point[1] - pos[1])) if sample["right_gaze_point_validity"]: gaze_point = self._norm_2_px(sample["right_gaze_point_on_display_area"]) rxsamples.append(abs(gaze_point[0] - pos[0])) rysamples.append(abs(gaze_point[1] - pos[1])) # calculate mean deviation lxacc.append(self._mean(lxsamples)) lyacc.append(self._mean(lysamples)) rxacc.append(self._mean(rxsamples)) ryacc.append(self._mean(rysamples)) # wait for a bit to slow down validation process a bit clock.pause(1000) # calculate mean accuracy self.pxaccuracy = [(self._mean(lxacc), self._mean(lyacc)), (self._mean(rxacc), self._mean(ryacc))] # sample rate # calculate intersample times timestamps = [] gaze_samples = copy.copy(self.gaze) for i in xrange(0, len(gaze_samples) - 1): timestamps.append((gaze_samples[i + 1]['system_time_stamp'] - gaze_samples[i]['system_time_stamp']) / 1000.0) # mean intersample time self.sampletime = self._mean(timestamps) self.samplerate = int(1000.0 / self.sampletime) # # # # # # # # RMS noise # # present instructions self.screen.clear() self.screen.draw_text(text="Noise calibration: please look at the dot\n\n(press space to start)", pos=(self.disp.dispsize[0] / 2, int(self.disp.dispsize[1] * 0.2)), colour=(255, 255, 255), fontsize=20) self.screen.draw_fixation(fixtype='dot', colour=(255, 255, 255)) self.disp.fill(self.screen) self.disp.show() # # wait for spacepress self.kb.get_key(keylist=['space'], flush=True, timeout=None) # # show fixation self.screen.draw_fixation(fixtype='dot', colour=(255, 255, 255)) self.disp.fill(self.screen) self.disp.show() self.screen.clear() # # wait for a bit, to allow participant to fixate clock.pause(500) # # get samples sl = [self.sample()] # samplelist, prefilled with 1 sample to prevent sl[-1] from producing an error; first sample will be ignored for RMS calculation t0 = clock.get_time() # starting time while clock.get_time() - t0 < 1000: s = self.sample() # sample if s != sl[-1] and self.is_valid_sample(s) and s != (0, 0): sl.append(s) # # calculate RMS noise Xvar, Yvar = [], [] for i in xrange(2, len(sl)): Xvar.append((sl[i][0] - sl[i - 1][0])**2) Yvar.append((sl[i][1] - sl[i - 1][1])**2) XRMS = (self._mean(Xvar))**0.5 YRMS = (self._mean(Yvar))**0.5 self.pxdsttresh = (XRMS, YRMS) # # # # # # # # # # calibration report # # # # recalculate thresholds (degrees to pixels) self.pxfixtresh = self._deg2pix(self.screendist, self.fixtresh, self.pixpercm) self.pxspdtresh = self._deg2pix(self.screendist, self.spdtresh / 1000.0, self.pixpercm) # in pixels per millisecons self.pxacctresh = self._deg2pix(self.screendist, self.accthresh / 1000.0, self.pixpercm) # in pixels per millisecond**2 data_to_write = '' data_to_write += "pygaze calibration report start\n" data_to_write += "samplerate: %s Hz\n" % self.samplerate data_to_write += "sampletime: %s ms\n" % self.sampletime data_to_write += "accuracy (in pixels): LX=%s, LY=%s, RX=%s, RY=%s\n" % (self.pxaccuracy[0][0], self.pxaccuracy[0][1], self.pxaccuracy[1][0], self.pxaccuracy[1][1]) data_to_write += "precision (RMS noise in pixels): X=%s, Y=%s\n" % (self.pxdsttresh[0], self.pxdsttresh[1]) data_to_write += "distance between participant and display: %s cm\n" % self.screendist data_to_write += "fixation threshold: %s pixels\n" % self.pxfixtresh data_to_write += "speed threshold: %s pixels/ms\n" % self.pxspdtresh data_to_write += "accuracy threshold: %s pixels/ms**2\n" % self.pxacctresh data_to_write += "pygaze calibration report end\n" # # # # write report to log #self.datafile.write(data_to_write) self.screen.clear() self.screen.draw_text(text=data_to_write, pos=(self.disp.dispsize[0] / 2, int(self.disp.dispsize[1] / 2)), colour=(255, 255, 255), fontsize=20) self.disp.fill(self.screen) self.disp.show() self.kb.get_key(keylist=['space'], flush=True, timeout=None) self.stop_recording() self._write_enabled = True return True
def preCalibrate(self): """Helps position the infant while playing a video. returns Boolean indicating whether the positioning is done (True: 'space' has been pressed) """ self._write_enabled = False self.start_recording() # origin: top-left corner of precalibration box; size: tuple of lengths for box sides origin = (int(self.disp.dispsize[0] / 4), int(self.disp.dispsize[1] / 4)) size = (int(2 * self.disp.dispsize[0] / 4), int(2 * self.disp.dispsize[1] / 4)) # Initialise a PsychoPy MovieStim mov = visual.MovieStim3(self.video_win, c.CALIBVIDEO, flipVert=False) # print("------------> Pre-calibration process started.") print( "\t-> When correctly positioned, press \'space\' to start the calibration." ) while mov.status != visual.FINISHED: if not self.gaze: continue self.screen.clear() # Add the MovieStim to a PyGaze Screen instance. self.screen.screen.append(mov) # self.gaze.append(gaze_data), gaze_data is the data structure provided by Tobii gaze_sample = copy.copy(self.gaze[-1]) # latest gazepoint validity_colour = (255, 0, 0) if gaze_sample['right_gaze_origin_validity'] and gaze_sample[ 'left_gaze_origin_validity']: left_validity = 0.15 < gaze_sample[ 'left_gaze_origin_in_trackbox_coordinate_system'][2] < 0.85 right_validity = 0.15 < gaze_sample[ 'right_gaze_origin_in_trackbox_coordinate_system'][2] < 0.85 if left_validity and right_validity: validity_colour = (0, 255, 0) self.screen.draw_line(colour=validity_colour, spos=origin, epos=(origin[0] + size[0], origin[1]), pw=1) self.screen.draw_line(colour=validity_colour, spos=origin, epos=(origin[0], origin[1] + size[1]), pw=1) self.screen.draw_line(colour=validity_colour, spos=(origin[0], origin[1] + size[1]), epos=(origin[0] + size[0], origin[1] + size[1]), pw=1) self.screen.draw_line(colour=validity_colour, spos=(origin[0] + size[0], origin[1] + size[1]), epos=(origin[0] + size[0], origin[1]), pw=1) right_eye, left_eye, distance = None, None, [] if gaze_sample['right_gaze_origin_validity']: distance.append( round( gaze_sample[ 'right_gaze_origin_in_user_coordinate_system'][2] / 10, 1)) right_pos = gaze_sample[ 'right_gaze_origin_in_trackbox_coordinate_system'] right_eye = ((1 - right_pos[0]) * size[0] + origin[0], right_pos[1] * size[1] + origin[1]) self.screen.draw_circle(colour=validity_colour, pos=right_eye, r=int(self.disp.dispsize[0] / 100), pw=5, fill=True) if gaze_sample['left_gaze_origin_validity']: distance.append( round( gaze_sample[ 'left_gaze_origin_in_user_coordinate_system'][2] / 10, 1)) left_pos = gaze_sample[ 'left_gaze_origin_in_trackbox_coordinate_system'] left_eye = ((1 - left_pos[0]) * size[0] + origin[0], left_pos[1] * size[1] + origin[1]) self.screen.draw_circle(colour=validity_colour, pos=left_eye, r=int(self.disp.dispsize[0] / 100), pw=5, fill=True) self.screen.draw_text( text="Current distance to the eye tracker: {0} cm.".format( self._mean(distance)), pos=(int(self.disp.dispsize[0] / 2), int(self.disp.dispsize[1] * 0.9)), colour=(255, 255, 255), fontsize=20) self.disp.fill(self.screen) self.disp.show() key = self._getKeyPress() if key == "space": break # because looping doesn't seem to work if mov.status != visual.FINISHED: # pause and discard video for the audio to stop as well mov.pause() self.screen.screen.remove(mov) #video_win.close() del mov self.screen.clear() clock.pause(1000) return True else: return False
# loop through points for i in range(len(CALIBPOINTS)): # get coordinate x, y = CALIBPOINTS[i] # draw calibration point scr.clear() scr.draw_fixation(fixtype='dot', pos=(x,y)) disp.fill(scr) # start recording tracker.start_recording() tracker.log("VALIDATION_TRIALSTART, trialnr=%d, x=%d, y=%d" % (i,x,y)) # show display disp.show() tracker.log("validation_point_on") # allow for a bit of time so the subject can fixate the target clock.pause(1000) tracker.log("validation_point_fix") # wait for a bit clock.pause(POINTTIME) # clear screen scr.clear() disp.fill(scr) disp.show() # stop recording tracker.log("validation_point_off") tracker.stop_recording() # inter-trial interval clock.pause(ITI) # pause screen scr.clear()
def calibrate(self): """See pygaze._eyetracker.baseeyetracker.BaseEyeTracker""" while True: if self.recording: raise Exception( "Error in libeyelink.libeyelink.calibrate(): Trying to " "calibrate after recording has started!") # # # # # # EyeLink calibration and validation # attempt calibrate; confirm abort when esc pressed while True: self.eyelink_graphics.esc_pressed = False pylink.getEYELINK().doTrackerSetup() if not self.eyelink_graphics.esc_pressed: break self.confirm_abort_experiment() # If we are using the built-in EyeLink event detection, we don't need # the RMS calibration routine. if self.eventdetection == 'native': return # # # # # # RMS calibration while True: # present instructions self.display.fill() # clear display self.scr.draw_text(text= \ "Noise calibration: please look at the dot\n\n(press space to start)", pos=(self.resolution[0]/2, int(self.resolution[1]*0.2)), center=True, fontsize=self.fontsize) self.scr.draw_fixation(fixtype='dot') self.display.fill(self.scr) self.display.show() self.scr.clear() # clear screen again # wait for spacepress self.kb.get_key(keylist=['space'], timeout=None) # start recording self.log("PYGAZE RMS CALIBRATION START") self.start_recording() # show fixation self.display.fill() self.scr.draw_fixation(fixtype='dot') self.display.fill(self.scr) self.display.show() self.scr.clear() # wait for a bit, to allow participant to fixate clock.pause(500) # get samples # samplelist, prefilled with 1 sample to prevent sl[-1] from producing # an error; first sample will be ignored for RMS calculation sl = [self.sample()] t0 = clock.get_time() # starting time while clock.get_time() - t0 < 1000: s = self.sample() # sample if s != sl[-1] and s != (-1, -1) and s != (0, 0): sl.append(s) # stop recording self.log("PYGAZE RMS CALIBRATION END") self.stop_recording() # calculate RMS noise Xvar = [] Yvar = [] for i in range(2, len(sl)): Xvar.append((sl[i][0] - sl[i - 1][0])**2) Yvar.append((sl[i][1] - sl[i - 1][1])**2) if Xvar and Yvar: # check if properly recorded to avoid risk of division by zero error XRMS = (sum(Xvar) / len(Xvar))**0.5 YRMS = (sum(Yvar) / len(Yvar))**0.5 self.pxdsttresh = (XRMS, YRMS) # recalculate thresholds (degrees to pixels) self.pxfixtresh = deg2pix(self.screendist, self.fixtresh, self.pixpercm) self.pxspdtresh = deg2pix( self.screendist, self.spdtresh, self.pixpercm) / 1000.0 # in pixels per millisecons self.pxacctresh = deg2pix( self.screendist, self.accthresh, self.pixpercm) / 1000.0 # in pixels per millisecond**2 return else: # if nothing recorded, display message saying so self.display.fill() self.scr.draw_text(text = \ "Noise calibration failed.\n\nPress r to retry,\nor press space to return to calibration screen.", \ pos=(self.resolution[0]/2, int(self.resolution[1]*0.2)), \ center=True, fontsize=self.fontsize) self.display.fill(self.scr) self.display.show() self.scr.clear() # wait for space or r press, if r restart noise calibration, if space return to calibration menu keypressed = self.kb.get_key(keylist=['space', 'r'], timeout=None) if keypressed[0] == 'space': break
def calibrate(self): """Calibrates the eye tracking system arguments None keyword arguments None returns success -- returns True if calibration succeeded, or False if not; in addition a calibration log is added to the log file and some properties are updated (i.e. the thresholds for detection algorithms) """ # CALIBRATION # determine the calibration points calibpoints = [] for x in [0.1, 0.5, 0.9]: for y in [0.1, 0.5, 0.9]: calibpoints.append( (int(x * self.dispsize[0]), int(y * self.dispsize[1]))) random.shuffle(calibpoints) # show a message self.screen.clear() self.screen.draw_text( text="Press Space to calibrate, S to skip, and Q to quit", fontsize=20) self.disp.fill(self.screen) self.disp.show() # wait for keyboard input key, keytime = self.kb.get_key(keylist=['q', 's', 'space'], timeout=None, flush=True) if key == 's': return True if key == 'q': quited = True else: quited = False # Pause the processing of samples during the calibration. # self.eyetribe._pause_sample_processing() # run until the user is statisfied, or quits calibrated = False calibresult = None while not quited and not calibrated: # Clear the existing calibration. if self.eyetribe._tracker.get_iscalibrated(): self.eyetribe._lock.acquire(True) self.eyetribe.calibration.clear() self.eyetribe._lock.release() # Wait for a bit. clock.pause(1500) # start a new calibration if not self.eyetribe._tracker.get_iscalibrating(): self.eyetribe._lock.acquire(True) self.eyetribe.calibration.start(pointcount=len(calibpoints)) self.eyetribe._lock.release() # loop through calibration points for cpos in calibpoints: # Check whether the calibration is already done. # (Not sure how or why, but for some reason some data # can persist between calbrations, and the tracker will # simply stop allowing further pointstart requests.) if self.eyetribe._tracker.get_iscalibrated(): break # Draw a calibration target. self.draw_calibration_target(cpos[0], cpos[1]) # wait for a bit to allow participant to start looking at # the calibration point (#TODO: space press?) clock.pause(settings.EYETRIBEPRECALIBDUR) # start calibration of point self.eyetribe._lock.acquire(True) self.eyetribe.calibration.pointstart(cpos[0], cpos[1]) self.eyetribe._lock.release() # wait for a second clock.pause(settings.EYETRIBECALIBDUR) # stop calibration of this point self.eyetribe._lock.acquire(True) self.eyetribe.calibration.pointend() self.eyetribe._lock.release() # check if the Q key has been pressed if self.kb.get_key(keylist=['q'], timeout=10, flush=False)[0] == 'q': # abort calibration self.eyetribe._lock.acquire(True) self.eyetribe.calibration.abort() self.eyetribe._lock.release() # set quited variable and break this for loop quited = True break # retry option if the calibration was aborted if quited: # show retry message self.screen.clear() self.screen.draw_text( "Calibration aborted. Press Space to restart or 'Q' to quit", fontsize=20) self.disp.fill(self.screen) self.disp.show() # get input key, keytime = self.kb.get_key(keylist=['q', 'space'], timeout=None, flush=True) if key == 'space': # unset quited Boolean quited = False # skip further processing continue # empty display self.disp.fill() self.disp.show() # allow for a bit of calculation time # (this is waaaaaay too much) clock.pause(1000) # get the calibration result self.eyetribe._lock.acquire(True) calibresult = self.eyetribe._tracker.get_calibresult() self.eyetribe._lock.release() # results # clear the screen self.screen.clear() # draw results for each point if type(calibresult) == dict: for p in calibresult['calibpoints']: # only draw the point if data was obtained if p['state'] > 0: # draw the mean error # self.screen.draw_circle(colour=(252,233,79), # pos=(p['cpx'],p['cpy']), r=p['mepix'], pw=0, # fill=True) self.screen.draw_line(spos=(p['cpx'], p['cpy']), epos=(p['mecpx'], p['mecpy']), pw=2) # draw the point self.screen.draw_fixation(fixtype='dot', colour=(115, 210, 22), pos=(p['cpx'], p['cpy'])) # draw the estimated point self.screen.draw_fixation(fixtype='dot', colour=(32, 74, 135), pos=(p['mecpx'], p['mecpy'])) # annotate accuracy self.screen.draw_text(text="{}".format(\ round(p['acd'], ndigits=2)), pos=(p['cpx']+10,p['cpy']+10), fontsize=20) # if no data was obtained, draw the point in red else: self.screen.draw_fixation(fixtype='dot', colour=(204, 0, 0), pos=(p['cpx'], p['cpy'])) # draw box for averages # self.screen.draw_rect(colour=(238,238,236), x=int(self.dispsize[0]*0.15), y=int(self.dispsize[1]*0.2), w=400, h=200, pw=0, fill=True) # draw result if calibresult['result']: self.screen.draw_text(text="Calibration successful", colour=(0, 255, 0), pos=(int(self.dispsize[0] * 0.5), int(self.dispsize[1] * 0.25)), fontsize=20) else: self.screen.draw_text(text="Calibration failed", colour=(255, 0, 0), pos=(int(self.dispsize[0] * 0.5), int(self.dispsize[1] * 0.25)), fontsize=20) # draw average accuracy self.screen.draw_text( text="Average error = {} degrees".format(round(\ calibresult['deg'], ndigits=2)), \ pos=(int(self.dispsize[0]*0.5),int(self.dispsize[1]*0.25+30)), fontsize=20) # draw input options self.screen.draw_text( text="Press Space to continue or 'R' to restart", pos=(int(self.dispsize[0] * 0.5), int(self.dispsize[1] * 0.25 + 60)), fontsize=20) else: self.screen.draw_text( text="Calibration failed. Press 'R' to try again.", fontsize=20) # show the results self.disp.fill(self.screen) self.disp.show() # wait for input key, keytime = self.kb.get_key(keylist=['space', 'r'], timeout=None, flush=True) # process input if key == 'space': calibrated = True # Continue the processing of samples after the calibration. # self.eyetribe._unpause_sample_processing() # calibration failed if the user quited if quited: return False # NOISE CALIBRATION # get all error estimates (pixels) var = [] for p in calibresult['calibpoints']: # only draw the point if data was obtained if p['state'] > 0: var.append(p['mepix']) noise = sum(var) / float(len(var)) self.pxdsttresh = (noise, noise) # AFTERMATH # store some variables pixpercm = (self.dispsize[0] / float(self.screensize[0]) + self.dispsize[1] / float(self.screensize[1])) / 2 screendist = settings.SCREENDIST # calculate thresholds based on tracker settings self.accuracy = ((calibresult['Ldeg'], calibresult['Ldeg']), (calibresult['Rdeg'], calibresult['Rdeg'])) self.pxerrdist = deg2pix(screendist, self.errdist, pixpercm) self.pxfixtresh = deg2pix(screendist, self.fixtresh, pixpercm) self.pxaccuracy = ((deg2pix(screendist, self.accuracy[0][0], pixpercm), deg2pix(screendist, self.accuracy[0][1], pixpercm)), (deg2pix(screendist, self.accuracy[1][0], pixpercm), deg2pix(screendist, self.accuracy[1][1], pixpercm))) self.pxspdtresh = deg2pix(screendist, self.spdtresh / 1000.0, pixpercm) # in pixels per millisecond self.pxacctresh = deg2pix(screendist, self.accthresh / 1000.0, pixpercm) # in pixels per millisecond**2 # calibration report self.log("pygaze calibration report start") self.log("accuracy (degrees): LX={}, LY={}, RX={}, RY={}".format( self.accuracy[0][0], self.accuracy[0][1], self.accuracy[1][0], \ self.accuracy[1][1])) self.log("accuracy (in pixels): LX={}, LY={}, RX={}, RY={}".format( \ self.pxaccuracy[0][0], self.pxaccuracy[0][1], \ self.pxaccuracy[1][0], self.pxaccuracy[1][1])) self.log("precision (RMS noise in pixels): X={}, Y={}".format( \ self.pxdsttresh[0], self.pxdsttresh[1])) self.log("distance between participant and display: {} cm".format( \ screendist)) self.log("fixation threshold: {} pixels".format(self.pxfixtresh)) self.log("speed threshold: {} pixels/ms".format(self.pxspdtresh)) self.log("acceleration threshold: {} pixels/ms**2".format( \ self.pxacctresh)) self.log("pygaze calibration report end") return True