def runSetupProcedure(self, calibration_args={}): """runSetupProcedure performs a calibration routine for the Tobii eye tracking system. """ try: genv = TobiiPsychopyCalibrationGraphics(self, calibration_args) calibrationOK = genv.runCalibration() # On some graphics cards, we have to minimize before closing or the calibration window will stay visible # after close is called. genv.window.winHandle.set_visible(False) genv.window.winHandle.minimize() genv.window.close() genv._unregisterEventMonitors() genv.clearAllEventBuffers() return calibrationOK except Exception: print2err('Error during runSetupProcedure') printExceptionDetailsToStdErr() return EyeTrackerConstants.EYETRACKER_ERROR
def run(rootScriptPathDir, configFilePath): s = None try: psychopy.iohub.EXP_SCRIPT_DIRECTORY = rootScriptPathDir tdir = tempfile.gettempdir() cdir, _ = os.path.split(configFilePath) if tdir == cdir: tf = open(configFilePath) ioHubConfig = json.loads(tf.read()) tf.close() os.remove(configFilePath) else: ioHubConfig = yload(open(configFilePath, 'r'), Loader=yLoader) hub_config_path = os.path.join(IOHUB_DIRECTORY, 'default_config.yaml') hub_defaults_config = yload(open(hub_config_path, 'r'), Loader=yLoader) updateDict(ioHubConfig, hub_defaults_config) s = ioServer(rootScriptPathDir, ioHubConfig) udp_port = s.config.get('udp_port', 9000) s.log("Receiving diagram's on: {}".format(udp_port)) s.udpService.start() s.setStatus("INITIALIZING") msgpump_interval = s.config.get('msgpump_interval', 0.001) glets = [] tlet = gevent.spawn(s.pumpMsgTasklet, msgpump_interval) glets.append(tlet) for m in s.deviceMonitors: m.start() glets.append(m) tlet = gevent.spawn(s.processEventsTasklet, 0.01) glets.append(tlet) if Computer.psychopy_process: tlet = gevent.spawn(s.checkForPsychopyProcess, 0.5) glets.append(tlet) s.setStatus("RUNNING") if hasattr(gevent, 'run'): gevent.run() glets = [] else: gevent.joinall(glets) lrtime = Computer.global_clock.getLastResetTime() s.log('Server END Time Offset: {0}'.format(lrtime), 'DEBUG') return True except Exception: # pylint: disable=broad-except printExceptionDetailsToStdErr() if s: s.shutdown() return False
def enableEventReporting(self, enabled=True): """enableEventReporting is functionally identical to the eye tracker device specific setRecordingState method.""" try: self.setRecordingState(enabled) enabled = EyeTrackerDevice.enableEventReporting(self, enabled) return enabled except Exception as e: print2err('Exception in EyeTracker.enableEventReporting: ', str(e)) printExceptionDetailsToStdErr()
def enableEventReporting(self, enabled=True): """enableEventReporting is functionally identical to the eye tracker device specific enableEventReporting method.""" try: enabled = EyeTrackerDevice.enableEventReporting(self, enabled) self.setRecordingState(enabled) return enabled except Exception as e: print2err('Error during enableEventReporting') printExceptionDetailsToStdErr() return EyeTrackerConstants.EYETRACKER_ERROR
def __init__(self, *args, **kwargs): EyeTrackerDevice.__init__(self, *args, **kwargs) if self.model_name: self.model_name = self.model_name.strip() if len(self.model_name) == 0: self.model_name = None model_name = self.model_name serial_num = self.getConfiguration().get('serial_number') EyeTracker._tobii = None try: EyeTracker._tobii = TobiiTracker(serial_num, model_name) except Exception: print2err('Error creating Tobii Device class') printExceptionDetailsToStdErr() # Apply license file if needed try: license_file = self.getConfiguration().get('license_file', "") if license_file != "": with open(license_file, "rb") as f: license = f.read() res = self._tobii._eyetracker.apply_licenses(license) if len(res) == 0: print2err( "Successfully applied Tobii license from: {}". format(license_file)) else: print2err( "Error: Failed to apply Tobii license from single key. " "Validation result: %s." % (res[0].validation_result)) else: print2err("No Tobii license_file in config. Skipping.") except Exception: print2err( "Error calling Tobii.apply_licenses with file {}.".format( license_file)) printExceptionDetailsToStdErr() srate = self._runtime_settings['sampling_rate'] if srate and srate in self._tobii.getAvailableSamplingRates(): self._tobii.setSamplingRate(srate) self._latest_sample = None self._latest_gaze_position = None
def runSetupProcedure(self, calibration_args: Optional[Dict] =None) -> int: """ The runSetupProcedure method starts the Pupil Capture calibration choreography. .. note:: This is a blocking call for the PsychoPy Process and will not return to the experiment script until the calibration procedure was either successful, aborted, or failed. :param calibration_args: This argument will be ignored and has only been added for the purpose of compatibility with the Common Eye Tracker Interface :return: - :py:attr:`.EyeTrackerConstants.EYETRACKER_OK` if the calibration was succesful - :py:attr:`.EyeTrackerConstants.EYETRACKER_SETUP_ABORTED` if the choreography was aborted by the user - :py:attr:`.EyeTrackerConstants.EYETRACKER_CALIBRATION_ERROR` if the calibration failed, check logs for details - :py:attr:`.EyeTrackerConstants.EYETRACKER_ERROR` if any other error occured, check logs for details """ self._pupil_remote.start_calibration() logger.info("Waiting for calibration to complete") try: for topic, payload in self._pupil_remote.fetch(endless=True): if topic.startswith("notify.calibration"): if topic.endswith("successful"): return EyeTrackerConstants.EYETRACKER_OK elif topic.endswith("stopped"): return EyeTrackerConstants.EYETRACKER_SETUP_ABORTED elif topic.endswith("failed"): print2err(f"Calibration failed: {payload}") return EyeTrackerConstants.EYETRACKER_CALIBRATION_ERROR elif "setup" in topic or "should" in topic or "start" in topic: # ignore setup data notification (includes raw reference and # pupil data that can be used to reproduce the calibration), # and calibration should_start/stop + started notifications. pass else: print2err(f"Handling for {topic} not implemented ({payload})") except Exception: print2err("Error during runSetupProcedure") printExceptionDetailsToStdErr() return EyeTrackerConstants.EYETRACKER_ERROR
def runSetupProcedure(self, calibration_args={}): """ The runSetupProcedure method starts the eye tracker calibration routine. If calibration_args are provided, they should be used to update calibration related settings prior to starting the calibration. The details of this method are implementation-specific. .. note:: This is a blocking call for the PsychoPy Process and will not return to the experiment script until the necessary steps have been completed so that the eye tracker is ready to start collecting eye sample data when the method returns. Args: None """ self._pupil_remote.start_calibration() logger.info("Waiting for calibration to complete") try: for topic, payload in self._pupil_remote.fetch(endless=True): if topic.startswith("notify.calibration"): if topic.endswith("successful"): return EyeTrackerConstants.EYETRACKER_OK elif topic.endswith("stopped"): return EyeTrackerConstants.EYETRACKER_SETUP_ABORTED elif topic.endswith("failed"): print2err(f"Calibration failed: {payload}") return EyeTrackerConstants.EYETRACKER_CALIBRATION_ERROR elif "setup" in topic or "should" in topic or "start" in topic: # ignore setup data notification (includes raw reference and # pupil data that can be used to reproduce the calibration), # and calibration should_start/stop + started notifications. pass else: print2err( f"Handling for {topic} not implemented ({payload})" ) except Exception: print2err("Error during runSetupProcedure") printExceptionDetailsToStdErr() return EyeTrackerConstants.EYETRACKER_ERROR
def _handleNativeEvent(self, *args, **kwargs): """This method is called every time there is new eye data available from the Tobii system, which will be roughly equal to the sampling rate eye data is being recorded at. The callback needs to return as quickly as possible so there is no chance of overlapping calls being made to the callback. Therefore this method simply puts the event data received from the eye tracker device, and the local ioHub time the callback was called, into a buffer for processing by the ioHub event system. """ if self.isReportingEvents(): try: logged_time = Computer.getTime() tobii_logged_time = self._tobii.getCurrentLocalTobiiTime( ) * self.DEVICE_TIMEBASE_TO_SEC eye_data_event = args[0] data_delay = tobii_logged_time - ( eye_data_event['system_time_stamp'] * self.DEVICE_TIMEBASE_TO_SEC) device_event_time = eye_data_event['device_time_stamp'] iohub_event_time = (logged_time - data_delay) self._addNativeEventToBuffer( (logged_time, device_event_time, iohub_event_time, data_delay, eye_data_event)) return True except Exception: print2err('ERROR IN _handleNativeEvent') printExceptionDetailsToStdErr() else: print2err( 'self._handleNativeEvent called but isReportingEvents == false' )
def _getIOHubEventObject(self, native_event_data): """The _getIOHubEventObject method is called by the ioHub Server to convert new native device event objects that have been received to the appropriate ioHub Event type representation. The Tobii ioHub eye tracker implementation uses a callback method to register new native device events with the ioHub Server. Therefore this method converts the native Tobii event data into an appropriate ioHub Event representation. Args: native_event_data: object or tuple of (callback_time, native_event_object) Returns: tuple: The appropriate ioHub Event type in list form. """ try: logged_time, device_event_time, iohub_event_time, data_delay, eye_data_event = native_event_data event_type = EventConstants.BINOCULAR_EYE_SAMPLE left_gaze_x, left_gaze_y = eye_data_event[ 'left_gaze_point_on_display_area'] right_gaze_x, right_gaze_y = eye_data_event[ 'right_gaze_point_on_display_area'] status = 0 if eye_data_event['left_gaze_point_validity'] > 0: left_gaze_x, left_gaze_y = self._eyeTrackerToDisplayCoords( (left_gaze_x, left_gaze_y)) else: status += 20 if eye_data_event['right_gaze_point_validity'] > 0: right_gaze_x, right_gaze_y = self._eyeTrackerToDisplayCoords( (right_gaze_x, right_gaze_y)) else: status += 2 right_gx, right_gy, right_gz = eye_data_event[ 'right_gaze_origin_in_trackbox_coordinate_system'] left_gx, left_gy, left_gz = eye_data_event[ 'left_gaze_origin_in_trackbox_coordinate_system'] confidenceInterval = 0.0 binocSample = [ 0, 0, 0, # device id (not currently used) Device._getNextEventID(), event_type, device_event_time, logged_time, iohub_event_time, confidenceInterval, data_delay, 0, # filtered id (always 0 right now) left_gaze_x, left_gaze_y, EyeTrackerConstants.UNDEFINED, left_gx, left_gy, left_gz, EyeTrackerConstants.UNDEFINED, # Left Eye Angle x EyeTrackerConstants.UNDEFINED, # Left Eye Angle y EyeTrackerConstants.UNDEFINED, # Left Camera Sensor position x EyeTrackerConstants.UNDEFINED, # Left Camera Sensor position y eye_data_event['left_pupil_diameter'], EyeTrackerConstants.PUPIL_DIAMETER_MM, EyeTrackerConstants.UNDEFINED, # Left pupil size measure 2 EyeTrackerConstants. UNDEFINED, # Left pupil size measure 2 type EyeTrackerConstants.UNDEFINED, # Left PPD x EyeTrackerConstants.UNDEFINED, # Left PPD y EyeTrackerConstants.UNDEFINED, # Left velocity x EyeTrackerConstants.UNDEFINED, # Left velocity y EyeTrackerConstants.UNDEFINED, # Left velocity xy right_gaze_x, right_gaze_y, EyeTrackerConstants.UNDEFINED, # Right Eye Angle z right_gx, right_gy, right_gz, EyeTrackerConstants.UNDEFINED, # Right Eye Angle x EyeTrackerConstants.UNDEFINED, # Right Eye Angle y EyeTrackerConstants. UNDEFINED, # Right Camera Sensor position x EyeTrackerConstants. UNDEFINED, # Right Camera Sensor position y eye_data_event['right_pupil_diameter'], EyeTrackerConstants.PUPIL_DIAMETER_MM, EyeTrackerConstants.UNDEFINED, # Right pupil size measure 2 EyeTrackerConstants. UNDEFINED, # Right pupil size measure 2 type EyeTrackerConstants.UNDEFINED, # Right PPD x EyeTrackerConstants.UNDEFINED, # Right PPD y EyeTrackerConstants.UNDEFINED, # right velocity x EyeTrackerConstants.UNDEFINED, # right velocity y EyeTrackerConstants.UNDEFINED, # right velocity xy status ] self._latest_sample = binocSample if eye_data_event['left_gaze_point_validity'] == eye_data_event[ 'right_gaze_point_validity'] == 0: self._latest_gaze_position = None elif eye_data_event['left_gaze_point_validity'] == eye_data_event[ 'right_gaze_point_validity'] == 1: self._latest_gaze_position = [ (right_gaze_x + left_gaze_x) / 2.0, (right_gaze_y + left_gaze_y) / 2.0 ] elif eye_data_event['left_gaze_point_validity'] == 1: self._latest_gaze_position = [left_gaze_x, left_gaze_y] elif eye_data_event['right_gaze_point_validity'] == 1: self._latest_gaze_position = [right_gaze_x, right_gaze_y] self._last_callback_time = logged_time return binocSample except Exception: printExceptionDetailsToStdErr() return None
# Part of the PsychoPy library # Copyright (C) 2012-2020 iSolver Software Solutions (C) 2021 Open Science Tools Ltd. # Distributed under the terms of the GNU General Public License (GPL). import math from psychopy.iohub.constants import EventConstants, EyeTrackerConstants from psychopy.iohub.devices import Computer, Device from psychopy.iohub.devices.eyetracker import EyeTrackerDevice from psychopy.iohub.devices.eyetracker.hw.tobii.tobiiCalibrationGraphics import TobiiPsychopyCalibrationGraphics from psychopy.iohub.devices.eyetracker.eye_events import * from psychopy.iohub.errors import print2err, printExceptionDetailsToStdErr try: from .tobiiwrapper import TobiiTracker except Exception: print2err('Error importing tobiiwrapper.TobiiTracker') printExceptionDetailsToStdErr() class EyeTracker(EyeTrackerDevice): """ To start iohub with a Tobii eye tracker device, add the Tobii device to the dictionary passed to launchHubServer or the experiment's iohub_config.yaml:: eyetracker.hw.tobii.EyeTracker Examples: A. Start ioHub with a Tobii device and run tracker calibration:: from psychopy.iohub import launchHubServer from psychopy.core import getTime, wait