Ejemplo n.º 1
0
    def __init__(
            self,
            eyetracker_info=None,
            product_id=None,
            model=None,
            mainloop=None,
            create_sync_manager=True):
        self._eyetracker_info = eyetracker_info
        self._requested_product_id = product_id
        self._requested_model = model
        self._mainloop = mainloop
        self._eyetracker = None
        self._queue = None
        self._tobiiClock = None
        self._getTobiiClockResolution = None
        self._getTobiiClockTime = None
        self._sync_manager = None
        self._syncTimeEventDeque = None
        self._isRecording = False

        if eyetracker_info is None:
            if not TobiiTrackerBrowser.isActive():
                TobiiTrackerBrowser.start()
                self._eyetracker_info = TobiiTrackerBrowser.findDevice(
                    model, product_id)
                if self._eyetracker_info:
                    self._mainloop = TobiiTrackerBrowser.getMainLoop()
                TobiiTrackerBrowser.stop()
            else:
                self._eyetracker_info = TobiiTrackerBrowser.findDevice(
                    model, product_id)

        if self._eyetracker_info is None:
            raise exceptions.BaseException(
                'Could not find a Tobii Eye Tracker matching requirements.')

        if self._mainloop is None:
            if TobiiTrackerBrowser.isActive():
                self._mainloop = TobiiTrackerBrowser.getMainLoop()
            else:
                TobiiPy.init()
                self._mainloop = TobiiPyMainloopThread()
                self._mainloop.start()

        self._queue = Queue.Queue()

        TobiiPyEyeTracker.create_async(
            self._mainloop,
            self._eyetracker_info,
            self.on_eyetracker_created)

        stime = getTime()
        while getTime() - stime < 10.0:
            try:
                event = self._queue.get(block=True, timeout=.1)
                if isinstance(event, TobiiTrackerCreatedEvent):
                    self._eyetracker = event.tracker_object
                    self._eyetracker.events.OnFramerateChanged += self.on_external_framerate_change
                    if hasattr(
                            self._eyetracker.events,
                            'OnHeadMovementBoxChanged'):
                        self._eyetracker.events.OnHeadMovementBoxChanged += self.on_head_box_change
                    elif hasattr(self._eyetracker.events, 'OnTrackBoxChanged'):
                        self._eyetracker.events.OnTrackBoxChanged += self.on_head_box_change
                    else:
                        print 'WARNING: TobiiClasses could not set callback hook for "self.on_head_box_change".'
                    self._eyetracker.events.OnXConfigurationChanged += self.on_x_series_physical_config_change

                    break
                self._queue.task_done()
            except Queue.Empty:
                pass

        if self._eyetracker is None:
            raise exceptions.BaseException(
                'Could not connect to Tobii. Timeout.')

        if create_sync_manager:
            self._eyetracker.events.OnError += self.on_eyetracker_error
            self._tobiiClock = TobiiPyClock()
            self._getTobiiClockResolution = self._tobiiClock.get_resolution
            self._getTobiiClockTime = self._tobiiClock.get_time
            self._syncTimeEventDeque = collections.deque(maxlen=32)
            self._sync_manager = TobiiPySyncManager(self._tobiiClock,
                                                    self._eyetracker_info,
                                                    self._mainloop,
                                                    self.on_sync_error,
                                                    self.on_sync_status)
Ejemplo n.º 2
0
    def __init__(self,
                 eyetracker_info=None,
                 product_id=None,
                 model=None,
                 mainloop=None,
                 create_sync_manager=True):
        self._eyetracker_info = eyetracker_info
        self._requested_product_id = product_id
        self._requested_model = model
        self._mainloop = mainloop
        self._eyetracker = None
        self._queue = None
        self._tobiiClock = None
        self._getTobiiClockResolution = None
        self._getTobiiClockTime = None
        self._sync_manager = None
        self._syncTimeEventDeque = None
        self._isRecording = False

        if eyetracker_info is None:
            if not TobiiTrackerBrowser.isActive():
                TobiiTrackerBrowser.start()
                self._eyetracker_info = TobiiTrackerBrowser.findDevice(
                    model, product_id)
                if self._eyetracker_info:
                    self._mainloop = TobiiTrackerBrowser.getMainLoop()
                TobiiTrackerBrowser.stop()
            else:
                self._eyetracker_info = TobiiTrackerBrowser.findDevice(
                    model, product_id)

        if self._eyetracker_info is None:
            raise exceptions.BaseException(
                'Could not find a Tobii Eye Tracker matching requirements.')

        if self._mainloop is None:
            if TobiiTrackerBrowser.isActive():
                self._mainloop = TobiiTrackerBrowser.getMainLoop()
            else:
                TobiiPy.init()
                self._mainloop = TobiiPyMainloopThread()
                self._mainloop.start()

        self._queue = Queue.Queue()

        TobiiPyEyeTracker.create_async(self._mainloop, self._eyetracker_info,
                                       self.on_eyetracker_created)

        stime = getTime()
        while getTime() - stime < 10.0:
            try:
                event = self._queue.get(block=True, timeout=.1)
                if isinstance(event, TobiiTrackerCreatedEvent):
                    self._eyetracker = event.tracker_object
                    self._eyetracker.events.OnFramerateChanged += self.on_external_framerate_change
                    if hasattr(self._eyetracker.events,
                               'OnHeadMovementBoxChanged'):
                        self._eyetracker.events.OnHeadMovementBoxChanged += self.on_head_box_change
                    elif hasattr(self._eyetracker.events, 'OnTrackBoxChanged'):
                        self._eyetracker.events.OnTrackBoxChanged += self.on_head_box_change
                    else:
                        print 'WARNING: TobiiClasses could not set callback hook for "self.on_head_box_change".'
                    self._eyetracker.events.OnXConfigurationChanged += self.on_x_series_physical_config_change

                    break
                self._queue.task_done()
            except Queue.Empty:
                pass

        if self._eyetracker is None:
            raise exceptions.BaseException(
                'Could not connect to Tobii. Timeout.')

        if create_sync_manager:
            self._eyetracker.events.OnError += self.on_eyetracker_error
            self._tobiiClock = TobiiPyClock()
            self._getTobiiClockResolution = self._tobiiClock.get_resolution
            self._getTobiiClockTime = self._tobiiClock.get_time
            self._syncTimeEventDeque = collections.deque(maxlen=32)
            self._sync_manager = TobiiPySyncManager(self._tobiiClock,
                                                    self._eyetracker_info,
                                                    self._mainloop,
                                                    self.on_sync_error,
                                                    self.on_sync_status)
