Exemple #1
0
	def __init__(self, display):

		"""Initiates an eyetracker dummy object, that simulates gaze position using the mouse
		
		arguments
		display		--	a pygaze display.Display instance
		
		keyword arguments
		None
		"""

		# try to copy docstrings (but ignore it if it fails, as we do
		# not need it for actual functioning of the code)
		try:
			copy_docstr(BaseEyeTracker, Dummy)
		except:
			# we're not even going to show a warning, since the copied
			# docstring is useful for code editors; these load the docs
			# in a non-verbose manner, so warning messages would be lost
			pass

		self.recording = False
		self.blinking = False
		self.bbpos = (settings.DISPSIZE[0]/2, settings.DISPSIZE[1]/2)
		self.resolution = settings.DISPSIZE[:]
		self.simulator = Mouse(disptype=settings.DISPTYPE, mousebuttonlist=None,
			timeout=2, visible=False)
		self.kb = Keyboard(disptype=settings.DISPTYPE, keylist=None,
			timeout=None)
		self.angrybeep = Sound(osc='saw',freq=100, length=100, attack=0,
			decay=0, soundfile=None)
		self.display = display
		self.screen = Screen(disptype=settings.DISPTYPE, mousevisible=False)
	def __init__(self, display):

		"""Initiates an eyetracker dummy object, that simulates gaze position using the mouse
		
		arguments
		display		--	a pygaze display.Display instance
		
		keyword arguments
		None
		"""

		# try to copy docstrings (but ignore it if it fails, as we do
		# not need it for actual functioning of the code)
		try:
			copy_docstr(BaseEyeTracker, Dummy)
		except:
			# we're not even going to show a warning, since the copied
			# docstring is useful for code editors; these load the docs
			# in a non-verbose manner, so warning messages would be lost
			pass

		self.recording = False
		self.blinking = False
		self.bbpos = (settings.DISPSIZE[0]/2, settings.DISPSIZE[1]/2)
		self.resolution = settings.DISPSIZE[:]
		self.simulator = Mouse(disptype=settings.DISPTYPE, mousebuttonlist=None,
			timeout=2, visible=False)
		self.kb = Keyboard(disptype=settings.DISPTYPE, keylist=None,
			timeout=None)
		self.angrybeep = Sound(osc='saw',freq=100, length=100, attack=0,
			decay=0, soundfile=None)
		self.display = display
		self.screen = Screen(disptype=settings.DISPTYPE, mousevisible=False)
Exemple #3
0
class Dummy(DumbDummy):
    """A dummy class to run experiments in dummy mode, where eye movements are simulated by the mouse"""
    def __init__(self, display):
        """Initiates an eyetracker dummy object, that simulates gaze position using the mouse
        
        arguments
        display        --    a pygaze display.Display instance
        
        keyword arguments
        None
        """

        # try to copy docstrings (but ignore it if it fails, as we do
        # not need it for actual functioning of the code)
        try:
            copy_docstr(BaseEyeTracker, Dummy)
        except:
            # we're not even going to show a warning, since the copied
            # docstring is useful for code editors; these load the docs
            # in a non-verbose manner, so warning messages would be lost
            pass

        self.recording = False
        self.blinking = False
        self.bbpos = (settings.DISPSIZE[0] / 2, settings.DISPSIZE[1] / 2)
        self.resolution = settings.DISPSIZE[:]
        self.simulator = Mouse(disptype=settings.DISPTYPE,
                               mousebuttonlist=None,
                               timeout=2,
                               visible=False)
        self.kb = Keyboard(disptype=settings.DISPTYPE,
                           keylist=None,
                           timeout=None)
        self.angrybeep = Sound(osc='saw',
                               freq=100,
                               length=100,
                               attack=0,
                               decay=0,
                               soundfile=None)
        self.display = display
        self.screen = Screen(disptype=settings.DISPTYPE, mousevisible=False)

    def calibrate(self):
        """Dummy calibration"""

        print("Calibration would now take place")
        clock.pause(1000)

    def drift_correction(self, pos=None, fix_triggered=False):
        """Dummy drift correction"""

        print("Drift correction would now take place")

        if fix_triggered:
            return self.fix_triggered_drift_correction(pos)

        if pos == None:
            pos = settings.DISPSIZE[0] / 2, settings.DISPSIZE[1] / 2

        # show mouse
        self.simulator.set_visible(visible=True)

        # show fixation dot
        self.draw_drift_correction_target(pos[0], pos[1])

        # perform drift check
        errdist = 60  # pixels (on a 1024x768px and 39.9x29.9cm monitor at 67 cm, this is about 2 degrees of visual angle)
        pressed = None
        while True:
            # check for keyboard input
            pressed, presstime = self.kb.get_key(
                keylist=['q', 'escape', 'space'], timeout=1)

            # quit key
            if pressed in ['q', 'escape']:
                # hide mouse
                self.simulator.set_visible(visible=False)
                return False

            # space bar
            elif pressed == 'space':
                # get sample
                gazepos = self.sample()
                # sample is close enough to fixation dot
                if ((gazepos[0] - pos[0])**2 +
                    (gazepos[1] - pos[1])**2)**0.5 < errdist:
                    # hide mouse
                    self.simulator.set_visible(visible=False)
                    return True
                # sample is NOT close enough to fixation dot
                else:
                    # show discontent
                    self.angrybeep.play()

    def fix_triggered_drift_correction(self,
                                       pos=None,
                                       min_samples=30,
                                       max_dev=60,
                                       reset_threshold=10):
        """Dummy drift correction (fixation triggered)"""

        print("Drift correction (fixation triggered) would now take place")

        if pos == None:
            pos = settings.DISPSIZE[0] / 2, settings.DISPSIZE[1] / 2

        # show mouse
        self.simulator.set_visible(visible=True)

        # show fixation dot
        self.draw_drift_correction_target(pos[0], pos[1])

        while True:
            # loop until we have sufficient samples
            lx = []
            ly = []
            while len(lx) < min_samples:

                # pressing escape enters the calibration screen
                if self.kb.get_key(keylist=["escape", "q"],
                                   timeout=0)[0] != None:
                    self.recording = False
                    print(
                        "libeyetracker.libeyetracker.fix_triggered_drift_correction(): 'q' pressed"
                    )
                    self.simulator.set_visible(visible=False)
                    return False

                # collect a sample
                x, y = self.sample()

                if len(lx) == 0 or x != lx[-1] or y != ly[-1]:

                    # if present sample deviates too much from previous sample, reset counting
                    if len(lx) > 0 and (abs(x - lx[-1]) > reset_threshold
                                        or abs(y - ly[-1]) > reset_threshold):
                        lx = []
                        ly = []

                    # collect samples
                    else:
                        lx.append(x)
                        ly.append(y)

                # check if samples are within max. deviation
                if len(lx) == min_samples:

                    avg_x = sum(lx) / len(lx)
                    avg_y = sum(ly) / len(ly)
                    d = ((avg_x - pos[0])**2 + (avg_y - pos[1])**2)**0.5

                    if d < max_dev:
                        self.simulator.set_visible(visible=False)
                        return True
                    else:
                        lx = []
                        ly = []

    def start_recording(self):
        """Dummy for starting recording, prints what would have been the recording start"""

        self.simulator.set_visible(visible=True)
        dumrectime = clock.get_time()

        self.recording = True

        print("Recording would have started at: " + str(dumrectime))

    def stop_recording(self):
        """Dummy for stopping recording, prints what would have been the recording end"""

        self.simulator.set_visible(visible=False)
        dumrectime = clock.get_time()

        self.recording = False

        print("Recording would have stopped at: " + str(dumrectime))

    def close(self):
        """Dummy for closing connection with eyetracker, prints what would have been connection closing time"""

        if self.recording:
            self.stop_recording()

        closetime = clock.get_time()

        print("eyetracker connection would have closed at: " + str(closetime))

    def pupil_size(self):
        """Returns dummy pupil size"""

        return 19

    def sample(self):
        """Returns simulated gaze position (=mouse position)"""

        if self.blinking:
            if self.simulator.get_pressed()[2]:  # buttondown
                self.simulator.set_pos(pos=(
                    self.bbpos[0],
                    self.resolution[1]))  # set position to blinking position
            elif not self.simulator.get_pressed()[2]:  # buttonup
                self.simulator.set_pos(
                    pos=self.bbpos)  # set position to position before blinking
                self.blinking = False  # 'blink' stopped

        elif not self.blinking:
            if self.simulator.get_pressed()[2]:  # buttondown
                self.blinking = True  # 'blink' started
                self.bbpos = self.simulator.get_pos(
                )  # position before blinking
                self.simulator.set_pos(pos=(
                    self.bbpos[0],
                    self.resolution[1]))  # set position to blinking position

        return self.simulator.get_pos()

    def wait_for_saccade_start(self):
        """Returns starting time and starting position when a simulated saccade is started"""

        # function assumes that a 'saccade' has been started when a deviation of more than
        # maxerr from the initial 'gaze' position has been detected (using Pythagoras, ofcourse)

        spos = self.sample()  # starting position
        maxerr = 3  # pixels
        while True:
            npos = self.sample()  # get newest sample
            if ((spos[0] - npos[0])**2 +
                (spos[1] - npos[1])**2)**0.5 > maxerr:  # Pythagoras
                break

        return clock.get_time(), spos

    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 wait_for_fixation_end(self):
        """Returns time and gaze position when a simulated fixation is ended"""

        # function assumes that a 'fixation' has ended when a deviation of more than maxerr
        # from the initial 'fixation' position has been detected (using Pythagoras, ofcourse)

        stime, spos = self.wait_for_fixation_start()
        maxerr = 3  # pixels

        while True:
            npos = self.sample()  # get newest sample
            if ((spos[0] - npos[0])**2 +
                (spos[1] - npos[1])**2)**0.5 > maxerr:  # Pythagoras
                break

        return clock.get_time(), spos

    def wait_for_blink_start(self):
        """Returns starting time and position of a simulated blink (mousebuttondown)"""

        # blinks are simulated with mouseclicks: a right mouseclick simulates the closing
        # of the eyes, a mousebuttonup the opening.

        while not self.blinking:
            pos = self.sample()

        return clock.get_time(), pos

    def wait_for_blink_end(self):
        """Returns ending time and position of a simulated blink (mousebuttonup)"""

        # blinks are simulated with mouseclicks: a right mouseclick simulates the closing
        # of the eyes, a mousebuttonup the opening.

        # wait for blink start
        while not self.blinking:
            spos = self.sample()
        # wait for blink end
        while self.blinking:
            epos = self.sample()

        return clock.get_time(), epos

    def set_draw_drift_correction_target_func(self, func):
        """See pygaze._eyetracker.baseeyetracker.BaseEyeTracker"""

        self.draw_drift_correction_target = func

    # ***
    #
    # Internal functions below
    #
    # ***

    def draw_drift_correction_target(self, x, y):
        """
        Draws the drift-correction target.
        
        arguments
        
        x        --    The X coordinate
        y        --    The Y coordinate
        """

        self.screen.clear()
        self.screen.draw_fixation(fixtype='dot', colour=settings.FGC, \
            pos=(x,y), pw=0, diameter=12)
        self.display.fill(self.screen)
        self.display.show()
