コード例 #1
0
    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
コード例 #2
0
    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()
コード例 #3
0
    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
コード例 #4
0
    def _registerEventMonitors(self):
        kbDevice = None
        if self._eyetracker._iohub_server:
            for dev in self._eyetracker._iohub_server.devices:
                if dev.__class__.__name__ == 'Keyboard':
                    kbDevice = dev

        if kbDevice:
            eventIDs = []
            for event_class_name in kbDevice.__class__.EVENT_CLASS_NAMES:
                eventIDs.append(getattr(EC, convertCamelToSnake(event_class_name[:-5], False)))

            self._ioKeyboard = kbDevice
            self._ioKeyboard._addEventListener(self, eventIDs)
        else:
            print2err('Warning: GazePoint Cal GFX could not connect to Keyboard device for events.')
コード例 #5
0
    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
コード例 #6
0
    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'
            )
コード例 #7
0
    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
コード例 #8
0
    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
コード例 #9
0
# -*- coding: utf-8 -*-
# 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
コード例 #10
0
    def __init__(self, eyetrackerInterface, calibration_args):
        self._eyetracker = eyetrackerInterface
        self.screenSize = eyetrackerInterface._display_device.getPixelResolution()
        self.width = self.screenSize[0]
        self.height = self.screenSize[1]
        self._ioKeyboard = None
        self._msg_queue = []
        self._lastCalibrationOK = False
        self._device_config = self._eyetracker.getConfiguration()
        display = self._eyetracker._display_device

        updateSettings(self._device_config.get('calibration'), calibration_args)
        self._calibration_args = self._device_config.get('calibration')
        print2err("self._calibration_args:", self._calibration_args)
        unit_type = self.getCalibSetting('unit_type')
        if unit_type is None:
            unit_type = display.getCoordinateType()
            self._calibration_args['unit_type'] = unit_type
        color_type = self.getCalibSetting('color_type')
        if color_type is None:
            color_type = display.getColorSpace()
            self._calibration_args['color_type'] = color_type

        calibration_methods = dict(THREE_POINTS=3,
                                   FIVE_POINTS=5,
                                   NINE_POINTS=9,
                                   THIRTEEN_POINTS=13)

        cal_type = self.getCalibSetting('type')

        if cal_type in calibration_methods:
            num_points = calibration_methods[cal_type]

            if num_points == 3:
                MouseGazePsychopyCalibrationGraphics.CALIBRATION_POINT_LIST = [(0.5, 0.1),
                                                                               (0.1, 0.9),
                                                                               (0.9, 0.9)]
            elif num_points == 5:
                MouseGazePsychopyCalibrationGraphics.CALIBRATION_POINT_LIST = [(0.5, 0.5),
                                                                               (0.1, 0.1),
                                                                               (0.9, 0.1),
                                                                               (0.9, 0.9),
                                                                               (0.1, 0.9)]
            elif num_points == 9:
                MouseGazePsychopyCalibrationGraphics.CALIBRATION_POINT_LIST = [(0.5, 0.5),
                                                                               (0.1, 0.5),
                                                                               (0.9, 0.5),
                                                                               (0.1, 0.1),
                                                                               (0.5, 0.1),
                                                                               (0.9, 0.1),
                                                                               (0.9, 0.9),
                                                                               (0.5, 0.9),
                                                                               (0.1, 0.9)]
            elif num_points == 13:
                MouseGazePsychopyCalibrationGraphics.CALIBRATION_POINT_LIST = [(0.5, 0.5),
                                                                               (0.1, 0.5),
                                                                               (0.9, 0.5),
                                                                               (0.1, 0.1),
                                                                               (0.5, 0.1),
                                                                               (0.9, 0.1),
                                                                               (0.9, 0.9),
                                                                               (0.5, 0.9),
                                                                               (0.1, 0.9),
                                                                               (0.25, 0.25),
                                                                               (0.25, 0.75),
                                                                               (0.75, 0.75),
                                                                               (0.75, 0.25)
                                                                               ]

        self.window = visual.Window(
            self.screenSize,
            monitor=display.getPsychopyMonitorName(),
            units=unit_type,
            fullscr=True,
            allowGUI=False,
            screen=display.getIndex(),
            color=self.getCalibSetting(['screen_background_color']),
            colorSpace=color_type)
        self.window.flip(clearBuffer=True)

        self._createStim()
        self._registerEventMonitors()
        self._lastMsgPumpTime = currentTime()

        self.clearAllEventBuffers()