Ejemplo n.º 3
0
class TobiiTracker(object):
    LEFT = 0
    RIGHT = 1
    eye_data = OrderedDict(tracker_time_usec=np.NaN,
                           pupil_diameter_mm=np.NaN,
                           gaze_norm=[np.NaN, np.NaN],
                           gaze_mm=[np.NaN, np.NaN, np.NaN],
                           eye_location_norm=[np.NaN, np.NaN, np.NaN],
                           eye_location_mm=[np.NaN, np.NaN, np.NaN],
                           validity_code=np.NaN,
                           status='UNKNOWN',
                           trigger_signal=None)

    def __init__(
            self,
            eyetracker_info=None,
            product_id=None,
            model=None,
            mainloop=None,
            create_sync_manager=True):
        self._eyetracker_info = eyetracker_info
        self._requested_product_id = product_id
        self._requested_model = model
        self._mainloop = mainloop
        self._eyetracker = None
        self._queue = None
        self._tobiiClock = None
        self._getTobiiClockResolution = None
        self._getTobiiClockTime = None
        self._sync_manager = None
        self._syncTimeEventDeque = None
        self._isRecording = False

        if eyetracker_info is None:
            if not TobiiTrackerBrowser.isActive():
                TobiiTrackerBrowser.start()
                self._eyetracker_info = TobiiTrackerBrowser.findDevice(
                    model, product_id)
                if self._eyetracker_info:
                    self._mainloop = TobiiTrackerBrowser.getMainLoop()
                TobiiTrackerBrowser.stop()
            else:
                self._eyetracker_info = TobiiTrackerBrowser.findDevice(
                    model, product_id)

        if self._eyetracker_info is None:
            raise exceptions.BaseException(
                'Could not find a Tobii Eye Tracker matching requirements.')

        if self._mainloop is None:
            if TobiiTrackerBrowser.isActive():
                self._mainloop = TobiiTrackerBrowser.getMainLoop()
            else:
                TobiiPy.init()
                self._mainloop = TobiiPyMainloopThread()
                self._mainloop.start()

        self._queue = Queue.Queue()

        TobiiPyEyeTracker.create_async(
            self._mainloop,
            self._eyetracker_info,
            self.on_eyetracker_created)

        stime = getTime()
        while getTime() - stime < 10.0:
            try:
                event = self._queue.get(block=True, timeout=.1)
                if isinstance(event, TobiiTrackerCreatedEvent):
                    self._eyetracker = event.tracker_object
                    self._eyetracker.events.OnFramerateChanged += self.on_external_framerate_change
                    if hasattr(
                            self._eyetracker.events,
                            'OnHeadMovementBoxChanged'):
                        self._eyetracker.events.OnHeadMovementBoxChanged += self.on_head_box_change
                    elif hasattr(self._eyetracker.events, 'OnTrackBoxChanged'):
                        self._eyetracker.events.OnTrackBoxChanged += self.on_head_box_change
                    else:
                        print 'WARNING: TobiiClasses could not set callback hook for "self.on_head_box_change".'
                    self._eyetracker.events.OnXConfigurationChanged += self.on_x_series_physical_config_change

                    break
                self._queue.task_done()
            except Queue.Empty:
                pass

        if self._eyetracker is None:
            raise exceptions.BaseException(
                'Could not connect to Tobii. Timeout.')

        if create_sync_manager:
            self._eyetracker.events.OnError += self.on_eyetracker_error
            self._tobiiClock = TobiiPyClock()
            self._getTobiiClockResolution = self._tobiiClock.get_resolution
            self._getTobiiClockTime = self._tobiiClock.get_time
            self._syncTimeEventDeque = collections.deque(maxlen=32)
            self._sync_manager = TobiiPySyncManager(self._tobiiClock,
                                                    self._eyetracker_info,
                                                    self._mainloop,
                                                    self.on_sync_error,
                                                    self.on_sync_status)

    def on_eyetracker_created(self, *args, **kwargs):
        # print 'on_eyetracker_created: entered'
        et = None
        if len(args) >= 2:
            et = args[1]
        else:
            raise exceptions.BaseException(
                'WARNING: on_eyetracker_created: Unhandled args count', len(args), args)

        error = kwargs.get('error', None)
        if error:
            raise exceptions.BaseException(
                'Connection to Tobii failed because of an exception: %s' %
                (str(error),))

        self._queue.put(TobiiTrackerCreatedEvent(et))

        return False

    def on_eyetracker_error(self, *args, **kwargs):
        print2err('TobiiTracker.on_eyetracker_error: ', args, kwargs)
        return False

    def on_sync_error(self, *args, **kwargs):
        print2err('TobiiTracker.on_sync_error: ', args, kwargs)
        return False

    def on_sync_status(self, *args, **kwargs):
        sync_state = args[0]
        self._syncTimeEventDeque.append(sync_state)
        return False

    def on_eyetracker_data(self, *args, **kwargs):
        eye_data_event = args[1]
        LEFT = self.LEFT
        RIGHT = self.RIGHT

        eyes = (copy.deepcopy(self.eye_data), copy.deepcopy(self.eye_data))

        eyes[LEFT]['validity_code'] = eye_data_event.LeftValidity
        eyes[RIGHT]['validity_code'] = eye_data_event.RightValidity
        eyes[LEFT]['tracker_time_usec'] = eye_data_event.Timestamp
        eyes[RIGHT]['tracker_time_usec'] = eye_data_event.Timestamp
        if hasattr(eye_data_event, 'TrigSignal'):
            eyes[LEFT]['trigger_signal'] = eye_data_event.TrigSignal
            eyes[RIGHT]['trigger_signal'] = eye_data_event.TrigSignal

        # print "*** lastEyeData.RightGazePoint2D:
        # ",lastEyeData.RightGazePoint2D.__dict__
        if eye_data_event.LeftValidity >= 2 and eye_data_event.RightValidity >= 2:
            # no eye signal
            eyes[LEFT]['status'] = 'Missing'
            eyes[RIGHT]['status'] = 'Missing'

        elif eye_data_event.LeftValidity < 2 and eye_data_event.RightValidity >= 2:
            # left eye only available
            eyes[LEFT]['status'] = 'Available'
            eyes[RIGHT]['status'] = 'Missing'

            eyes[LEFT]['pupil_diameter_mm'] = eye_data_event.LeftPupil
            eyes[LEFT]['gaze_norm'][0] = eye_data_event.LeftGazePoint2D.x
            eyes[LEFT]['gaze_norm'][1] = eye_data_event.LeftGazePoint2D.y
            eyes[LEFT]['gaze_mm'][0] = eye_data_event.LeftGazePoint3D.x
            eyes[LEFT]['gaze_mm'][1] = eye_data_event.LeftGazePoint3D.y
            eyes[LEFT]['gaze_mm'][2] = eye_data_event.LeftGazePoint3D.z
            eyes[LEFT]['eye_location_norm'][
                0] = eye_data_event.LeftEyePosition3DRelative.x
            eyes[LEFT]['eye_location_norm'][
                1] = eye_data_event.LeftEyePosition3DRelative.y
            eyes[LEFT]['eye_location_norm'][
                2] = eye_data_event.LeftEyePosition3DRelative.z
            eyes[LEFT]['eye_location_mm'][
                0] = eye_data_event.LeftEyePosition3D.x
            eyes[LEFT]['eye_location_mm'][
                1] = eye_data_event.LeftEyePosition3D.y
            eyes[LEFT]['eye_location_mm'][
                2] = eye_data_event.LeftEyePosition3D.z

        elif eye_data_event.LeftValidity >= 2 and eye_data_event.RightValidity < 2:
            # right eye only available
            eyes[RIGHT]['status'] = 'Available'
            eyes[LEFT]['status'] = 'Missing'

            eyes[RIGHT]['pupil_diameter_mm'] = eye_data_event.RightPupil
            eyes[RIGHT]['gaze_norm'][0] = eye_data_event.RightGazePoint2D.x
            eyes[RIGHT]['gaze_norm'][1] = eye_data_event.RightGazePoint2D.y
            eyes[RIGHT]['gaze_mm'][0] = eye_data_event.RightGazePoint3D.x
            eyes[RIGHT]['gaze_mm'][1] = eye_data_event.RightGazePoint3D.y
            eyes[RIGHT]['gaze_mm'][2] = eye_data_event.RightGazePoint3D.z
            eyes[RIGHT]['eye_location_norm'][
                0] = eye_data_event.RightEyePosition3DRelative.x
            eyes[RIGHT]['eye_location_norm'][
                1] = eye_data_event.RightEyePosition3DRelative.y
            eyes[RIGHT]['eye_location_norm'][
                2] = eye_data_event.RightEyePosition3DRelative.z
            eyes[RIGHT]['eye_location_mm'][
                0] = eye_data_event.RightEyePosition3D.x
            eyes[RIGHT]['eye_location_mm'][
                1] = eye_data_event.RightEyePosition3D.y
            eyes[RIGHT]['eye_location_mm'][
                2] = eye_data_event.RightEyePosition3D.z
        else:
            # binocular available
            eyes[RIGHT]['status'] = 'Available'
            eyes[LEFT]['status'] = 'Available'

            eyes[LEFT]['pupil_diameter_mm'] = eye_data_event.LeftPupil
            eyes[LEFT]['gaze_norm'][0] = eye_data_event.LeftGazePoint2D.x
            eyes[LEFT]['gaze_norm'][1] = eye_data_event.LeftGazePoint2D.y
            eyes[LEFT]['gaze_mm'][0] = eye_data_event.LeftGazePoint3D.x
            eyes[LEFT]['gaze_mm'][1] = eye_data_event.LeftGazePoint3D.y
            eyes[LEFT]['gaze_mm'][2] = eye_data_event.LeftGazePoint3D.z
            eyes[LEFT]['eye_location_norm'][
                0] = eye_data_event.LeftEyePosition3DRelative.x
            eyes[LEFT]['eye_location_norm'][
                1] = eye_data_event.LeftEyePosition3DRelative.y
            eyes[LEFT]['eye_location_norm'][
                2] = eye_data_event.LeftEyePosition3DRelative.z
            eyes[LEFT]['eye_location_mm'][
                0] = eye_data_event.LeftEyePosition3D.x
            eyes[LEFT]['eye_location_mm'][
                1] = eye_data_event.LeftEyePosition3D.y
            eyes[LEFT]['eye_location_mm'][
                2] = eye_data_event.LeftEyePosition3D.z

            eyes[RIGHT]['pupil_diameter_mm'] = eye_data_event.RightPupil
            eyes[RIGHT]['gaze_norm'][0] = eye_data_event.RightGazePoint2D.x
            eyes[RIGHT]['gaze_norm'][1] = eye_data_event.RightGazePoint2D.y
            eyes[RIGHT]['gaze_mm'][0] = eye_data_event.RightGazePoint3D.x
            eyes[RIGHT]['gaze_mm'][1] = eye_data_event.RightGazePoint3D.y
            eyes[RIGHT]['gaze_mm'][2] = eye_data_event.RightGazePoint3D.z
            eyes[RIGHT]['eye_location_norm'][
                0] = eye_data_event.RightEyePosition3DRelative.x
            eyes[RIGHT]['eye_location_norm'][
                1] = eye_data_event.RightEyePosition3DRelative.y
            eyes[RIGHT]['eye_location_norm'][
                2] = eye_data_event.RightEyePosition3DRelative.z
            eyes[RIGHT]['eye_location_mm'][
                0] = eye_data_event.RightEyePosition3D.x
            eyes[RIGHT]['eye_location_mm'][
                1] = eye_data_event.RightEyePosition3D.y
            eyes[RIGHT]['eye_location_mm'][
                2] = eye_data_event.RightEyePosition3D.z

        return False

    def on_start_tracking(self, *args, **kwargs):
        return False

    def on_stop_tracking(self, *args, **kwargs):
        return False

    def on_external_framerate_change(self, *args, **kwargs):
        print2err('NOTE: Tobii System Sampling Rate Changed.')
        return False

    def on_head_box_change(self, *args, **kwargs):
        print2err('NOTE: Tobii Head Movement Box Changed.')
        return False

    def on_x_series_physical_config_change(self, *args, **kwargs):
        print2err('NOTE: Tobii X Series Physical Settings Changed.')
        return False

    def getTimeSyncManager(self):
        return self._sync_manager

    def getTimeSyncState(self):
        return self._sync_manager.sync_state()

    def getCurrentEyeTrackerTime(self):
        return self._sync_manager.convert_from_local_to_remote(
            self._getTobiiClockTime())

    def getCurrentLocalTobiiTime(self):
        return self._getTobiiClockTime()

    def getTobiiTimeResolution(self):
        return self._getTobiiClockResolution()

    def getMainLoop(self):
        return self._mainloop

    def getTrackerDetails(self):
        tobiiInfoProperties = [
            'product_id',
            'given_name',
            'model',
            'generation',
            'firmware_version',
            'status']
        tprops = OrderedDict()
        eyetracker_info = self._eyetracker_info
        for tp in tobiiInfoProperties:
            ta = getattr(eyetracker_info, tp)
            if callable(ta):
                ta = ta()
            tprops[tp] = ta
        tprops['factory_info_string'] = str(eyetracker_info.factory_info)
        return tprops

    def getTrackerInfo(self):
        if hasattr(self._eyetracker, 'GetUnitInfo'):
            return self._eyetracker.GetUnitInfo()
        return None

    def startTracking(self, et_data_rx_callback=None):
        if et_data_rx_callback:
            self.on_eyetracker_data = et_data_rx_callback
        self._eyetracker.events.OnGazeDataReceived += self.on_eyetracker_data
        self._eyetracker.StartTracking(self.on_start_tracking)
        self._isRecording = True
        return True

    def stopTracking(self):
        self._eyetracker.events.OnGazeDataReceived -= self.on_eyetracker_data
        self._eyetracker.StopTracking(self.on_stop_tracking)
        self._isRecording = False

    def getName(self):
        return self._eyetracker.GetUnitName()

    def setName(self, name):
        self._eyetracker.SetUnitName(name)

    def getLowBlinkMode(self):
        try:
            return self._eyetracker.GetLowblinkMode()
        except Exception:
            pass
        return None

    def setLowBlinkMode(self, enable):
        if hasattr(self._eyetracker, 'SetLowblinkMode'):
            try:
                if isinstance(enable, bool) or enable == 0 or enable == 1:
                    self._eyetracker.SetLowblinkMode(enable)
                return self._eyetracker.GetLowblinkMode()
            except Exception:
                pass
        return None

    def setSamplingRate(self, rate):
        if rate in self._eyetracker.EnumerateFramerates():
            self._eyetracker.SetFramerate(rate)
            return rate
        return self._eyetracker.GetFramerate()

    def getAvailableSamplingRates(self):
        return self._eyetracker.EnumerateFramerates()

    def getSamplingRate(self):
        return self._eyetracker.GetFramerate()

    def getIlluminationMode(self):
        try:
            return self._eyetracker.getIlluminationMode()
        except Exception:
            pass
        return None

    def getAvailableIlluminationModes(self):
        try:
            return self._eyetracker.EnumerateIlluminationModes()
        except Exception:
            return []

    def setIlluminationMode(self, imode):
        imodes = self.getAvailableIlluminationModes()
        if len(imodes) > 0:
            if imode in imodes:
                self._eyetracker.SetIlluminationMode(imode)
                return imode
            return self._eyetracker.getIlluminationMode()
        else:
            print 'WARNING: setIlluminationMode is not supported by either your Tobii model, or the version of the Tobii SDK being used.'

    def getHeadBox(self):
        hb = None
        if hasattr(self._eyetracker, 'GetTrackBox'):
            hb = self._eyetracker.GetTrackBox()
        elif hasattr(self._eyetracker, 'GetHeadMovementBox'):
            hb = self._eyetracker.GetHeadMovementBox()

        if hb:
            return np.asarray([
                (hb.Point1.x, hb.Point1.y, hb.Point1.z),
                (hb.Point2.x, hb.Point2.y, hb.Point2.z),
                (hb.Point3.x, hb.Point3.y, hb.Point3.z),
                (hb.Point4.x, hb.Point4.y, hb.Point4.z),
                (hb.Point5.x, hb.Point5.y, hb.Point5.z),
                (hb.Point6.x, hb.Point6.y, hb.Point6.z),
                (hb.Point7.x, hb.Point7.y, hb.Point7.z),
                (hb.Point8.x, hb.Point8.y, hb.Point8.z)
            ])
        return None

    def setXSeriesPhysicalPlacement(self, upperLeft, upperRight, lowerLeft):
        if self.getTrackerDetails()['generation'] == 'X':
            self._eyetracker.SetXConfiguration(upperLeft,
                                               upperRight,
                                               lowerLeft)
            return True
        return False

    def getEyeTrackerPhysicalPlacement(self):
        etpc = self._eyetracker.GetXConfiguration()
        ll = etpc.LowerLeft
        ul = etpc.UpperLeft
        ur = etpc.UpperRight
        return dict(lowerLeft=(ll.x, ll.y, ll.z),
                    upperLeft=(ul.x, ul.y, ul.z),
                    upperRight=(ur.x, ur.y, ur.z))

    def getAvailableExtensions(self):
        return [
            OrderedDict(
                name=e.Name,
                extensionId=e.ExtensionId,
                protocolVersion=e.ProtocolVersion,
                realm=e.Realm) for e in self._eyetracker.GetAvailableExtensions()]

    def getEnabledExtensions(self):
        return [
            OrderedDict(
                name=e.Name,
                extensionId=e.ExtensionId,
                protocolVersion=e.ProtocolVersion,
                realm=e.Realm) for e in self._eyetracker.GetEnabledExtensions()]

    def enableExtension(self, extension):
        extension_id = extension
        if isinstance(extension, OrderedDict):
            extension_id = extension['extensionId']
        self._eyetracker.EnableExtension(extension_id)

    def disconnect(self):
        if self._isRecording:
            self.stopTracking()
        self._mainloop.stop()
        self._mainloop = None
        self._eyetracker_info = None
        self._requested_product_id = None
        self._requested_model = None
        self._eyetracker = None

    def __del__(self):
        if self._mainloop:
            self.disconnect()