Exemple #4
0
	def __init__(self, libeyelink, tracker):

		"""
		Constructor.

		Arguments:
		libeyelink	--	A libeyelink object.
		tracker		--	An tracker object as returned by pylink.EyeLink().
		"""

		pylink.EyeLinkCustomDisplay.__init__(self)

		# objects
		self.libeyelink = libeyelink
		self.display = libeyelink.display
		self.screen = Screen(disptype=DISPTYPE, mousevisible=False)
		self.kb = Keyboard(keylist=None, timeout=0)
		self.mouse = Mouse(timeout=0)
		if DISPTYPE == 'pygame':
			self.kb.set_timeout(timeout=0.001)
		# If we are using a DISPTYPE that cannot be used directly, we have to
		# save the camera image to a temporary file on each frame.
		#if DISPTYPE not in ('pygame', 'psychopy'):
		import tempfile
		import os
		self.tmp_file = os.path.join(tempfile.gettempdir(), '__eyelink__.jpg')
		# drawing properties
		self.xc = self.display.dispsize[0]/2
		self.yc = self.display.dispsize[1]/2
		self.extra_info = True
		self.ld = 40 # line distance
		self.fontsize = libeyelink.fontsize
		self.title = ""
		self.display_open = True
		# menu
		self.menuscreen = Screen(disptype=DISPTYPE, mousevisible=False)		
		self.menuscreen.draw_text(text="Eyelink calibration menu",
			pos=(self.xc,self.yc-6*self.ld), center=True, font='mono',
			fontsize=int(2*self.fontsize), antialias=True)
		self.menuscreen.draw_text(text="%s (pygaze %s, pylink %s)" \
			% (libeyelink.eyelink_model, pygaze.version, pylink.__version__),
			pos=(self.xc,self.yc-5*self.ld), center=True,
			font='mono', fontsize=int(.8*self.fontsize), antialias=True)
		self.menuscreen.draw_text(text="Press C to calibrate", 
			pos=(self.xc, self.yc-3*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press V to validate",
			pos=(self.xc, self.yc-2*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press A to auto-threshold",
			pos=(self.xc,self.yc-1*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press I to toggle extra info in camera image",
			pos=(self.xc,self.yc-0*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press Enter to show camera image",
			pos=(self.xc,self.yc+1*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(
			text="(then change between images using the arrow keys)",
			pos=(self.xc, self.yc+2*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press Escape to abort experiment",
			pos=(self.xc, self.yc+4*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)			
		self.menuscreen.draw_text(text="Press Q to exit menu",
			pos=(self.xc, self.yc+5*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		# beeps
		self.__target_beep__ = Sound(osc='sine', freq=440, length=50, 
			attack=0, decay=0, soundfile=None)
		self.__target_beep__done__ = Sound(osc='sine', freq=880, length=200,
			attack=0, decay=0, soundfile=None)
		self.__target_beep__error__ = Sound(osc='sine', freq=220, length=200,
			attack=0, decay=0, soundfile=None)
		# Colors
		self.color = {
			pylink.CR_HAIR_COLOR:			pygame.Color('white'),
			pylink.PUPIL_HAIR_COLOR:		pygame.Color('white'),
			pylink.PUPIL_BOX_COLOR:			pygame.Color('green'),
			pylink.SEARCH_LIMIT_BOX_COLOR:	pygame.Color('red'),
			pylink.MOUSE_CURSOR_COLOR:		pygame.Color('red'),	
			'font':							pygame.Color('white'),		
			}
		# Font
		pygame.font.init()
		self.font = pygame.font.SysFont('Courier New', 11)
		# further properties
		self.state = None
		self.pal = None
		
		self.size = (0,0)
		self.set_tracker(tracker)
		self.last_mouse_state = -1
		self.bit64 = '64bit' in platform.architecture()
		self.imagebuffer = self.new_array()		
Exemple #5
0
class EyelinkGraphics(custom_display):

	"""
	Implements the EyeLink graphics that are shown on the experimental PC, such
	as the camera image, and the calibration dots. This class only implements
	the drawing operations, and little to no of the logic behind the set-up,
	which is implemented in PyLink.
	"""

	def __init__(self, libeyelink, tracker):

		"""
		Constructor.

		Arguments:
		libeyelink	--	A libeyelink object.
		tracker		--	An tracker object as returned by pylink.EyeLink().
		"""

		pylink.EyeLinkCustomDisplay.__init__(self)

		# objects
		self.libeyelink = libeyelink
		self.display = libeyelink.display
		self.screen = Screen(disptype=DISPTYPE, mousevisible=False)
		self.kb = Keyboard(keylist=None, timeout=0)
		self.mouse = Mouse(timeout=0)
		if DISPTYPE == 'pygame':
			self.kb.set_timeout(timeout=0.001)
		# If we are using a DISPTYPE that cannot be used directly, we have to
		# save the camera image to a temporary file on each frame.
		#if DISPTYPE not in ('pygame', 'psychopy'):
		import tempfile
		import os
		self.tmp_file = os.path.join(tempfile.gettempdir(), '__eyelink__.jpg')
		# drawing properties
		self.xc = self.display.dispsize[0]/2
		self.yc = self.display.dispsize[1]/2
		self.extra_info = True
		self.ld = 40 # line distance
		self.fontsize = libeyelink.fontsize
		self.title = ""
		self.display_open = True
		# menu
		self.menuscreen = Screen(disptype=DISPTYPE, mousevisible=False)		
		self.menuscreen.draw_text(text="Eyelink calibration menu",
			pos=(self.xc,self.yc-6*self.ld), center=True, font='mono',
			fontsize=int(2*self.fontsize), antialias=True)
		self.menuscreen.draw_text(text="%s (pygaze %s, pylink %s)" \
			% (libeyelink.eyelink_model, pygaze.version, pylink.__version__),
			pos=(self.xc,self.yc-5*self.ld), center=True,
			font='mono', fontsize=int(.8*self.fontsize), antialias=True)
		self.menuscreen.draw_text(text="Press C to calibrate", 
			pos=(self.xc, self.yc-3*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press V to validate",
			pos=(self.xc, self.yc-2*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press A to auto-threshold",
			pos=(self.xc,self.yc-1*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press I to toggle extra info in camera image",
			pos=(self.xc,self.yc-0*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press Enter to show camera image",
			pos=(self.xc,self.yc+1*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(
			text="(then change between images using the arrow keys)",
			pos=(self.xc, self.yc+2*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		self.menuscreen.draw_text(text="Press Escape to abort experiment",
			pos=(self.xc, self.yc+4*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)			
		self.menuscreen.draw_text(text="Press Q to exit menu",
			pos=(self.xc, self.yc+5*self.ld), center=True, font='mono',
			fontsize=self.fontsize, antialias=True)
		# beeps
		self.__target_beep__ = Sound(osc='sine', freq=440, length=50, 
			attack=0, decay=0, soundfile=None)
		self.__target_beep__done__ = Sound(osc='sine', freq=880, length=200,
			attack=0, decay=0, soundfile=None)
		self.__target_beep__error__ = Sound(osc='sine', freq=220, length=200,
			attack=0, decay=0, soundfile=None)
		# Colors
		self.color = {
			pylink.CR_HAIR_COLOR:			pygame.Color('white'),
			pylink.PUPIL_HAIR_COLOR:		pygame.Color('white'),
			pylink.PUPIL_BOX_COLOR:			pygame.Color('green'),
			pylink.SEARCH_LIMIT_BOX_COLOR:	pygame.Color('red'),
			pylink.MOUSE_CURSOR_COLOR:		pygame.Color('red'),	
			'font':							pygame.Color('white'),		
			}
		# Font
		pygame.font.init()
		self.font = pygame.font.SysFont('Courier New', 11)
		# further properties
		self.state = None
		self.pal = None
		
		self.size = (0,0)
		self.set_tracker(tracker)
		self.last_mouse_state = -1
		self.bit64 = '64bit' in platform.architecture()
		self.imagebuffer = self.new_array()		
		
	def close(self):
	
		"""
		Is called when the connection and display are shutting down.		
		"""
		
		self.display_open = False
		
	def new_array(self):
	
		"""
		Creates a new array with a system-specific format.
		
		Returns:
		An array.
		"""
		
		# On 64 bit Linux, we need to use an unsigned int data format.
		# <https://www.sr-support.com/showthread.php?3215-Visual-glitch-when-/
		# sending-eye-image-to-display-PC&highlight=ubuntu+pylink>
		if os.name == 'posix' and self.bit64:
			return array.array('I')
		return array.array('L')

	def set_tracker(self, tracker):

		"""
		Connects the tracker to the graphics environment.

		Arguments:
		tracker		--	An tracker object as returned by pylink.EyeLink().
		"""

		self.tracker = tracker
		self.tracker_version = tracker.getTrackerVersion()
		if self.tracker_version >= 3:
			self.tracker.sendCommand("enable_search_limits=YES")
			self.tracker.sendCommand("track_search_limits=YES")
			self.tracker.sendCommand("autothreshold_click=YES")
			self.tracker.sendCommand("autothreshold_repeat=YES")
			self.tracker.sendCommand("enable_camera_position_detect=YES")

	def setup_cal_display(self):

		"""
		Sets up the initial calibration display, which contains a menu with
		instructions.
		"""
		
		# show instructions
		self.display.fill(self.menuscreen)
		self.display.show()

	def exit_cal_display(self):

		"""Exits calibration display."""

		self.clear_cal_display()

	def record_abort_hide(self):

		"""TODO: What does this do?"""

		pass

	def clear_cal_display(self):

		"""Clears the calibration display"""

		self.display.fill()
		self.display.show()

	def erase_cal_target(self):

		"""TODO: What does this do?"""

		self.clear_cal_display()

	def draw_cal_target(self, x, y):

		"""
		Draws calibration target.

		Arguments:
		x		--	The X coordinate of the target.
		y		--	The Y coordinate of the target.
		"""

		self.play_beep(pylink.CAL_TARG_BEEP)
		self.screen.clear()		
		self.screen.draw_fixation(fixtype='dot', pos=(x,y))
		self.display.fill(screen=self.screen)
		self.display.show()

	def play_beep(self, beepid):

		"""
		Plays a sound.

		Arguments:
		beepid		--	A number that identifies the sound.
		"""

		if beepid == pylink.CAL_TARG_BEEP:
			# For some reason, playing the beep here doesn't work, so we have
			# to play it when the calibration target is drawn.
			if EYELINKCALBEEP:
				self.__target_beep__.play()			
		elif beepid == pylink.CAL_ERR_BEEP or beepid == pylink.DC_ERR_BEEP:
			# show a picture
			self.screen.clear()
			self.screen.draw_text(
				text="calibration lost, press 'Enter' to return to menu",
				pos=(self.xc,self.yc), center=True, font='mono',
				fontsize=self.fontsize, antialias=True)
			self.display.fill(self.screen)
			self.display.show()
			# play beep
			self.__target_beep__error__.play()
		elif beepid == pylink.CAL_GOOD_BEEP:
			self.screen.clear()
			if self.state == "calibration":
				self.screen.draw_text(
					text="Calibration succesfull, press 'v' to validate",
					pos=(self.xc,self.yc), center=True, font='mono',
					fontsize=self.fontsize, antialias=True)
			elif self.state == "validation":
				self.screen.draw_text(
					text="Validation succesfull, press 'Enter' to return to menu",
					pos=(self.xc,self.yc), center=True, font='mono',
					fontsize=self.fontsize, antialias=True)				
			else:
				self.screen.draw_text(text="Press 'Enter' to return to menu",
					pos=(self.xc,self.yc), center=True, font='mono',
					fontsize=self.fontsize, antialias=True)
			# show screen
			self.display.fill(self.screen)
			self.display.show()
			# play beep
			self.__target_beep__done__.play()
		else: #	DC_GOOD_BEEP	or DC_TARG_BEEP
			pass

	def draw_line(self, x1, y1, x2, y2, colorindex):

		"""
		Unlike the function name suggests, this draws a single pixel. I.e.
		the end coordinates are always exactly one pixel away from the start
		coordinates.
		
		Arguments:
		x1			--	The starting x.
		y1			--	The starting y.
		x2			--	The end x.
		y2			--	The end y.
		colorIndex	--	A color index.
		"""

		x1 = int(self.scale*x1)
		y1 = int(self.scale*y1)
		x2 = int(self.scale*x2)
		y2 = int(self.scale*y2)			
		pygame.draw.line(self.cam_img, self.color[colorindex], (x1, y1),
			(x2, y2))
		
	def draw_lozenge(self, x, y, w, h, colorindex):

		"""
		desc:
			Draws a rectangle.
			
		arguments:
			x:
				desc:	X coordinate.
				type:	int
			y:
				desc:	Y coordinate.
				type:	int
			w:
				desc:	A width.
				type:	int
			h:
				desc:	A height.
				type:	int
			colorindex:
				desc:	A colorindex.
				type:	int
		"""

		x = int(self.scale*x)
		y = int(self.scale*y)
		w = int(self.scale*w)
		h = int(self.scale*h)		
		pygame.draw.rect(self.cam_img, self.color[colorindex], (x, y, w, h), 2)
		
	def draw_title(self):
	
		"""
		desc:
			Draws title info.
		"""
	
		y = 0
		for line in self.title:
			surf = self.font.render(line, 0, self.color['font'])
			self.cam_img.blit(surf, (1, y))
			y += 12

	def get_mouse_state(self):

		"""
		desc:
			Gets the mouse position and state.
			
		returns:
			desc:	A (pos, state) tuple.
			type:	tuple.		
		"""
		
		button, pos, time = self.mouse.get_clicked()
		if button == None:
			button = -1
		if pos == None:
			pos = self.mouse.get_pos()
		return pos, button

	def get_input_key(self):

		"""
		Gets an input key.

		Returns:
		A list containing a single pylink key identifier.
		"""

		# Don't try to collect key presses when the display is no longer
		# available. This is necessary, because pylink polls key presses during
		# file transfer, which generally occurs after the display has been
		# closed.
		if not self.display_open:
			return None
		try:
			key, time = self.kb.get_key(keylist=None, timeout='default')
		except:
			self.esc_pressed = True
			key = 'q'
		if key == None:
			return None
		# Escape functions as a 'q' with the additional esc_pressed flag
		if key == 'escape':
			key = 'q'
			self.esc_pressed = True
		# Process regular keys
		if key == "return":
			keycode = pylink.ENTER_KEY
			self.state = None
		elif key == "space":
			keycode = ord(" ")
		elif key == "q":
			keycode = pylink.ESC_KEY
			self.state = None
		elif key == "c":
			keycode = ord("c")
			self.state = "calibration"
		elif key == "v":
			keycode = ord("v")
			self.state = "validation"
		elif key == "a":
			keycode = ord("a")
		elif key == "i":
			self.extra_info = not self.extra_info
			keycode = 0
		elif key == "up":
			keycode = pylink.CURS_UP
		elif key == "down":
			keycode = pylink.CURS_DOWN
		elif key == "left":
			keycode = pylink.CURS_LEFT
		elif key == "right":
			keycode = pylink.CURS_RIGHT
		else:
			keycode = 0
		# Convert key to PyLink keycode and return
		return [pylink.KeyInput(keycode, 0)] # 0 = pygame.KMOD_NONE

	def exit_image_display(self):

		"""Exits the image display."""

		self.clear_cal_display()

	def alert_printf(self,msg):

		"""
		Prints alert message.

		Arguments:
		msg		--	The message to be played.
		"""

		print "eyelink_graphics.alert_printf(): %s" % msg

	def setup_image_display(self, width, height):

		"""
		Initializes the buffer that will contain the camera image.

		Arguments:
		width		--	The width of the image.
		height		--	The height of the image.
		"""

		self.size = width, height
		self.clear_cal_display()
		self.last_mouse_state = -1
		self.imagebuffer = self.new_array()

	def image_title(self, text):

		"""
		Sets the current image title.

		Arguments:
		text	--	An image title.
		"""

		while ': ' in text:
			text = text.replace(': ', ':')
		self.title = text.split()
		
	def draw_image_line(self, width, line, totlines, buff):

		"""
		Draws a single eye video frame, line by line.

		Arguments:

		width		--	Width of the video.
		line		--	Line nr of current line.
		totlines	--	Total lines in video.
		buff		--	Frame buffer.
		imagesize	--	The size of the image, which is (usually?) 192x160 px.
		"""

		# If the buffer hasn't been filled yet, add a line.
		for i in range(width):
			try:
				self.imagebuffer.append(self.pal[buff[i]])
			except:
				pass
		# If the buffer is full, push it to the display.
		if line == totlines:
			self.scale = totlines/320.
			self._size = int(self.scale*self.size[0]), int(
				self.scale*self.size[1])
			# Convert the image buffer to a pygame image, save it ...			
			self.cam_img = pygame.image.fromstring(self.imagebuffer.tostring(),
				self._size, 'RGBX')
			if self.extra_info:
				self.draw_cross_hair()				
				self.draw_title()
			pygame.image.save(self.cam_img, self.tmp_file)
			# ... and then show the image.
			self.screen.clear()
			self.screen.draw_image(self.tmp_file, scale=1.5/self.scale)
			self.display.fill(self.screen)
			self.display.show()			
			# Clear the buffer for the next round!
			self.imagebuffer = self.new_array()

	def set_image_palette(self, r, g, b):

		"""
		Sets the image palette.

		TODO: What this function actually does is highly mysterious. Figure it
		out!

		Arguments:
		r		--	The red channel.
		g		--	The green channel.
		b		--	The blue channel.
		"""

		self.imagebuffer = self.new_array()
		self.clear_cal_display()
		sz = len(r)
		i = 0
		self.pal = []
		while i < sz:
			rf = int(b[i])
			gf = int(g[i])
			bf = int(r[i])
			self.pal.append((rf<<16) | (gf<<8) | (bf))
			i += 1
    def __init__(self, libeyelink, tracker):

        """
		Constructor.

		Arguments:
		libeyelink	--	A libeyelink object.
		tracker		--	An tracker object as returned by pylink.EyeLink().
		"""

        pylink.EyeLinkCustomDisplay.__init__(self)

        # objects
        self.libeyelink = libeyelink
        self.display = libeyelink.display
        self.screen = Screen(disptype=DISPTYPE, mousevisible=False)
        self.kb = Keyboard(keylist=None, timeout=0)
        self.mouse = Mouse(timeout=0)
        if DISPTYPE == "pygame":
            self.kb.set_timeout(timeout=0.001)
            # If we are using a DISPTYPE that cannot be used directly, we have to
            # save the camera image to a temporary file on each frame.
            # if DISPTYPE not in ('pygame', 'psychopy'):
        import tempfile
        import os

        self.tmp_file = os.path.join(tempfile.gettempdir(), "__eyelink__.jpg")
        # drawing properties
        self.xc = self.display.dispsize[0] / 2
        self.yc = self.display.dispsize[1] / 2
        self.extra_info = True
        self.ld = 40  # line distance
        self.fontsize = libeyelink.fontsize
        self.title = ""
        self.display_open = True
        # menu
        self.menuscreen = Screen(disptype=DISPTYPE, mousevisible=False)
        self.menuscreen.draw_text(
            text="Eyelink calibration menu",
            pos=(self.xc, self.yc - 6 * self.ld),
            center=True,
            font="mono",
            fontsize=int(2 * self.fontsize),
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="%s (pygaze %s, pylink %s)" % (libeyelink.eyelink_model, pygaze.version, pylink.__version__),
            pos=(self.xc, self.yc - 5 * self.ld),
            center=True,
            font="mono",
            fontsize=int(0.8 * self.fontsize),
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press C to calibrate",
            pos=(self.xc, self.yc - 3 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press V to validate",
            pos=(self.xc, self.yc - 2 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press A to auto-threshold",
            pos=(self.xc, self.yc - 1 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press I to toggle extra info in camera image",
            pos=(self.xc, self.yc - 0 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press Enter to show camera image",
            pos=(self.xc, self.yc + 1 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="(then change between images using the arrow keys)",
            pos=(self.xc, self.yc + 2 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press Escape to abort experiment",
            pos=(self.xc, self.yc + 4 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press Q to exit menu",
            pos=(self.xc, self.yc + 5 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        # beeps
        self.__target_beep__ = Sound(osc="sine", freq=440, length=50, attack=0, decay=0, soundfile=None)
        self.__target_beep__done__ = Sound(osc="sine", freq=880, length=200, attack=0, decay=0, soundfile=None)
        self.__target_beep__error__ = Sound(osc="sine", freq=220, length=200, attack=0, decay=0, soundfile=None)
        # Colors
        self.color = {
            pylink.CR_HAIR_COLOR: pygame.Color("white"),
            pylink.PUPIL_HAIR_COLOR: pygame.Color("white"),
            pylink.PUPIL_BOX_COLOR: pygame.Color("green"),
            pylink.SEARCH_LIMIT_BOX_COLOR: pygame.Color("red"),
            pylink.MOUSE_CURSOR_COLOR: pygame.Color("red"),
            "font": pygame.Color("white"),
        }
        # Font
        pygame.font.init()
        self.font = pygame.font.SysFont("Courier New", 11)
        # further properties
        self.state = None
        self.pal = None

        self.size = (0, 0)
        self.set_tracker(tracker)
        self.last_mouse_state = -1
        self.bit64 = "64bit" in platform.architecture()
        self.imagebuffer = self.new_array()
class EyelinkGraphics(custom_display):

    """
	Implements the EyeLink graphics that are shown on the experimental PC, such
	as the camera image, and the calibration dots. This class only implements
	the drawing operations, and little to no of the logic behind the set-up,
	which is implemented in PyLink.
	"""

    def __init__(self, libeyelink, tracker):

        """
		Constructor.

		Arguments:
		libeyelink	--	A libeyelink object.
		tracker		--	An tracker object as returned by pylink.EyeLink().
		"""

        pylink.EyeLinkCustomDisplay.__init__(self)

        # objects
        self.libeyelink = libeyelink
        self.display = libeyelink.display
        self.screen = Screen(disptype=DISPTYPE, mousevisible=False)
        self.kb = Keyboard(keylist=None, timeout=0)
        self.mouse = Mouse(timeout=0)
        if DISPTYPE == "pygame":
            self.kb.set_timeout(timeout=0.001)
            # If we are using a DISPTYPE that cannot be used directly, we have to
            # save the camera image to a temporary file on each frame.
            # if DISPTYPE not in ('pygame', 'psychopy'):
        import tempfile
        import os

        self.tmp_file = os.path.join(tempfile.gettempdir(), "__eyelink__.jpg")
        # drawing properties
        self.xc = self.display.dispsize[0] / 2
        self.yc = self.display.dispsize[1] / 2
        self.extra_info = True
        self.ld = 40  # line distance
        self.fontsize = libeyelink.fontsize
        self.title = ""
        self.display_open = True
        # menu
        self.menuscreen = Screen(disptype=DISPTYPE, mousevisible=False)
        self.menuscreen.draw_text(
            text="Eyelink calibration menu",
            pos=(self.xc, self.yc - 6 * self.ld),
            center=True,
            font="mono",
            fontsize=int(2 * self.fontsize),
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="%s (pygaze %s, pylink %s)" % (libeyelink.eyelink_model, pygaze.version, pylink.__version__),
            pos=(self.xc, self.yc - 5 * self.ld),
            center=True,
            font="mono",
            fontsize=int(0.8 * self.fontsize),
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press C to calibrate",
            pos=(self.xc, self.yc - 3 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press V to validate",
            pos=(self.xc, self.yc - 2 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press A to auto-threshold",
            pos=(self.xc, self.yc - 1 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press I to toggle extra info in camera image",
            pos=(self.xc, self.yc - 0 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press Enter to show camera image",
            pos=(self.xc, self.yc + 1 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="(then change between images using the arrow keys)",
            pos=(self.xc, self.yc + 2 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press Escape to abort experiment",
            pos=(self.xc, self.yc + 4 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        self.menuscreen.draw_text(
            text="Press Q to exit menu",
            pos=(self.xc, self.yc + 5 * self.ld),
            center=True,
            font="mono",
            fontsize=self.fontsize,
            antialias=True,
        )
        # beeps
        self.__target_beep__ = Sound(osc="sine", freq=440, length=50, attack=0, decay=0, soundfile=None)
        self.__target_beep__done__ = Sound(osc="sine", freq=880, length=200, attack=0, decay=0, soundfile=None)
        self.__target_beep__error__ = Sound(osc="sine", freq=220, length=200, attack=0, decay=0, soundfile=None)
        # Colors
        self.color = {
            pylink.CR_HAIR_COLOR: pygame.Color("white"),
            pylink.PUPIL_HAIR_COLOR: pygame.Color("white"),
            pylink.PUPIL_BOX_COLOR: pygame.Color("green"),
            pylink.SEARCH_LIMIT_BOX_COLOR: pygame.Color("red"),
            pylink.MOUSE_CURSOR_COLOR: pygame.Color("red"),
            "font": pygame.Color("white"),
        }
        # Font
        pygame.font.init()
        self.font = pygame.font.SysFont("Courier New", 11)
        # further properties
        self.state = None
        self.pal = None

        self.size = (0, 0)
        self.set_tracker(tracker)
        self.last_mouse_state = -1
        self.bit64 = "64bit" in platform.architecture()
        self.imagebuffer = self.new_array()

    def close(self):

        """
		Is called when the connection and display are shutting down.		
		"""

        self.display_open = False

    def new_array(self):

        """
		Creates a new array with a system-specific format.
		
		Returns:
		An array.
		"""

        # On 64 bit Linux, we need to use an unsigned int data format.
        # <https://www.sr-support.com/showthread.php?3215-Visual-glitch-when-/
        # sending-eye-image-to-display-PC&highlight=ubuntu+pylink>
        if os.name == "posix" and self.bit64:
            return array.array("I")
        return array.array("L")

    def set_tracker(self, tracker):

        """
		Connects the tracker to the graphics environment.

		Arguments:
		tracker		--	An tracker object as returned by pylink.EyeLink().
		"""

        self.tracker = tracker
        self.tracker_version = tracker.getTrackerVersion()
        if self.tracker_version >= 3:
            self.tracker.sendCommand("enable_search_limits=YES")
            self.tracker.sendCommand("track_search_limits=YES")
            self.tracker.sendCommand("autothreshold_click=YES")
            self.tracker.sendCommand("autothreshold_repeat=YES")
            self.tracker.sendCommand("enable_camera_position_detect=YES")

    def setup_cal_display(self):

        """
		Sets up the initial calibration display, which contains a menu with
		instructions.
		"""

        # show instructions
        self.display.fill(self.menuscreen)
        self.display.show()

    def exit_cal_display(self):

        """Exits calibration display."""

        self.clear_cal_display()

    def record_abort_hide(self):

        """TODO: What does this do?"""

        pass

    def clear_cal_display(self):

        """Clears the calibration display"""

        self.display.fill()
        self.display.show()

    def erase_cal_target(self):

        """TODO: What does this do?"""

        self.clear_cal_display()

    def draw_cal_target(self, x, y):

        """
		Draws calibration target.

		Arguments:
		x		--	The X coordinate of the target.
		y		--	The Y coordinate of the target.
		"""

        self.play_beep(pylink.CAL_TARG_BEEP)
        self.screen.clear()
        self.screen.draw_fixation(fixtype="dot", pos=(x, y))
        self.display.fill(screen=self.screen)
        self.display.show()

    def play_beep(self, beepid):

        """
		Plays a sound.

		Arguments:
		beepid		--	A number that identifies the sound.
		"""

        if beepid == pylink.CAL_TARG_BEEP:
            # For some reason, playing the beep here doesn't work, so we have
            # to play it when the calibration target is drawn.
            if EYELINKCALBEEP:
                self.__target_beep__.play()
        elif beepid == pylink.CAL_ERR_BEEP or beepid == pylink.DC_ERR_BEEP:
            # show a picture
            self.screen.clear()
            self.screen.draw_text(
                text="calibration lost, press 'Enter' to return to menu",
                pos=(self.xc, self.yc),
                center=True,
                font="mono",
                fontsize=self.fontsize,
                antialias=True,
            )
            self.display.fill(self.screen)
            self.display.show()
            # play beep
            self.__target_beep__error__.play()
        elif beepid == pylink.CAL_GOOD_BEEP:
            self.screen.clear()
            if self.state == "calibration":
                self.screen.draw_text(
                    text="Calibration succesfull, press 'v' to validate",
                    pos=(self.xc, self.yc),
                    center=True,
                    font="mono",
                    fontsize=self.fontsize,
                    antialias=True,
                )
            elif self.state == "validation":
                self.screen.draw_text(
                    text="Validation succesfull, press 'Enter' to return to menu",
                    pos=(self.xc, self.yc),
                    center=True,
                    font="mono",
                    fontsize=self.fontsize,
                    antialias=True,
                )
            else:
                self.screen.draw_text(
                    text="Press 'Enter' to return to menu",
                    pos=(self.xc, self.yc),
                    center=True,
                    font="mono",
                    fontsize=self.fontsize,
                    antialias=True,
                )
                # show screen
            self.display.fill(self.screen)
            self.display.show()
            # play beep
            self.__target_beep__done__.play()
        else:  # 	DC_GOOD_BEEP	or DC_TARG_BEEP
            pass

    def draw_line(self, x1, y1, x2, y2, colorindex):

        """
		Unlike the function name suggests, this draws a single pixel. I.e.
		the end coordinates are always exactly one pixel away from the start
		coordinates.
		
		Arguments:
		x1			--	The starting x.
		y1			--	The starting y.
		x2			--	The end x.
		y2			--	The end y.
		colorIndex	--	A color index.
		"""

        x1 = int(self.scale * x1)
        y1 = int(self.scale * y1)
        x2 = int(self.scale * x2)
        y2 = int(self.scale * y2)
        pygame.draw.line(self.cam_img, self.color[colorindex], (x1, y1), (x2, y2))

    def draw_lozenge(self, x, y, w, h, colorindex):

        """
		desc:
			Draws a rectangle.
			
		arguments:
			x:
				desc:	X coordinate.
				type:	int
			y:
				desc:	Y coordinate.
				type:	int
			w:
				desc:	A width.
				type:	int
			h:
				desc:	A height.
				type:	int
			colorindex:
				desc:	A colorindex.
				type:	int
		"""

        x = int(self.scale * x)
        y = int(self.scale * y)
        w = int(self.scale * w)
        h = int(self.scale * h)
        pygame.draw.rect(self.cam_img, self.color[colorindex], (x, y, w, h), 2)

    def draw_title(self):

        """
		desc:
			Draws title info.
		"""

        y = 0
        for line in self.title:
            surf = self.font.render(line, 0, self.color["font"])
            self.cam_img.blit(surf, (1, y))
            y += 12

    def get_mouse_state(self):

        """
		desc:
			Gets the mouse position and state.
			
		returns:
			desc:	A (pos, state) tuple.
			type:	tuple.		
		"""

        button, pos, time = self.mouse.get_clicked()
        if button == None:
            button = -1
        if pos == None:
            pos = self.mouse.get_pos()
        return pos, button

    def get_input_key(self):

        """
		Gets an input key.

		Returns:
		A list containing a single pylink key identifier.
		"""

        # Don't try to collect key presses when the display is no longer
        # available. This is necessary, because pylink polls key presses during
        # file transfer, which generally occurs after the display has been
        # closed.
        if not self.display_open:
            return None
        try:
            key, time = self.kb.get_key(keylist=None, timeout="default")
        except:
            self.esc_pressed = True
            key = "q"
        if key == None:
            return None
            # Escape functions as a 'q' with the additional esc_pressed flag
        if key == "escape":
            key = "q"
            self.esc_pressed = True
            # Process regular keys
        if key == "return":
            keycode = pylink.ENTER_KEY
            self.state = None
        elif key == "space":
            keycode = ord(" ")
        elif key == "q":
            keycode = pylink.ESC_KEY
            self.state = None
        elif key == "c":
            keycode = ord("c")
            self.state = "calibration"
        elif key == "v":
            keycode = ord("v")
            self.state = "validation"
        elif key == "a":
            keycode = ord("a")
        elif key == "i":
            self.extra_info = not self.extra_info
            keycode = 0
        elif key == "up":
            keycode = pylink.CURS_UP
        elif key == "down":
            keycode = pylink.CURS_DOWN
        elif key == "left":
            keycode = pylink.CURS_LEFT
        elif key == "right":
            keycode = pylink.CURS_RIGHT
        else:
            keycode = 0
            # Convert key to PyLink keycode and return
        return [pylink.KeyInput(keycode, 0)]  # 0 = pygame.KMOD_NONE

    def exit_image_display(self):

        """Exits the image display."""

        self.clear_cal_display()

    def alert_printf(self, msg):

        """
		Prints alert message.

		Arguments:
		msg		--	The message to be played.
		"""

        print "eyelink_graphics.alert_printf(): %s" % msg

    def setup_image_display(self, width, height):

        """
		Initializes the buffer that will contain the camera image.

		Arguments:
		width		--	The width of the image.
		height		--	The height of the image.
		"""

        self.size = width, height
        self.clear_cal_display()
        self.last_mouse_state = -1
        self.imagebuffer = self.new_array()

    def image_title(self, text):

        """
		Sets the current image title.

		Arguments:
		text	--	An image title.
		"""

        while ": " in text:
            text = text.replace(": ", ":")
        self.title = text.split()

    def draw_image_line(self, width, line, totlines, buff):

        """
		Draws a single eye video frame, line by line.

		Arguments:

		width		--	Width of the video.
		line		--	Line nr of current line.
		totlines	--	Total lines in video.
		buff		--	Frame buffer.
		imagesize	--	The size of the image, which is (usually?) 192x160 px.
		"""

        # If the buffer hasn't been filled yet, add a line.
        for i in range(width):
            try:
                self.imagebuffer.append(self.pal[buff[i]])
            except:
                pass
                # If the buffer is full, push it to the display.
        if line == totlines:
            self.scale = totlines / 320.0
            self._size = int(self.scale * self.size[0]), int(self.scale * self.size[1])
            # Convert the image buffer to a pygame image, save it ...
            self.cam_img = pygame.image.fromstring(self.imagebuffer.tostring(), self._size, "RGBX")
            if self.extra_info:
                self.draw_cross_hair()
                self.draw_title()
            pygame.image.save(self.cam_img, self.tmp_file)
            # ... and then show the image.
            self.screen.clear()
            self.screen.draw_image(self.tmp_file, scale=1.5 / self.scale)
            self.display.fill(self.screen)
            self.display.show()
            # Clear the buffer for the next round!
            self.imagebuffer = self.new_array()

    def set_image_palette(self, r, g, b):

        """
		Sets the image palette.

		TODO: What this function actually does is highly mysterious. Figure it
		out!

		Arguments:
		r		--	The red channel.
		g		--	The green channel.
		b		--	The blue channel.
		"""

        self.imagebuffer = self.new_array()
        self.clear_cal_display()
        sz = len(r)
        i = 0
        self.pal = []
        while i < sz:
            rf = int(b[i])
            gf = int(g[i])
            bf = int(r[i])
            self.pal.append((rf << 16) | (gf << 8) | (bf))
            i += 1
    def __init__(self, libeyelink, tracker):

        """
		Constructor.

		Arguments:
		libeyelink	--	A libeyelink object.
		tracker		--	An tracker object as returned by pylink.EyeLink().
		"""

        pylink.EyeLinkCustomDisplay.__init__(self)

        # objects
        self.libeyelink = libeyelink
        self.display = libeyelink.display
        self.screen = Screen(disptype=settings.DISPTYPE, mousevisible=False)
        self.kb = Keyboard(keylist=None, timeout=0)
        self.mouse = Mouse(timeout=0)
        if settings.DISPTYPE == "pygame":
            self.kb.set_timeout(timeout=0.001)
            # If we are using a DISPTYPE that cannot be used directly, we have to
            # save the camera image to a temporary file on each frame.
            # if DISPTYPE not in ('pygame', 'psychopy'):
        import tempfile
        import os

        self.tmp_file = os.path.join(tempfile.gettempdir(), "__eyelink__.jpg")
        # drawing properties
        self.xc = self.display.dispsize[0] / 2
        self.yc = self.display.dispsize[1] / 2
        self.extra_info = True
        self.ld = 40  # line distance
        self.fontsize = libeyelink.fontsize
        self.title = ""
        self.display_open = True
        self.draw_menu_screen()
        # beeps
        self.__target_beep__ = Sound(osc="sine", freq=440, length=50, attack=0, decay=0, soundfile=None)
        self.__target_beep__done__ = Sound(osc="sine", freq=880, length=200, attack=0, decay=0, soundfile=None)
        self.__target_beep__error__ = Sound(osc="sine", freq=220, length=200, attack=0, decay=0, soundfile=None)
        # Colors
        self.color = {
            pylink.CR_HAIR_COLOR: pygame.Color("white"),
            pylink.PUPIL_HAIR_COLOR: pygame.Color("white"),
            pylink.PUPIL_BOX_COLOR: pygame.Color("green"),
            pylink.SEARCH_LIMIT_BOX_COLOR: pygame.Color("red"),
            pylink.MOUSE_CURSOR_COLOR: pygame.Color("red"),
            "font": pygame.Color("white"),
        }
        # Font
        pygame.font.init()
        self.font = pygame.font.SysFont("Courier New", 11)
        # further properties
        self.state = None
        self.pal = None

        self.size = (0, 0)
        self.set_tracker(tracker)
        self.last_mouse_state = -1
        self.bit64 = "64bit" in platform.architecture()
        self.imagebuffer = self.new_array()
# PYGAZE
# Initialise a new Display instance
disp = Display()

# Initialise a Screen instance for arbitrary drawing.
scr = Screen()
scr.draw_text(text="Initialising the experiment...", fontsize=FONTSIZE)
disp.fill(scr)
disp.show()

# Initialise the ka-ching sound.
kaching = Sound(soundfile=KACHING)

# Initialise a Keyboard and a Mouse instance for participant interaction.
kb = Keyboard()
mouse = Mouse()

# COMMUNICATIONS
timer.pause(5000)
_print("Initialising Client.")
# Initialise a new Client instance.
client = Client(multicast_ip)

# Establish a connection with the server.
scr.clear()
scr.draw_text(text="Connecting to the server...", fontsize=FONTSIZE)
disp.fill(scr)
disp.show()
_print("Connecting to the Server.")
success = client.contact_server(timeout=CONTIMEOUT)
Exemple #10
0
    def __init__(self, libeyelink, tracker):
        """
		Constructor.

		Arguments:
		libeyelink	--	A libeyelink object.
		tracker		--	An tracker object as returned by pylink.EyeLink().
		"""

        pylink.EyeLinkCustomDisplay.__init__(self)

        # objects
        self.libeyelink = libeyelink
        self.display = libeyelink.display
        self.screen = Screen(disptype=settings.DISPTYPE, mousevisible=False)
        self.kb = Keyboard(keylist=None, timeout=0)
        self.mouse = Mouse(timeout=0)
        if settings.DISPTYPE == 'pygame':
            self.kb.set_timeout(timeout=0.001)
        # If we are using a DISPTYPE that cannot be used directly, we have to
        # save the camera image to a temporary file on each frame.
        #if DISPTYPE not in ('pygame', 'psychopy'):
        import tempfile
        import os
        self.tmp_file = os.path.join(tempfile.gettempdir(), '__eyelink__.jpg')
        # drawing properties
        self.xc = self.display.dispsize[0] / 2
        self.yc = self.display.dispsize[1] / 2
        self.extra_info = True
        self.ld = 40  # line distance
        self.fontsize = libeyelink.fontsize
        self.title = ""
        self.display_open = True
        self.draw_menu_screen()
        # beeps
        self.__target_beep__ = Sound(osc='sine',
                                     freq=440,
                                     length=50,
                                     attack=0,
                                     decay=0,
                                     soundfile=None)
        self.__target_beep__done__ = Sound(osc='sine',
                                           freq=880,
                                           length=200,
                                           attack=0,
                                           decay=0,
                                           soundfile=None)
        self.__target_beep__error__ = Sound(osc='sine',
                                            freq=220,
                                            length=200,
                                            attack=0,
                                            decay=0,
                                            soundfile=None)
        # Colors
        self.color = {
            pylink.CR_HAIR_COLOR: pygame.Color('white'),
            pylink.PUPIL_HAIR_COLOR: pygame.Color('white'),
            pylink.PUPIL_BOX_COLOR: pygame.Color('green'),
            pylink.SEARCH_LIMIT_BOX_COLOR: pygame.Color('red'),
            pylink.MOUSE_CURSOR_COLOR: pygame.Color('red'),
            'font': pygame.Color('white'),
        }
        # Font
        pygame.font.init()
        self.font = pygame.font.SysFont('Courier New', 11)
        # further properties
        self.state = None
        self.pal = None

        self.size = (0, 0)
        self.set_tracker(tracker)
        self.last_mouse_state = -1
        self.bit64 = '64bit' in platform.architecture()
        self.imagebuffer = self.new_array()
Exemple #11
0

# Open a new log file.
log = Logfile(filename = LOGFILE)
log_det = Logfile(filename = DETAILED_LOGFILE)
log_events = Logfile(filename = EVENT_LOGFILE)
# TODO: Write header.
log.write(["trialnr","left_ang","right_ang", "cue_dir", "targ", "targ_ang", "resp_ang", "perc_diff", "resp_onset", "resp_duration", "iti", "iti_onset", "stim_onset","delay_onset", "cue_onset", "postcue_onset","probe_onset", "prac"])
log_det.write(["trialnr", "timestamp", "angle", "event", "targ_ang", "cue_dir"])
log_events.write(["Trigger", "Timestamp"])
# Initialise the eye tracker.
tracker = EyeTracker(disp)

# Create a new Keyboard instance to process key presses.
kb = Keyboard(keylist=None, timeout=5000)
mouse = Mouse()
mouse.set_visible(visible=False)

# intitliase the MEG interface NI box 
if MEG:
    trigbox = MEGTriggerBox()

# initialise a function 


# trigbox.set_trigger_state(1) <= Example usage 

# btn_list, state = trigbox.get_button_state() <= example usage, needs to be constantly updated during task 

# (0) <= might have to do this
yax = DISPSIZE[1]

cy = yax / 2
cx = xax / 2

### Set size of game matrix relative to screen with 'margin'.
margin = 0.7
side = yax * margin
t_side = side / p1
tloc = t_side / p1

owncol = (255, 255, 255)
othercol = (255, 255, 255)

disp = Display(dispsize=DISPSIZE, screennr=2)
mse = Mouse(visible=False, timeout=10)

gamescreen = Screen(dispsize=DISPSIZE)

#tracker = EyeTracker(disp)

fixscr = Screen(dispsize=DISPSIZE)
fixscr.draw_fixation(fixtype='cross', diameter=8)

introscreen = Screen(dispsize=DISPSIZE)
introscreen.draw_text("Welcome to the experiment", fontsize=30)
introscreen.draw_text("\n\n\n (To proceed, press any key)", fontsize=25)

istrscreen = Screen(dispsize=DISPSIZE)
istrscreen.draw_text(
    "In this experiment, you will \n play a sequence of games \n like the one displayed on the right",
Exemple #13
0
class Dummy(DumbDummy):

	"""A dummy class to run experiments in dummy mode, where eye movements are simulated by the mouse"""
	

	def __init__(self, display):

		"""Initiates an eyetracker dummy object, that simulates gaze position using the mouse
		
		arguments
		display		--	a pygaze display.Display instance
		
		keyword arguments
		None
		"""

		# try to copy docstrings (but ignore it if it fails, as we do
		# not need it for actual functioning of the code)
		try:
			copy_docstr(BaseEyeTracker, Dummy)
		except:
			# we're not even going to show a warning, since the copied
			# docstring is useful for code editors; these load the docs
			# in a non-verbose manner, so warning messages would be lost
			pass

		self.recording = False
		self.blinking = False
		self.bbpos = (settings.DISPSIZE[0]/2, settings.DISPSIZE[1]/2)
		self.resolution = settings.DISPSIZE[:]
		self.simulator = Mouse(disptype=settings.DISPTYPE, mousebuttonlist=None,
			timeout=2, visible=False)
		self.kb = Keyboard(disptype=settings.DISPTYPE, keylist=None,
			timeout=None)
		self.angrybeep = Sound(osc='saw',freq=100, length=100, attack=0,
			decay=0, soundfile=None)
		self.display = display
		self.screen = Screen(disptype=settings.DISPTYPE, mousevisible=False)

	def calibrate(self):

		"""Dummy calibration"""

		print("Calibration would now take place")
		clock.pause(1000)

	def drift_correction(self, pos=None, fix_triggered=False):

		"""Dummy drift correction"""

		print("Drift correction would now take place")
		
		if fix_triggered:
			return self.fix_triggered_drift_correction(pos)
		
		if pos == None:
			pos = settings.DISPSIZE[0] / 2, settings.DISPSIZE[1] / 2

		# show mouse
		self.simulator.set_visible(visible=True)
		
		# show fixation dot
		self.draw_drift_correction_target(pos[0], pos[1])

		# perform drift check
		errdist = 60 # pixels (on a 1024x768px and 39.9x29.9cm monitor at 67 cm, this is about 2 degrees of visual angle)
		pressed = None
		while True:
			# check for keyboard input
			pressed, presstime = self.kb.get_key(keylist=['q','escape','space'], timeout=1)
			
			# quit key
			if pressed in ['q','escape']:
				# hide mouse
				self.simulator.set_visible(visible=False)
				return False
				
			# space bar
			elif pressed == 'space':
				# get sample
				gazepos = self.sample()
				# sample is close enough to fixation dot
				if ((gazepos[0]-pos[0])**2  + (gazepos[1]-pos[1])**2)**0.5 < errdist:
					# hide mouse
					self.simulator.set_visible(visible=False)
					return True
				# sample is NOT close enough to fixation dot
				else:
					# show discontent
					self.angrybeep.play()


	def fix_triggered_drift_correction(self, pos=None, min_samples=30, max_dev=60, reset_threshold=10):

		"""Dummy drift correction (fixation triggered)"""

		print("Drift correction (fixation triggered) would now take place")

		if pos == None:
			pos = settings.DISPSIZE[0] / 2, settings.DISPSIZE[1] / 2

		# show mouse
		self.simulator.set_visible(visible=True)

		# show fixation dot
		self.draw_drift_correction_target(pos[0], pos[1])

		while True:
			# loop until we have sufficient samples
			lx = []
			ly = []
			while len(lx) < min_samples:
	
				# pressing escape enters the calibration screen
				if self.kb.get_key(keylist=["escape", "q"], timeout=0)[0] != None:
					self.recording = False
					print("libeyetracker.libeyetracker.fix_triggered_drift_correction(): 'q' pressed")
					self.simulator.set_visible(visible=False)
					return False
	
				# collect a sample
				x, y = self.sample()
	
				if len(lx) == 0 or x != lx[-1] or y != ly[-1]:
	
					# if present sample deviates too much from previous sample, reset counting
					if len(lx) > 0 and (abs(x - lx[-1]) > reset_threshold or abs(y - ly[-1]) > reset_threshold):
						lx = []
						ly = []
	
					# collect samples
					else:
						lx.append(x)
						ly.append(y)

				# check if samples are within max. deviation
				if len(lx) == min_samples:
	
					avg_x = sum(lx) / len(lx)
					avg_y = sum(ly) / len(ly)
					d = ((avg_x - pos[0]) ** 2 + (avg_y - pos[1]) ** 2)**0.5
	
					if d < max_dev:
						self.simulator.set_visible(visible=False)
						return True
					else:
						lx = []
						ly = []
						

	def start_recording(self):

		"""Dummy for starting recording, prints what would have been the recording start"""

		self.simulator.set_visible(visible=True)
		dumrectime = clock.get_time()

		self.recording = True
		
		print("Recording would have started at: " + str(dumrectime))


	def stop_recording(self):

		"""Dummy for stopping recording, prints what would have been the recording end"""

		self.simulator.set_visible(visible=False)
		dumrectime = clock.get_time()

		self.recording = False

		print("Recording would have stopped at: " + str(dumrectime))


	def close(self):

		"""Dummy for closing connection with eyetracker, prints what would have been connection closing time"""

		if self.recording:
			self.stop_recording()
		
		closetime = clock.get_time()

		print("eyetracker connection would have closed at: " + str(closetime))

	def pupil_size(self):
		
		"""Returns dummy pupil size"""
		
		return 19


	def sample(self):

		"""Returns simulated gaze position (=mouse position)"""

		if self.blinking:
			if self.simulator.get_pressed()[2]: # buttondown
				self.simulator.set_pos(pos=(self.bbpos[0],self.resolution[1])) # set position to blinking position
			elif not self.simulator.get_pressed()[2]: # buttonup
				self.simulator.set_pos(pos=self.bbpos) # set position to position before blinking
				self.blinking = False # 'blink' stopped

		elif not self.blinking:
			if self.simulator.get_pressed()[2]: # buttondown
				self.blinking = True # 'blink' started
				self.bbpos =  self.simulator.get_pos() # position before blinking
				self.simulator.set_pos(pos=(self.bbpos[0],self.resolution[1])) # set position to blinking position

		return self.simulator.get_pos()

	def wait_for_saccade_start(self):

		"""Returns starting time and starting position when a simulated saccade is started"""

		# function assumes that a 'saccade' has been started when a deviation of more than
		# maxerr from the initial 'gaze' position has been detected (using Pythagoras, ofcourse)

		spos = self.sample() # starting position
		maxerr = 3 # pixels
		while True:
			npos = self.sample() # get newest sample
			if ((spos[0]-npos[0])**2  + (spos[1]-npos[1])**2)**0.5 > maxerr: # Pythagoras
				break

		return clock.get_time(), spos


	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 wait_for_fixation_end(self):

		"""Returns time and gaze position when a simulated fixation is ended"""

		# function assumes that a 'fixation' has ended when a deviation of more than maxerr
		# from the initial 'fixation' position has been detected (using Pythagoras, ofcourse)

		stime, spos = self.wait_for_fixation_start()
		maxerr = 3 # pixels
		
		while True:
			npos = self.sample() # get newest sample
			if ((spos[0]-npos[0])**2  + (spos[1]-npos[1])**2)**0.5 > maxerr: # Pythagoras
				break

		return clock.get_time(), spos


	def wait_for_blink_start(self):

		"""Returns starting time and position of a simulated blink (mousebuttondown)"""

		# blinks are simulated with mouseclicks: a right mouseclick simulates the closing
		# of the eyes, a mousebuttonup the opening.

		while not self.blinking:
			pos = self.sample()

		return clock.get_time(), pos


	def wait_for_blink_end(self):

		"""Returns ending time and position of a simulated blink (mousebuttonup)"""
		
		# blinks are simulated with mouseclicks: a right mouseclick simulates the closing
		# of the eyes, a mousebuttonup the opening.

		# wait for blink start
		while not self.blinking:
			spos = self.sample()
		# wait for blink end
		while self.blinking:
			epos = self.sample()

		return clock.get_time(), epos
	
	def set_draw_drift_correction_target_func(self, func):
		
		"""See pygaze._eyetracker.baseeyetracker.BaseEyeTracker"""
		
		self.draw_drift_correction_target = func
	
	# ***
	#
	# Internal functions below
	#
	# ***

	def draw_drift_correction_target(self, x, y):
		
		"""
		Draws the drift-correction target.
		
		arguments
		
		x		--	The X coordinate
		y		--	The Y coordinate
		"""
		
		self.screen.clear()
		self.screen.draw_fixation(fixtype='dot', colour=settings.FGC, \
			pos=(x,y), pw=0, diameter=12)
		self.display.fill(self.screen)
		self.display.show()
Exemple #14
0
# Open a new log file.
log = Logfile()
# TODO: Write header.
log.write(["trialnr", "block", "run","stim", "keypress", "go_nogo", "face_onset", "signal_onset","resp_onset", "RT", "accuracy", "respmap", "block_type"])

# Open a new log file to log events.
event_log = Logfile(filename=EVENT_LOG)
event_log.write(["time", "event"])

# Initialise the eye tracker.
tracker = EyeTracker(disp)

# Create a new Keyboard instance to process key presses.
kb = Keyboard(keylist=None, timeout=5000)
mouse = Mouse()

# intitliase the MEG interface NI box 
if MRI:
	trigbox = MRITriggerBox()

###################
# GENERATE TRIALS #
###################
""" 
A run is a split of experiment, so each run should be matched by randomised 
We have 8 blocks: 4 distractors types x 2 go or no-go conditions 

The trial information is head in the trial object. The structure is as follows:

trial[runNo][blockNo][trialNo][infoNo]
Exemple #15
0

# # # # #
# create instances

# initialize the display
disp = Display()

# initialize a screen
scr = Screen()

# initialize a keyboard
kb = Keyboard(keylist=['space'],timeout=None)

# initialize a mouse
mouse = Mouse(mousebuttonlist=None, timeout=None)

# initialize a sound
snd = Sound(osc='sine', freq=4400, length=3000)
sounds = {
	'a sine wave (slightly oscillating)':Sound(osc='sine', freq=440, length=5000, attack=1000, decay=1000),
	'a saw wave':Sound(osc='saw', freq=880, length=5000, attack=0, decay=0),
	'a square wave':Sound(osc='square', freq=1760, length=5000, attack=0, decay=0),
	'white noise':Sound(osc='whitenoise'),
	'soundfile':Sound(soundfile=soundfile)
	}

# initialize a Timer
timer = Time()

# create a new logfile
import pygaze.libtime as timer

from custom import calc_angular_dist, generate_oris, generate_locs, pol2car, StimScreen

import numpy


# # # # #
# PREPARATION

# Intialise the Display.
disp = Display()

# Initialise the basic input devices.
kb = Keyboard(keylist=None, timeout=None)
mouse = Mouse(mousebuttonlist=None, timeout=None)

# Initialise a log.
log = Logfile()
header = ['trialnr', 'nstim', 'fixonset', 'stimonset', 'maintenanceonset', \
    'probeonset', 'RT', 'response']
for i in range(max(NSTIM)):
    header.extend(['stimx%d' % (i), 'stimy%d' % (i), 'stimori%d' % (i), \
        'stimerror%d' % (i)])
header.extend(['E', 'X', 'T'])
for i in range(max(NSTIM)-1):
    header.append('NT%d' % i)
log.write(header)

# Initialise a blank Screen for ad-hoc drawing.
scr = Screen()
    def __init__(self, libeyelink, tracker):
        """
        Constructor.

        Arguments:
        libeyelink    --    A libeyelink object.
        tracker        --    An tracker object as returned by pylink.EyeLink().
        """

        pylink.EyeLinkCustomDisplay.__init__(self)

        # objects
        self.libeyelink = libeyelink
        self.display = libeyelink.display
        self.screen = Screen(disptype=settings.DISPTYPE, mousevisible=False)
        self.kb = Keyboard(keylist=None, timeout=1)
        self.mouse = Mouse(timeout=1)
        # If we are using a DISPTYPE that cannot be used directly, we have to
        # save the camera image to a temporary file on each frame.
        #if DISPTYPE not in ('pygame', 'psychopy'):
        import tempfile
        import os
        self.tmp_file = os.path.join(tempfile.gettempdir(), "__eyelink__.jpg")
        # drawing properties
        self.xc = self.display.dispsize[0] / 2
        self.yc = self.display.dispsize[1] / 2
        self.extra_info = True
        self.ld = 40  # line distance
        self.fontsize = libeyelink.fontsize
        self.title = ""
        self.display_open = True
        self.draw_menu_screen()
        # A crosshair is drawn onto the eye image. This should be scaled in
        # pylink 1.1.0.5 (tested on Python 2.7) but not on pylink 1.11.0.0
        # (tested on Python 3.6). I'm not sure when this change happened, so
        # it's quite likely we'll have to update the minor version used here.
        pl_version = pylink.__version__.split(".")
        if int(pl_version[0]) > 1 or int(pl_version[1]) >= 11:
            self.scale_lines_in_eye_image = False
        else:
            self.scale_lines_in_eye_image = True
        # Beeps
        self.__target_beep__ = Sound(osc="sine",
                                     freq=440,
                                     length=50,
                                     attack=0,
                                     decay=0,
                                     soundfile=None)
        self.__target_beep__done__ = Sound(osc="sine",
                                           freq=880,
                                           length=200,
                                           attack=0,
                                           decay=0,
                                           soundfile=None)
        self.__target_beep__error__ = Sound(osc="sine",
                                            freq=220,
                                            length=200,
                                            attack=0,
                                            decay=0,
                                            soundfile=None)
        # Colors
        self.color = {
            pylink.CR_HAIR_COLOR: pygame.Color("white"),
            pylink.PUPIL_HAIR_COLOR: pygame.Color("white"),
            pylink.PUPIL_BOX_COLOR: pygame.Color("green"),
            pylink.SEARCH_LIMIT_BOX_COLOR: pygame.Color("red"),
            pylink.MOUSE_CURSOR_COLOR: pygame.Color("red"),
            'font': pygame.Color("white"),
        }
        # Font
        pygame.font.init()
        self.font = pygame.font.SysFont("Courier New", 11)
        # further properties
        self.state = None
        self.pal = None

        self.size = (0, 0)
        self.set_tracker(tracker)
        self.last_mouse_state = -1
        self.bit64 = "64bit" in platform.architecture()
        self.imagebuffer = self.new_array()