コード例 #11
0
    def runCalibration(self):
        """Run calibration sequence
        """
        instuction_text = 'Press SPACE to Start Calibration.'
        self.showSystemSetupMessageScreen(instuction_text)

        target_delay = self.getCalibSetting('target_delay')
        target_duration = self.getCalibSetting('target_duration')
        auto_pace = self.getCalibSetting('auto_pace')
        cal_target_list = self.CALIBRATION_POINT_LIST
        randomize_points = self.getCalibSetting('randomize')
        print2err('randomize:', randomize_points)
        if randomize_points is True:
            # Randomize all but first target position.
            cal_target_list = self.CALIBRATION_POINT_LIST[1:]
            import random
            random.seed(None)
            random.shuffle(cal_target_list)
            cal_target_list.insert(0, self.CALIBRATION_POINT_LIST[0])

        left, top, right, bottom = self._eyetracker._display_device.getCoordBounds()
        w, h = right - left, top - bottom

        self.clearCalibrationWindow()

        i = 0
        abort_calibration = False
        for pt in cal_target_list:
            if abort_calibration:
                break
            # Convert normalized positions to psychopy window unit positions
            # by using iohub display/window getCoordBounds.
            x, y = left + w * pt[0], bottom + h * (1.0 - pt[1])
            start_time = currentTime()

            self.clearAllEventBuffers()

            # Target animate / delay
            animate_enable = self.getCalibSetting(['target_attributes', 'animate', 'enable'])
            animate_expansion_ratio = self.getCalibSetting(['target_attributes', 'animate', 'expansion_ratio'])
            animate_contract_only = self.getCalibSetting(['target_attributes', 'animate', 'contract_only'])
            while currentTime()-start_time <= target_delay:
                if animate_enable and i > 0:
                    t = (currentTime()-start_time) / target_delay
                    v1 = cal_target_list[i-1]
                    v2 = pt
                    t = 60.0 * ((1.0 / 10.0) * t ** 5 - (1.0 / 4.0) * t ** 4 + (1.0 / 6.0) * t ** 3)
                    mx, my = ((1.0 - t) * v1[0] + t * v2[0], (1.0 - t) * v1[1] + t * v2[1])
                    moveTo = left + w * mx, bottom + h * (1.0 - my)
                    self.drawCalibrationTarget(moveTo, reset=False)
                else:
                    self.drawCalibrationTarget((x, y), False)

            gevent.sleep(0.001)
            self.MsgPump()
            msg = self.getNextMsg()
            if msg == 'QUIT':
                abort_calibration = True
                break

            # Target expand / contract phase
            self.drawCalibrationTarget((x, y))
            start_time = currentTime()
            outer_diameter = self.getCalibSetting(['target_attributes', 'outer_diameter'])
            inner_diameter = self.getCalibSetting(['target_attributes', 'inner_diameter'])
            while currentTime()-start_time <= target_duration:
                elapsed_time = currentTime()-start_time
                d = t = None
                if animate_contract_only:
                    # Change target size from outer diameter to inner diameter over target_duration seconds.
                    t = elapsed_time / target_duration
                    d = outer_diameter - t * (outer_diameter - inner_diameter)
                    self.calibrationPointOUTER.radius = d / 2
                    self.calibrationPointOUTER.draw()
                    self.calibrationPointINNER.draw()
                    self.window.flip(clearBuffer=True)
                elif animate_expansion_ratio not in [1, 1.0]:
                    if elapsed_time <= target_duration/2:
                        # In expand phase
                        t = elapsed_time / (target_duration/2)
                        d = outer_diameter + t * (outer_diameter*animate_expansion_ratio - outer_diameter)
                    else:
                        # In contract phase
                        t = (elapsed_time-target_duration/2) / (target_duration/2)
                        d = outer_diameter*animate_expansion_ratio - t * (outer_diameter*animate_expansion_ratio - inner_diameter)
                if d:
                    self.calibrationPointOUTER.radius = d / 2
                    self.calibrationPointOUTER.draw()
                    self.calibrationPointINNER.draw()
                    self.window.flip(clearBuffer=True)

            if auto_pace is False:
                while 1:
                    gevent.sleep(0.001)
                    self.MsgPump()
                    msg = self.getNextMsg()
                    if msg == 'SPACE_KEY_ACTION':
                        break
                    elif msg == 'QUIT':
                        abort_calibration = True
                        break

            gevent.sleep(0.001)
            self.MsgPump()
            msg = self.getNextMsg()
            while msg:
                if msg == 'QUIT':
                    abort_calibration = True
                    break
                gevent.sleep(0.001)
                self.MsgPump()
                msg = self.getNextMsg()

            self.clearCalibrationWindow()
            self.clearAllEventBuffers()
            i += 1

        if abort_calibration is False:
            instuction_text = "Calibration Complete. Press 'SPACE' key to continue."
            self.showSystemSetupMessageScreen(instuction_text)
            return True
        return False