Ejemplo n.º 4
0
class TobiiTracker(object):
    LEFT = 0
    RIGHT = 1
    eye_data = OrderedDict(tracker_time_usec=np.NaN,
                           pupil_diameter_mm=np.NaN,
                           gaze_norm=[np.NaN, np.NaN],
                           gaze_mm=[np.NaN, np.NaN, np.NaN],
                           eye_location_norm=[np.NaN, np.NaN, np.NaN],
                           eye_location_mm=[np.NaN, np.NaN, np.NaN],
                           validity_code=np.NaN,
                           status='UNKNOWN',
                           trigger_signal=None)

    def __init__(self,
                 eyetracker_info=None,
                 product_id=None,
                 model=None,
                 mainloop=None,
                 create_sync_manager=True):
        self._eyetracker_info = eyetracker_info
        self._requested_product_id = product_id
        self._requested_model = model
        self._mainloop = mainloop
        self._eyetracker = None
        self._queue = None
        self._tobiiClock = None
        self._getTobiiClockResolution = None
        self._getTobiiClockTime = None
        self._sync_manager = None
        self._syncTimeEventDeque = None
        self._isRecording = False

        if eyetracker_info is None:
            if not TobiiTrackerBrowser.isActive():
                TobiiTrackerBrowser.start()
                self._eyetracker_info = TobiiTrackerBrowser.findDevice(
                    model, product_id)
                if self._eyetracker_info:
                    self._mainloop = TobiiTrackerBrowser.getMainLoop()
                TobiiTrackerBrowser.stop()
            else:
                self._eyetracker_info = TobiiTrackerBrowser.findDevice(
                    model, product_id)

        if self._eyetracker_info is None:
            raise exceptions.BaseException(
                'Could not find a Tobii Eye Tracker matching requirements.')

        if self._mainloop is None:
            if TobiiTrackerBrowser.isActive():
                self._mainloop = TobiiTrackerBrowser.getMainLoop()
            else:
                TobiiPy.init()
                self._mainloop = TobiiPyMainloopThread()
                self._mainloop.start()

        self._queue = Queue.Queue()

        TobiiPyEyeTracker.create_async(self._mainloop, self._eyetracker_info,
                                       self.on_eyetracker_created)

        stime = getTime()
        while getTime() - stime < 10.0:
            try:
                event = self._queue.get(block=True, timeout=.1)
                if isinstance(event, TobiiTrackerCreatedEvent):
                    self._eyetracker = event.tracker_object
                    self._eyetracker.events.OnFramerateChanged += self.on_external_framerate_change
                    if hasattr(self._eyetracker.events,
                               'OnHeadMovementBoxChanged'):
                        self._eyetracker.events.OnHeadMovementBoxChanged += self.on_head_box_change
                    elif hasattr(self._eyetracker.events, 'OnTrackBoxChanged'):
                        self._eyetracker.events.OnTrackBoxChanged += self.on_head_box_change
                    else:
                        print 'WARNING: TobiiClasses could not set callback hook for "self.on_head_box_change".'
                    self._eyetracker.events.OnXConfigurationChanged += self.on_x_series_physical_config_change

                    break
                self._queue.task_done()
            except Queue.Empty:
                pass

        if self._eyetracker is None:
            raise exceptions.BaseException(
                'Could not connect to Tobii. Timeout.')

        if create_sync_manager:
            self._eyetracker.events.OnError += self.on_eyetracker_error
            self._tobiiClock = TobiiPyClock()
            self._getTobiiClockResolution = self._tobiiClock.get_resolution
            self._getTobiiClockTime = self._tobiiClock.get_time
            self._syncTimeEventDeque = collections.deque(maxlen=32)
            self._sync_manager = TobiiPySyncManager(self._tobiiClock,
                                                    self._eyetracker_info,
                                                    self._mainloop,
                                                    self.on_sync_error,
                                                    self.on_sync_status)

    def on_eyetracker_created(self, *args, **kwargs):
        # print 'on_eyetracker_created: entered'
        et = None
        if len(args) >= 2:
            et = args[1]
        else:
            raise exceptions.BaseException(
                'WARNING: on_eyetracker_created: Unhandled args count',
                len(args), args)

        error = kwargs.get('error', None)
        if error:
            raise exceptions.BaseException(
                'Connection to Tobii failed because of an exception: %s' %
                (str(error), ))

        self._queue.put(TobiiTrackerCreatedEvent(et))

        return False

    def on_eyetracker_error(self, *args, **kwargs):
        print2err('TobiiTracker.on_eyetracker_error: ', args, kwargs)
        return False

    def on_sync_error(self, *args, **kwargs):
        print2err('TobiiTracker.on_sync_error: ', args, kwargs)
        return False

    def on_sync_status(self, *args, **kwargs):
        sync_state = args[0]
        self._syncTimeEventDeque.append(sync_state)
        return False

    def on_eyetracker_data(self, *args, **kwargs):
        eye_data_event = args[1]
        LEFT = self.LEFT
        RIGHT = self.RIGHT

        eyes = (copy.deepcopy(self.eye_data), copy.deepcopy(self.eye_data))

        eyes[LEFT]['validity_code'] = eye_data_event.LeftValidity
        eyes[RIGHT]['validity_code'] = eye_data_event.RightValidity
        eyes[LEFT]['tracker_time_usec'] = eye_data_event.Timestamp
        eyes[RIGHT]['tracker_time_usec'] = eye_data_event.Timestamp
        if hasattr(eye_data_event, 'TrigSignal'):
            eyes[LEFT]['trigger_signal'] = eye_data_event.TrigSignal
            eyes[RIGHT]['trigger_signal'] = eye_data_event.TrigSignal

        # print "*** lastEyeData.RightGazePoint2D:
        # ",lastEyeData.RightGazePoint2D.__dict__
        if eye_data_event.LeftValidity >= 2 and eye_data_event.RightValidity >= 2:
            # no eye signal
            eyes[LEFT]['status'] = 'Missing'
            eyes[RIGHT]['status'] = 'Missing'

        elif eye_data_event.LeftValidity < 2 and eye_data_event.RightValidity >= 2:
            # left eye only available
            eyes[LEFT]['status'] = 'Available'
            eyes[RIGHT]['status'] = 'Missing'

            eyes[LEFT]['pupil_diameter_mm'] = eye_data_event.LeftPupil
            eyes[LEFT]['gaze_norm'][0] = eye_data_event.LeftGazePoint2D.x
            eyes[LEFT]['gaze_norm'][1] = eye_data_event.LeftGazePoint2D.y
            eyes[LEFT]['gaze_mm'][0] = eye_data_event.LeftGazePoint3D.x
            eyes[LEFT]['gaze_mm'][1] = eye_data_event.LeftGazePoint3D.y
            eyes[LEFT]['gaze_mm'][2] = eye_data_event.LeftGazePoint3D.z
            eyes[LEFT]['eye_location_norm'][
                0] = eye_data_event.LeftEyePosition3DRelative.x
            eyes[LEFT]['eye_location_norm'][
                1] = eye_data_event.LeftEyePosition3DRelative.y
            eyes[LEFT]['eye_location_norm'][
                2] = eye_data_event.LeftEyePosition3DRelative.z
            eyes[LEFT]['eye_location_mm'][
                0] = eye_data_event.LeftEyePosition3D.x
            eyes[LEFT]['eye_location_mm'][
                1] = eye_data_event.LeftEyePosition3D.y
            eyes[LEFT]['eye_location_mm'][
                2] = eye_data_event.LeftEyePosition3D.z

        elif eye_data_event.LeftValidity >= 2 and eye_data_event.RightValidity < 2:
            # right eye only available
            eyes[RIGHT]['status'] = 'Available'
            eyes[LEFT]['status'] = 'Missing'

            eyes[RIGHT]['pupil_diameter_mm'] = eye_data_event.RightPupil
            eyes[RIGHT]['gaze_norm'][0] = eye_data_event.RightGazePoint2D.x
            eyes[RIGHT]['gaze_norm'][1] = eye_data_event.RightGazePoint2D.y
            eyes[RIGHT]['gaze_mm'][0] = eye_data_event.RightGazePoint3D.x
            eyes[RIGHT]['gaze_mm'][1] = eye_data_event.RightGazePoint3D.y
            eyes[RIGHT]['gaze_mm'][2] = eye_data_event.RightGazePoint3D.z
            eyes[RIGHT]['eye_location_norm'][
                0] = eye_data_event.RightEyePosition3DRelative.x
            eyes[RIGHT]['eye_location_norm'][
                1] = eye_data_event.RightEyePosition3DRelative.y
            eyes[RIGHT]['eye_location_norm'][
                2] = eye_data_event.RightEyePosition3DRelative.z
            eyes[RIGHT]['eye_location_mm'][
                0] = eye_data_event.RightEyePosition3D.x
            eyes[RIGHT]['eye_location_mm'][
                1] = eye_data_event.RightEyePosition3D.y
            eyes[RIGHT]['eye_location_mm'][
                2] = eye_data_event.RightEyePosition3D.z
        else:
            # binocular available
            eyes[RIGHT]['status'] = 'Available'
            eyes[LEFT]['status'] = 'Available'

            eyes[LEFT]['pupil_diameter_mm'] = eye_data_event.LeftPupil
            eyes[LEFT]['gaze_norm'][0] = eye_data_event.LeftGazePoint2D.x
            eyes[LEFT]['gaze_norm'][1] = eye_data_event.LeftGazePoint2D.y
            eyes[LEFT]['gaze_mm'][0] = eye_data_event.LeftGazePoint3D.x
            eyes[LEFT]['gaze_mm'][1] = eye_data_event.LeftGazePoint3D.y
            eyes[LEFT]['gaze_mm'][2] = eye_data_event.LeftGazePoint3D.z
            eyes[LEFT]['eye_location_norm'][
                0] = eye_data_event.LeftEyePosition3DRelative.x
            eyes[LEFT]['eye_location_norm'][
                1] = eye_data_event.LeftEyePosition3DRelative.y
            eyes[LEFT]['eye_location_norm'][
                2] = eye_data_event.LeftEyePosition3DRelative.z
            eyes[LEFT]['eye_location_mm'][
                0] = eye_data_event.LeftEyePosition3D.x
            eyes[LEFT]['eye_location_mm'][
                1] = eye_data_event.LeftEyePosition3D.y
            eyes[LEFT]['eye_location_mm'][
                2] = eye_data_event.LeftEyePosition3D.z

            eyes[RIGHT]['pupil_diameter_mm'] = eye_data_event.RightPupil
            eyes[RIGHT]['gaze_norm'][0] = eye_data_event.RightGazePoint2D.x
            eyes[RIGHT]['gaze_norm'][1] = eye_data_event.RightGazePoint2D.y
            eyes[RIGHT]['gaze_mm'][0] = eye_data_event.RightGazePoint3D.x
            eyes[RIGHT]['gaze_mm'][1] = eye_data_event.RightGazePoint3D.y
            eyes[RIGHT]['gaze_mm'][2] = eye_data_event.RightGazePoint3D.z
            eyes[RIGHT]['eye_location_norm'][
                0] = eye_data_event.RightEyePosition3DRelative.x
            eyes[RIGHT]['eye_location_norm'][
                1] = eye_data_event.RightEyePosition3DRelative.y
            eyes[RIGHT]['eye_location_norm'][
                2] = eye_data_event.RightEyePosition3DRelative.z
            eyes[RIGHT]['eye_location_mm'][
                0] = eye_data_event.RightEyePosition3D.x
            eyes[RIGHT]['eye_location_mm'][
                1] = eye_data_event.RightEyePosition3D.y
            eyes[RIGHT]['eye_location_mm'][
                2] = eye_data_event.RightEyePosition3D.z

        return False

    def on_start_tracking(self, *args, **kwargs):
        return False

    def on_stop_tracking(self, *args, **kwargs):
        return False

    def on_external_framerate_change(self, *args, **kwargs):
        print2err('NOTE: Tobii System Sampling Rate Changed.')
        return False

    def on_head_box_change(self, *args, **kwargs):
        print2err('NOTE: Tobii Head Movement Box Changed.')
        return False

    def on_x_series_physical_config_change(self, *args, **kwargs):
        print2err('NOTE: Tobii X Series Physical Settings Changed.')
        return False

    def getTimeSyncManager(self):
        return self._sync_manager

    def getTimeSyncState(self):
        return self._sync_manager.sync_state()

    def getCurrentEyeTrackerTime(self):
        return self._sync_manager.convert_from_local_to_remote(
            self._getTobiiClockTime())

    def getCurrentLocalTobiiTime(self):
        return self._getTobiiClockTime()

    def getTobiiTimeResolution(self):
        return self._getTobiiClockResolution()

    def getMainLoop(self):
        return self._mainloop

    def getTrackerDetails(self):
        tobiiInfoProperties = [
            'product_id', 'given_name', 'model', 'generation',
            'firmware_version', 'status'
        ]
        tprops = OrderedDict()
        eyetracker_info = self._eyetracker_info
        for tp in tobiiInfoProperties:
            ta = getattr(eyetracker_info, tp)
            if callable(ta):
                ta = ta()
            tprops[tp] = ta
        tprops['factory_info_string'] = str(eyetracker_info.factory_info)
        return tprops

    def getTrackerInfo(self):
        if hasattr(self._eyetracker, 'GetUnitInfo'):
            return self._eyetracker.GetUnitInfo()
        return None

    def startTracking(self, et_data_rx_callback=None):
        if et_data_rx_callback:
            self.on_eyetracker_data = et_data_rx_callback
        self._eyetracker.events.OnGazeDataReceived += self.on_eyetracker_data
        self._eyetracker.StartTracking(self.on_start_tracking)
        self._isRecording = True
        return True

    def stopTracking(self):
        self._eyetracker.events.OnGazeDataReceived -= self.on_eyetracker_data
        self._eyetracker.StopTracking(self.on_stop_tracking)
        self._isRecording = False

    def getName(self):
        return self._eyetracker.GetUnitName()

    def setName(self, name):
        self._eyetracker.SetUnitName(name)

    def getLowBlinkMode(self):
        try:
            return self._eyetracker.GetLowblinkMode()
        except Exception:
            pass
        return None

    def setLowBlinkMode(self, enable):
        if hasattr(self._eyetracker, 'SetLowblinkMode'):
            try:
                if isinstance(enable, bool) or enable == 0 or enable == 1:
                    self._eyetracker.SetLowblinkMode(enable)
                return self._eyetracker.GetLowblinkMode()
            except Exception:
                pass
        return None

    def setSamplingRate(self, rate):
        if rate in self._eyetracker.EnumerateFramerates():
            self._eyetracker.SetFramerate(rate)
            return rate
        return self._eyetracker.GetFramerate()

    def getAvailableSamplingRates(self):
        return self._eyetracker.EnumerateFramerates()

    def getSamplingRate(self):
        return self._eyetracker.GetFramerate()

    def getIlluminationMode(self):
        try:
            return self._eyetracker.getIlluminationMode()
        except Exception:
            pass
        return None

    def getAvailableIlluminationModes(self):
        try:
            return self._eyetracker.EnumerateIlluminationModes()
        except Exception:
            return []

    def setIlluminationMode(self, imode):
        imodes = self.getAvailableIlluminationModes()
        if len(imodes) > 0:
            if imode in imodes:
                self._eyetracker.SetIlluminationMode(imode)
                return imode
            return self._eyetracker.getIlluminationMode()
        else:
            print 'WARNING: setIlluminationMode is not supported by either your Tobii model, or the version of the Tobii SDK being used.'

    def getHeadBox(self):
        hb = None
        if hasattr(self._eyetracker, 'GetTrackBox'):
            hb = self._eyetracker.GetTrackBox()
        elif hasattr(self._eyetracker, 'GetHeadMovementBox'):
            hb = self._eyetracker.GetHeadMovementBox()

        if hb:
            return np.asarray([(hb.Point1.x, hb.Point1.y, hb.Point1.z),
                               (hb.Point2.x, hb.Point2.y, hb.Point2.z),
                               (hb.Point3.x, hb.Point3.y, hb.Point3.z),
                               (hb.Point4.x, hb.Point4.y, hb.Point4.z),
                               (hb.Point5.x, hb.Point5.y, hb.Point5.z),
                               (hb.Point6.x, hb.Point6.y, hb.Point6.z),
                               (hb.Point7.x, hb.Point7.y, hb.Point7.z),
                               (hb.Point8.x, hb.Point8.y, hb.Point8.z)])
        return None

    def setXSeriesPhysicalPlacement(self, upperLeft, upperRight, lowerLeft):
        if self.getTrackerDetails()['generation'] == 'X':
            self._eyetracker.SetXConfiguration(upperLeft, upperRight,
                                               lowerLeft)
            return True
        return False

    def getEyeTrackerPhysicalPlacement(self):
        etpc = self._eyetracker.GetXConfiguration()
        ll = etpc.LowerLeft
        ul = etpc.UpperLeft
        ur = etpc.UpperRight
        return dict(lowerLeft=(ll.x, ll.y, ll.z),
                    upperLeft=(ul.x, ul.y, ul.z),
                    upperRight=(ur.x, ur.y, ur.z))

    def getAvailableExtensions(self):
        return [
            OrderedDict(name=e.Name,
                        extensionId=e.ExtensionId,
                        protocolVersion=e.ProtocolVersion,
                        realm=e.Realm)
            for e in self._eyetracker.GetAvailableExtensions()
        ]

    def getEnabledExtensions(self):
        return [
            OrderedDict(name=e.Name,
                        extensionId=e.ExtensionId,
                        protocolVersion=e.ProtocolVersion,
                        realm=e.Realm)
            for e in self._eyetracker.GetEnabledExtensions()
        ]

    def enableExtension(self, extension):
        extension_id = extension
        if isinstance(extension, OrderedDict):
            extension_id = extension['extensionId']
        self._eyetracker.EnableExtension(extension_id)

    def disconnect(self):
        if self._isRecording:
            self.stopTracking()
        self._mainloop.stop()
        self._mainloop = None
        self._eyetracker_info = None
        self._requested_product_id = None
        self._requested_model = None
        self._eyetracker = None

    def __del__(self):
        if self._mainloop:
            self.disconnect()