Advertisement
Guest User

Untitled

a guest
Feb 7th, 2013
423
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 34.62 KB | None | 0 0
  1. """
  2. This file is part of OpenSesame.
  3.  
  4. OpenSesame is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8.  
  9. OpenSesame is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. GNU General Public License for more details.
  13.  
  14. You should have received a copy of the GNU General Public License
  15. along with OpenSesame.  If not, see <http://www.gnu.org/licenses/>.
  16. """
  17.  
  18. # Don't crash if we fail to load pylink, because we may still use dummy mode.
  19. try:
  20.     import pylink
  21.     custom_display = pylink.EyeLinkCustomDisplay
  22. except:
  23.     custom_display = object
  24.     print "libeyelink: failed to import pylink"
  25.  
  26. import pygame
  27. import openexp.exceptions
  28. from openexp.keyboard import keyboard
  29. from openexp.mouse import mouse
  30. from openexp.canvas import canvas
  31. from openexp.synth import synth
  32. from libopensesame import exceptions
  33. import os.path
  34. import array
  35. import math
  36. import tempfile
  37. try:
  38.     import Image
  39. except:
  40.     from PIL import Image
  41.  
  42. _eyelink = None
  43.  
  44. class libeyelink:
  45.  
  46.     MAX_TRY = 100
  47.  
  48.     def __init__(self, experiment, resolution, data_file="default.edf", fg_color=(255, 255, 255), bg_color=(0, 0, 0), saccade_velocity_threshold=35, saccade_acceleration_threshold=9500, force_drift_correct=False):
  49.  
  50.         """<DOC>
  51.         Constructor. Initializes the connection to the Eyelink
  52.  
  53.         Arguments:
  54.         experiment -- the experiment
  55.         resolution -- (width, height) tuple
  56.  
  57.         Keyword arguments:
  58.         data_file -- the name of the EDF file (default.edf)
  59.         fg_color -- the foreground color for the calibration screen #
  60.                     (default=255,255,255)
  61.         bg_color -- the background color for the calibration screen #
  62.                     (default=0,0,0)
  63.         saccade_velocity_threshold -- velocity threshold used for saccade #
  64.                                       detection (default=35)
  65.         saccade_acceleration_threshold -- acceleration threshold used for #
  66.                                           saccade detection (default=9500)
  67.         force_drift_correct -- indicates whether drift correction should be #
  68.                                enabled. This is useful only for Eyelink 1000 #
  69.                                models, for which drift correction is disabled #
  70.                                by default (default=False)                              
  71.  
  72.         Returns:
  73.         True on connection success and False on connection failure
  74.         </DOC>"""
  75.  
  76.         global _eyelink
  77.         experiment.eyelink_esc_pressed = False
  78.         stem, ext = os.path.splitext(data_file)
  79.         if len(stem) > 8 or len(ext) > 4:
  80.             raise exceptions.runtime_error("The Eyelink cannot handle filenames longer than 8 characters (plus .EDF extension)")
  81.  
  82.         self.experiment = experiment
  83.         self.data_file = data_file
  84.         self.resolution = resolution
  85.         self.recording = False
  86.         self.cal_beep = True
  87.         self.cal_target_size = 16
  88.  
  89.         self.saccade_velocity_treshold = saccade_velocity_threshold
  90.         self.saccade_acceleration_treshold = saccade_acceleration_threshold
  91.         self.eye_used = None
  92.         self.left_eye = 0
  93.         self.right_eye = 1
  94.         self.binocular = 2
  95.        
  96.         # Only initialize the eyelink once
  97.         if _eyelink == None:
  98.             try:
  99.                 _eyelink = pylink.EyeLink()
  100.             except Exception as e:
  101.                 raise exceptions.runtime_error( \
  102.                     "Failed to connect to the tracker: %s" % e)
  103.                    
  104.             graphics_env = eyelink_graphics(self.experiment, _eyelink)
  105.             pylink.openGraphicsEx(graphics_env)            
  106.            
  107.         # Optionally force drift correction. For some reason this must be done
  108.         # as (one of) the first thingsm otherwise a segmentation fault occurs.
  109.         if force_drift_correct:
  110.             self.send_command('driftcorrect_cr_disable = OFF')                                 
  111.            
  112.         pylink.getEYELINK().openDataFile(self.data_file)
  113.         pylink.flushGetkeyQueue()
  114.         pylink.getEYELINK().setOfflineMode()
  115.  
  116.         # Notify the eyelink of the display resolution
  117.         self.send_command("screen_pixel_coords =  0 0 %d %d" % ( \
  118.             self.resolution[0], self.resolution[1]))
  119.  
  120.         # Determine the software version of the tracker
  121.         self.tracker_software_ver = 0
  122.         self.eyelink_ver = pylink.getEYELINK().getTrackerVersion()
  123.         if self.eyelink_ver == 3:
  124.             tvstr = pylink.getEYELINK().getTrackerVersionString()
  125.             vindex = tvstr.find("EYELINK CL")
  126.             self.tracker_software_ver = int(float(tvstr[(vindex + \
  127.                 len("EYELINK CL")):].strip()))
  128.  
  129.         # Some configuration stuff (not sure what the parser and gazemap mean)
  130.         if self.eyelink_ver >= 2:
  131.             self.send_command("select_parser_configuration 0")
  132.             if self.eyelink_ver == 2: #turn off scenelink camera stuff
  133.                 self.send_command("scene_camera_gazemap = NO")
  134.         else:
  135.             self.send_command("saccade_velocity_threshold = %d" % \
  136.                 self.saccade_velocity_threshold)
  137.             self.send_command("saccade_acceleration_threshold = %s" % \
  138.                 self.saccade_acceleration_threshold)
  139.  
  140.         # Set EDF file contents
  141.         self.send_command( \
  142.             "file_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK,MESSAGE,BUTTON")
  143.         if self.tracker_software_ver >= 4:
  144.             self.send_command( \
  145.                 "file_sample_data  = LEFT,RIGHT,GAZE,AREA,GAZERES,STATUS,HTARGET")
  146.         else:
  147.             self.send_command( \
  148.                 "file_sample_data  = LEFT,RIGHT,GAZE,AREA,GAZERES,STATUS")
  149.  
  150.         # Set link data. This specifies which data is sent through the link and
  151.         # thus be used in gaze contingent displays
  152.         self.send_command( \
  153.             "link_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK,BUTTON")
  154.         self.send_command( \
  155.             "link_event_data = GAZE,GAZERES,HREF,AREA,VELOCITY,STATUS")
  156.         if self.tracker_software_ver >= 4:
  157.             self.send_command( \
  158.                 "link_sample_data  = LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS,HTARGET")
  159.         else:
  160.             self.send_command( \
  161.                 "link_sample_data  = LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS")             
  162.  
  163.         # Not sure what this means. Maybe the button that is used to end drift
  164.         # correction?
  165.         self.send_command("button_function 5 'accept_target_fixation'")
  166.  
  167.         # Make sure that we are connected to the eyelink before we start
  168.         # further communication
  169.         if not self.connected():
  170.             raise exceptions.runtime_error( \
  171.                 "Failed to connect to the eyetracker")
  172.  
  173.         # TODO: The code below potentially fixes a bug, but - pending a more
  174.         # thorough understanding - has been disabled to avoid regressions and
  175.         # other problems. Discussions on this issue can be found here:
  176.         # <http://forum.cogsci.nl/index.php?p=/discussion/comment/1161>
  177.         # <https://www.sr-support.com/showthread.php?3208-Event-data-from-the-link-buffer&p=11979>
  178.         #
  179.         # catch pylink bug: pre 1.0.0.28, calling getfloatData() on
  180.         # start_saccade data returns scrambled events so compare current
  181.         # version to up-to-date version
  182.         #cur_v = pylink.version.vernum
  183.         #utd_v = (1, 0, 0, 28)
  184.  
  185.         #utd = True
  186.         #for n in range( len(utd_v) ):
  187.             #if cur_v[n] < utd_v[n]:
  188.                 #utd = False
  189.             #if utd == False or cur_v[n] > utd_v[n]:
  190.                 #break
  191.  
  192.         ## if not  up to date, redefine wait_for_saccade_start
  193.         #if not utd:
  194.             #self.wait_for_saccade_start = self.__wait_for_saccade_start_pre_10028
  195.  
  196.     def send_command(self, cmd):
  197.  
  198.         """<DOC>
  199.         Sends a command to the eyelink
  200.  
  201.         Arguments:
  202.         cmd -- the eyelink command to be executed
  203.         </DOC>"""
  204.  
  205.         pylink.getEYELINK().sendCommand(cmd)
  206.  
  207.     def log(self, msg):
  208.  
  209.         """<DOC>
  210.         Writes a message to the eyelink data file
  211.  
  212.         Arguments:
  213.         msg -- the message to be logged
  214.         </DOC>"""
  215.        
  216.         # sendMessage() is not Unicode safe, so we need to strip all Unicode
  217.         # characters from the message
  218.         if type(msg) == unicode:
  219.             msg = msg.encode('ascii','ignore')
  220.         if type(msg) == str:
  221.             msg = msg.decode('ascii','ignore')
  222.         pylink.getEYELINK().sendMessage(msg)
  223.  
  224.     def log_var(self, var, val):
  225.  
  226.         """<DOC>
  227.         Writes a variable to the eyelink data file. This is a shortcut for
  228.         eyelink.log("var %s %s" % (var, val))
  229.  
  230.         Arguments:
  231.         var -- the variable name
  232.         val -- the value
  233.         </DOC>"""
  234.  
  235.         pylink.getEYELINK().sendMessage("var %s %s" % (var, val))
  236.  
  237.     def status_msg(self, msg):
  238.  
  239.         """<DOC>
  240.         Sets the eyelink status message, which is displayed on the
  241.         eyelink experimenter pc
  242.  
  243.         Arguments:
  244.         msg -- the status message
  245.         </DOC>"""
  246.  
  247.         pylink.getEYELINK().sendCommand("record_status_message '%s'" % msg)
  248.  
  249.     def connected(self):
  250.  
  251.         """<DOC>
  252.         Returs the status of the eyelink connection
  253.  
  254.         Returns:
  255.         True if connected, False otherwise
  256.         </DOC>"""
  257.  
  258.         return pylink.getEYELINK().isConnected()
  259.  
  260.     def calibrate(self, beep=True, target_size=16):
  261.  
  262.         """<DOC>
  263.         Starts eyelink calibration
  264.  
  265.         Keyword arguments:
  266.         beep -- indicates whether the calibration target should beep (default=True)
  267.         target_size -- the size of the calibration target (default=16)
  268.  
  269.         Exceptions:
  270.         Raises an exceptions.runtime_error on failure
  271.         </DOC>"""
  272.  
  273.         if self.recording:
  274.             raise exceptions.runtime_error("Trying to calibrate after recording has started")
  275.  
  276.         self.cal_beep = beep
  277.         self.cal_target_size = target_size
  278.         pylink.getEYELINK().doTrackerSetup()
  279.  
  280.     def get_eyelink_clock_async(self):
  281.  
  282.         """<DOC>
  283.         Retrieve difference between tracker time (as found in tracker timestamps)
  284.         and experiment time.
  285.  
  286.         Returns:
  287.         tracker time minus experiment time
  288.         </DOC>"""
  289.  
  290.         return pylink.getEYELINK().trackerTime() \
  291.                     - self.experiment.time()
  292.  
  293.     def drift_correction(self, pos=None, fix_triggered=False):
  294.  
  295.         """<DOC>
  296.         Performs drift correction and falls back to the calibration screen if
  297.         necessary
  298.  
  299.         Keyword arguments:
  300.         pos -- the coordinate (x,y tuple) of the drift correction dot or None
  301.                for the display center (default = None)
  302.         fix_triggered -- a boolean indicating whether drift correction should
  303.                          be fixation triggered, rather than spacebar triggered
  304.                          (default = False)
  305.  
  306.         Returns:
  307.         True on success, False on failure
  308.  
  309.         Exceptions:
  310.         Raises an exceptions.runtime_error on error
  311.         </DOC>"""
  312.        
  313.         self.experiment.eyelink_esc_pressed = False
  314.         if self.recording:
  315.             raise exceptions.runtime_error("Trying to do drift correction after recording has started")
  316.  
  317.         if fix_triggered:
  318.             return self.fix_triggered_drift_correction(pos)
  319.  
  320.         if pos == None:
  321.             pos = self.resolution[0] / 2, self.resolution[1] / 2
  322.  
  323.         # 'silly loop, it should be left out:'
  324.         # while True:
  325.         if not self.connected():
  326.             raise exceptions.runtime_error("The eyelink is not connected")
  327.         try:
  328.             # Params: x, y, draw fix, allow_setup
  329.             print 'attempting drift correct...'
  330.             error = pylink.getEYELINK().doDriftCorrect(pos[0], pos[1], 0, 0)
  331.             if error != 27:
  332.                 print "libeyelink.drift_correction(): success"
  333.                 return True
  334.             else:
  335.                 # TODO "...escape or q pressed" ?
  336.                 print "libeyelink.drift_correction(): escape pressed"
  337.                 return False
  338.         except Exception, e:
  339.             print "libeyelink.drift_correction(): try again"
  340.             return False
  341.  
  342.     def prepare_drift_correction(self, pos):
  343.  
  344.         """<DOC>
  345.         Puts the tracker in drift correction mode
  346.  
  347.         Arguments:
  348.         pos -- the reference point
  349.  
  350.         Exceptions:
  351.         Raises an exceptions.runtime_error on error
  352.         </DOC>"""
  353.  
  354.         # Start collecting samples in drift correction mode
  355.         self.send_command("heuristic_filter = ON")
  356.         self.send_command("drift_correction_targets = %d %d" % pos)
  357.         self.send_command("start_drift_correction data = 0 0 1 0")
  358.         pylink.msecDelay(50)
  359.  
  360.         # Wait for a bit until samples start coming in (I think?)
  361.         if not pylink.getEYELINK().waitForBlockStart(100, 1, 0):
  362.             raise exceptions.runtime_error("Failed to perform drift correction (waitForBlockStart error)")
  363.  
  364.     def fix_triggered_drift_correction(self, pos=None, min_samples=30, max_dev=60, reset_threshold=10):
  365.  
  366.         """<DOC>
  367.         Performs fixation triggered drift correction and falls back to the
  368.         calibration screen if necessary. You can return to the set-up screen by
  369.         pressing the 'q' key.
  370.  
  371.         Keyword arguments:
  372.         pos -- the coordinate (x,y tuple) of the drift correction dot or None
  373.                for the display center (default = None)
  374.         min_samples -- the minimum nr of stable samples that should be acquired
  375.                        (default = 30)
  376.         max_dev -- the maximum allowed deviation (default = 60)
  377.         reset_threshold -- the maximum allowed deviation from one sample to the
  378.                            next (default = 10)
  379.  
  380.         Returns:
  381.         True on success, False on failure
  382.  
  383.         Exceptions:
  384.         Raises an exceptions.runtime_error on error
  385.         </DOC>"""
  386.  
  387.         if self.recording:
  388.             raise exceptions.runtime_error("Trying to do drift correction after recording has started")
  389.  
  390.         self.recording = True
  391.  
  392.         if pos == None:
  393.             pos = self.resolution[0] / 2, self.resolution[1] / 2
  394.  
  395.         self.prepare_drift_correction(pos)
  396.         my_keyboard = keyboard(self.experiment, keylist=["escape", "q"], timeout=0)
  397.  
  398.         # Loop until we have sufficient samples
  399.         lx = []
  400.         ly = []
  401.         while len(lx) < min_samples:
  402.  
  403.             # Pressing escape enters the calibration screen
  404.             try:
  405.                 key,time = my_keyboard.get_key()
  406.             except:
  407.                 # if escape key pressed:
  408.                 self.experiment.eyelink_esc_pressed = True
  409.                 self.recording = False
  410.                 return False
  411.             else:
  412.                 if key != None: # i.e. 'q' was pressed
  413.                     self.recording = False
  414.                     print "libeyelink.fix_triggered_drift_correction(): 'q' pressed"
  415.                     return False
  416.            
  417.  
  418.             # Collect a sample
  419.             x, y = self.sample()
  420.  
  421.             if len(lx) == 0 or x != lx[-1] or y != ly[-1]:
  422.  
  423.                 # If the current sample deviates too much from the previous one,
  424.                 # reset counting
  425.                 if len(lx) > 0 and (abs(x - lx[-1]) > reset_threshold or abs(y - ly[-1]) > reset_threshold):
  426.  
  427.                     lx = []
  428.                     ly = []
  429.  
  430.                 # Collect samples
  431.                 else:
  432.  
  433.                     lx.append(x)
  434.                     ly.append(y)
  435.  
  436.  
  437.             if len(lx) == min_samples:
  438.  
  439.                 avg_x = sum(lx) / len(lx)
  440.                 avg_y = sum(ly) / len(ly)
  441.                 d = math.sqrt( (avg_x - pos[0]) ** 2 + (avg_y - pos[1]) ** 2)
  442.  
  443.                 # Emulate a spacebar press on success
  444.                 pylink.getEYELINK().sendKeybutton(32, 0, pylink.KB_PRESS)
  445.  
  446.                 # getCalibrationResult() returns 0 on success and an exception
  447.                 # or a non-zero value otherwise
  448.                 result = -1
  449.                 try:
  450.                     result = pylink.getEYELINK().getCalibrationResult()
  451.                 except:
  452.                     lx = []
  453.                     ly = []
  454.                     print "libeyelink.fix_triggered_drift_correction(): try again"
  455.                 if result != 0:
  456.                     lx = []
  457.                     ly = []
  458.                     print "libeyelink.fix_triggered_drift_correction(): try again"
  459.  
  460.  
  461.         # Apply drift correction
  462.         pylink.getEYELINK().applyDriftCorrect()
  463.         self.recording = False
  464.  
  465.         print "libeyelink.fix_triggered_drift_correction(): success"
  466.  
  467.         return True
  468.  
  469.     def start_recording(self):
  470.  
  471.         """<DOC>
  472.         Starts recording of gaze samples
  473.  
  474.         Exceptions:
  475.         Raises an exceptions.runtime_error on failure
  476.         </DOC>"""
  477.  
  478.         self.recording = True
  479.  
  480.         i = 0
  481.         while True:
  482.             # Params: write  samples, write event, send samples, send events
  483.             error = pylink.getEYELINK().startRecording(1, 1, 1, 1)
  484.             if not error:
  485.                 break
  486.             if i > self.MAX_TRY:
  487.                 raise exceptions.runtime_error("Failed to start recording (startRecording error)")
  488.             i += 1
  489.             print "libeyelink.start_recording(): failed to start recording (attempt %d of %d)" \
  490.                 % (i, self.MAX_TRY)
  491.             pylink.msecDelay(100)
  492.  
  493.         # Don't know what this is
  494.         pylink.pylink.beginRealTimeMode(100)
  495.  
  496.         # Wait for a bit until samples start coming in (I think?)
  497.         if not pylink.getEYELINK().waitForBlockStart(100, 1, 0):
  498.             raise exceptions.runtime_error("Failed to start recording (waitForBlockStart error)")
  499.  
  500.     def stop_recording(self):
  501.  
  502.         """<DOC>
  503.         Stop recording of gaze samples
  504.         </DOC>"""
  505.  
  506.         self.recording = False
  507.  
  508.         pylink.endRealTimeMode()
  509.         pylink.getEYELINK().setOfflineMode()
  510.         pylink.msecDelay(500)
  511.  
  512.     def close(self):
  513.  
  514.         """<DOC>
  515.         Close the connection with the eyelink
  516.         </DOC>"""
  517.  
  518.         if self.recording:
  519.             self.stop_recording()
  520.  
  521.         # Close the datafile and transfer it to the experimental pc
  522.         print "libeyelink: closing data file"
  523.         pylink.getEYELINK().closeDataFile()
  524.         pylink.msecDelay(100)
  525.         print "libeyelink: transferring data file"
  526.         pylink.getEYELINK().receiveDataFile(self.data_file, self.data_file)
  527.         pylink.msecDelay(100)
  528.         print "libeyelink: closing eyelink"
  529.         pylink.getEYELINK().close()
  530.         pylink.msecDelay(100)
  531.  
  532.     def set_eye_used(self):
  533.  
  534.         """<DOC>
  535.         Sets the eye_used variable, based on the eyelink's report, which
  536.         specifies which eye is being tracked. If both eyes are being tracked,
  537.         the left eye is used.
  538.  
  539.         Exceptions:
  540.         Raises an exceptions.runtime_error on failure
  541.         <DOC>"""
  542.  
  543.         self.eye_used = pylink.getEYELINK().eyeAvailable()
  544.         if self.eye_used == self.right_eye:
  545.             self.log_var("eye_used", "right")
  546.         elif self.eye_used == self.left_eye or self.eye_used == self.binocular:
  547.             self.log_var("eye_used", "left")
  548.             self.eye_used = self.left_eye
  549.         else:
  550.             raise exceptions.runtime_error("Failed to determine which eye is being recorded")
  551.  
  552.     def sample(self):
  553.  
  554.         """<DOC>
  555.         Gets the most recent gaze sample
  556.  
  557.         Returns:
  558.         A tuple (x, y) containing the coordinates of the sample. The value
  559.         (-1, -1) indicates missing data.
  560.  
  561.         Exceptions:
  562.         Raises an exceptions.runtime_error on failure
  563.         </DOC>"""
  564.  
  565.         if not self.recording:
  566.             raise exceptions.runtime_error( \
  567.                 "Please start recording before collecting eyelink data")
  568.  
  569.         if self.eye_used == None:
  570.             self.set_eye_used()
  571.  
  572.         s = pylink.getEYELINK().getNewestSample()
  573.         if s == None:
  574.             gaze = -1, -1
  575.         elif self.eye_used == self.right_eye and s.isRightSample():
  576.             gaze = s.getRightEye().getGaze()
  577.         elif self.eye_used == self.left_eye and s.isLeftSample():
  578.             gaze = s.getLeftEye().getGaze()
  579.         else:
  580.             gaze = -1, -1
  581.         return gaze
  582.  
  583.     def pupil_size(self):
  584.  
  585.         """<DOC>
  586.         Gets the most recent pupil size
  587.  
  588.         Returns:
  589.         A float corresponding to the pupil size (in arbitrary units). The value
  590.         -1 indicates missing data.
  591.  
  592.         Exceptions:
  593.         Raises an exceptions.runtime_error on failure
  594.         </DOC>"""
  595.  
  596.         if not self.recording:
  597.             raise exceptions.runtime_error( \
  598.                 "Please start recording before collecting eyelink data")
  599.  
  600.         if self.eye_used == None:
  601.             self.set_eye_used()
  602.  
  603.         s = pylink.getEYELINK().getNewestSample()
  604.         if s == None:
  605.             ps = -1
  606.         elif self.eye_used == self.right_eye and s.isRightSample():
  607.             ps = s.getRightEye().getPupilSize()
  608.         elif self.eye_used == self.left_eye and s.isLeftSample():
  609.             ps = s.getLeftEye().getPupilSize()
  610.         else:
  611.             ps = -1
  612.         return ps
  613.  
  614.     def wait_for_event(self, event):
  615.  
  616.         """<DOC>
  617.         Waits until an event has occurred
  618.  
  619.         Arguments:
  620.         event -- eyelink event, like pylink.STARTSACC
  621.  
  622.         Returns:
  623.         A tuple (timestamp, event).
  624.         The event is in float_data format. The timestamp is in experiment time
  625.  
  626.         Exceptions:
  627.         Raises an exceptions.runtime_error on failure
  628.         </DOC>"""
  629.  
  630.         if not self.recording:
  631.             raise exceptions.runtime_error("Please start recording before collecting eyelink data")
  632.  
  633.         if self.eye_used == None:
  634.             self.set_eye_used()
  635.  
  636.         t_0 = self.experiment.time()
  637.         while True:
  638.             d = 0
  639.             while d != event:
  640.                 d = pylink.getEYELINK().getNextData()
  641.             # ignore d if its event occured before t_0:
  642.             float_data = pylink.getEYELINK().getFloatData()
  643.             if float_data.getTime() - self.get_eyelink_clock_async() > t_0:
  644.                 break
  645.  
  646.         return float_data.getTime() - self.get_eyelink_clock_async(), float_data
  647.  
  648.     def wait_for_saccade_start(self):
  649.  
  650.         """<DOC>
  651.         Waits for a saccade start
  652.  
  653.         Returns:
  654.         timestamp in experiment time, start_pos
  655.  
  656.         Exceptions:
  657.         Raises an exceptions.runtime_error on failure
  658.         </DOC>"""
  659.  
  660.         t, d = self.wait_for_event(pylink.STARTSACC)
  661.         return t, d.getStartGaze()
  662.  
  663.     def __wait_for_saccade_start_pre_10028(self):
  664.  
  665.         """
  666.         Waits for a saccade start, see wait_for_saccade_start
  667.  
  668.         This implementation catches a pylink bug that existed before pylink 1.0.0.28
  669.         """
  670.  
  671.         t, d = self.wait_for_event(pylink.STARTSACC)
  672.         return t, ( d.getStartGaze()[1], d.getHref()[0] )
  673.  
  674.     def wait_for_saccade_end(self):
  675.  
  676.         """<DOC>
  677.         Waits for a saccade end
  678.  
  679.         Returns:
  680.         timestamp in experiment time, start_pos, end_pos
  681.  
  682.         Exceptions:
  683.         Raises an exceptions.runtime_error on failure
  684.         </DOC>"""
  685.  
  686.         t, d = self.wait_for_event(pylink.ENDSACC)
  687.         return t, d.getStartGaze(), d.getEndGaze()
  688.  
  689.     def wait_for_fixation_start(self):
  690.  
  691.         """<DOC>
  692.         Waits for a fixation start
  693.  
  694.         Returns:
  695.         timestamp (in experiment time), start_pos
  696.  
  697.         Exceptions:
  698.         Raises an exceptions.runtime_error on failure
  699.         </DOC>"""
  700.  
  701.         t, d = self.wait_for_event(pylink.STARTFIX)
  702.         return t, d.getStartGaze()
  703.  
  704.     def wait_for_fixation_end(self):
  705.  
  706.         """<DOC>
  707.         Waits for a fixation end
  708.  
  709.         Returns:
  710.         timestamp (in experiment time), start_pos, end_pos
  711.  
  712.         Exceptions:
  713.         Raises an exceptions.runtime_error on failure
  714.         </DOC>"""
  715.  
  716.         t, d = self.wait_for_event(pylink.ENDFIX)
  717.         return t, d.getStartGaze(), d.getEndGaze()
  718.  
  719.     def wait_for_blink_start(self):
  720.  
  721.         """<DOC>
  722.         Waits for a blink start
  723.  
  724.         Returns:
  725.         timestamp (in experiment time)
  726.  
  727.         Exceptions:
  728.         Raises an exceptions.runtime_error on failure
  729.         </DOC>"""
  730.  
  731.         t, d = self.wait_for_event(pylink.STARTBLINK)
  732.         return t
  733.  
  734.     def wait_for_blink_end(self):
  735.  
  736.         """<DOC>
  737.         Waits for a blink end
  738.  
  739.         Returns:
  740.         timestamp (in experiment time)
  741.  
  742.         Exceptions:
  743.         Raises an exceptions.runtime_error on failure
  744.         </DOC>"""
  745.  
  746.         t, d = self.wait_for_event(pylink.ENDBLINK)
  747.         return t
  748.  
  749.     def confirm_abort_experiment(self):
  750.         """
  751.         Asks for confirmation before aborting the experiment.
  752.         Displays a confirmation screen, collects the response, and acts accordingly
  753.  
  754.         Raises a response_error upon confirmation
  755.  
  756.         """
  757.         # Display the confirmation screen
  758.         conf_canvas = canvas(self.experiment)
  759.         conf_kb = keyboard(self.experiment, timeout = None)
  760.         yc = conf_canvas.ycenter()
  761.         ld = 40
  762.         conf_canvas.clear()
  763.         conf_canvas.text("Really abort experiment?", y = yc - 3 * ld)
  764.         conf_canvas.text("Hit 'Y' to abort, ", y = yc - 0.5 * ld)
  765.         conf_canvas.text("Hit any other key or wait 5s to go to setup, ", y = yc + 0.5 * ld)
  766.         conf_canvas.show()
  767.  
  768.         # process the response:
  769.         try:
  770.             key, time = conf_kb.get_key(timeout = 5000)
  771.         except:
  772.             return False
  773.  
  774.         # if confirmation, close experiment
  775.         if key == 'y':
  776.             raise openexp.exceptions.response_error( \
  777.                             "The experiment was aborted")
  778.         else:
  779.             return False
  780.  
  781.     def prepare_backdrop(self, canvas):
  782.  
  783.         """<DOC>
  784.         Convert a surface to the format required by the eyelink.
  785.  
  786.         WARNING: this function can take between 50-150 ms to complete, depending on the resolution of the image
  787.         and the cpu power of your machine. Do not use during time critical phases of your experiment
  788.  
  789.         Arguments:
  790.         canvas -- an openexp canvas
  791.  
  792.         Returns:
  793.         A tuple with in ((list) image in array2d format, (int) image width, (int) image height)
  794.         </DOC>"""
  795.  
  796.         if self.experiment.canvas_backend != 'legacy':
  797.             raise exceptions.runtime_error('prepare_backdrop requires the legacy back-end')
  798.  
  799.         return (pygame.surfarray.array2d(canvas.surface).transpose().tolist(), self.experiment.width, self.experiment.height)
  800.  
  801.     def set_backdrop(self, backdrop):
  802.        
  803.         """<DOC>
  804.         Set backdrop image of Eyelink computer. For better performance, it can be
  805.         useful to already convert the canvas to send to the eyelink in the prepare phase using eyelink.prepare_backdrop().
  806.         If speed is not an issue, you can also directly pass a openexp.canvas object and this function
  807.         will take care of the conversion
  808.  
  809.         WARNING: this function can take between 10-50 ms to complete, depending on the resolution of the image
  810.         and the cpu power of your machine. Do not use during time critical phases of your experiment
  811.  
  812.         Arguments:
  813.         backdrop --
  814.  
  815.         an openexp canvas
  816.         OR
  817.         a tuple representation (created with prepare_backdrop()) containing
  818.         1. (list) a numpy array2d.tolist() representation of the image
  819.         2. (int)the width of the image
  820.         3. (int)the height of the image
  821.  
  822.         Returns:
  823.         (int) The amount of time in ms the function took to complete
  824.         </DOC>"""
  825.         starttime = self.experiment.time()
  826.  
  827.         # For now only the legacy backend will be supported
  828.         # Future releases will support all backends
  829.         if self.experiment.canvas_backend != 'legacy':
  830.             raise exceptions.runtime_error('set_backdrop for now requires the legacy back-end')
  831.  
  832.         # backdrop argument needs to be a canvas or tuple object: if not raise an exception
  833.         if type(backdrop) not in [tuple, canvas]:
  834.             raise exceptions.runtime_error('Invalid backdrop argument: needs to be a openexp.canvas or a tuple(list,width,height) object')
  835.  
  836.         # If backdrop argument is a canvas, first convert it to the required list representation
  837.         if type(backdrop) == canvas:
  838.             backdrop = self.prepare_backdrop(backdrop)
  839.  
  840.         # If the backdrop argument is tuple containing the list representation, send it to the eyelink
  841.         # (also works for the canvas that just got converted)
  842.         if type(backdrop) == tuple:
  843.             # Check if tuple has correct format
  844.             if len(backdrop) != 3 or type(backdrop[0]) != list or type(backdrop[1]) != int or type(backdrop[2]) != int:
  845.                 raise exceptions.runtime_error('Invalid tuple; needs to be (array2d.image,width,height)')
  846.             else:
  847.                 el = pylink.getEYELINK()
  848.  
  849.                 # "Forward" compatibility
  850.                 # In the current unofficial version of pylink, the function that transfers a 2D array list representation
  851.                 # to the host PC is called bitmap2DBackdrop. According to the dev team, this function will be integrated with the
  852.                 # old bitmapBackdop function again and the bitmap2DBackdrop function will disappear. The following check is to make
  853.                 # sure the set_backdrop function will not break
  854.                 if hasattr(el,"bitmap2DBackdrop"):
  855.                     send_backdrop = el.bitmap2DBackdrop
  856.                 else:
  857.                     send_backdrop = el.bitmapBackdrop
  858.  
  859.                 send_backdrop = el.bitmap2DBackdrop
  860.  
  861.                 img = backdrop[0]
  862.                 width = backdrop[1]
  863.                 height = backdrop[2]
  864.                 send_backdrop(width,height,img,0,0,width,height,0,0,pylink.BX_MAXCONTRAST)
  865.         else:
  866.             raise exceptions.runtime_error('Unable to send backdrop')
  867.         return self.experiment.time() - starttime
  868.  
  869. class libeyelink_dummy:
  870.  
  871.     """
  872.     A dummy class to keep things running if there is
  873.     no tracker attached.
  874.     """
  875.  
  876.     def __init__(self):
  877.         pass
  878.  
  879.     def send_command(self, cmd):
  880.         pass
  881.  
  882.     def log(self, msg):
  883.         print 'libeyelink.log(): %s' % msg
  884.  
  885.     def log_var(self, var, val):
  886.         pass
  887.  
  888.     def status_msg(self, msg):
  889.         pass
  890.  
  891.     def connected(self):
  892.         pass
  893.  
  894.     def calibrate(self, beep=True, target_size=16):
  895.         pass
  896.  
  897.     def drift_correction(self, pos = None, fix_triggered = False):
  898.         pygame.time.delay(200)
  899.         return True
  900.  
  901.     def prepare_drift_correction(self, pos):
  902.         pass
  903.  
  904.     def fix_triggered_drift_correction(self, pos = None, min_samples = 30, max_dev = 60, reset_threshold = 10):
  905.         pygame.time.delay(200)
  906.         return True
  907.  
  908.     def start_recording(self):
  909.         pass
  910.  
  911.     def stop_recording(self):
  912.         pass
  913.  
  914.     def close(self):
  915.         pass
  916.  
  917.     def set_eye_used(self):
  918.         pass
  919.  
  920.     def sample(self):
  921.         return 0,0
  922.  
  923.     def pupil_size(self):
  924.         return 0
  925.  
  926.     def wait_for_event(self, event):
  927.         pass
  928.  
  929.     def wait_for_saccade_start(self):
  930.         pygame.time.delay(100)
  931.         return pygame.time.get_ticks(), (0, 0)
  932.  
  933.     def wait_for_saccade_end(self):
  934.         pygame.time.delay(100)
  935.         return pygame.time.get_ticks(), (0, 0), (0, 0)
  936.  
  937.     def wait_for_fixation_start(self):
  938.         pygame.time.delay(100)
  939.         return pygame.time.get_ticks(), (0, 0)
  940.  
  941.     def wait_for_fixation_end(self):
  942.         pygame.time.delay(100)
  943.         return pygame.time.get_ticks(), (0, 0)
  944.  
  945.     def wait_for_blink_start(self):
  946.         pygame.time.delay(100)
  947.         return pygame.time.get_ticks(), (0, 0)
  948.  
  949.     def wait_for_blink_end(self):
  950.         pygame.time.delay(100)
  951.         return pygame.time.get_ticks(), (0, 0)
  952.  
  953.     def prepare_backdrop(self, canvas):
  954.         pass
  955.  
  956.     def set_backdrop(self, backdrop):
  957.         pass
  958.  
  959. class eyelink_graphics(custom_display):
  960.  
  961.     """
  962.     A custom graphics environment to provide calibration functionality using
  963.     OpenSesame, rather than PyLinks built-in system. Derived from the examples
  964.     provided with PyLink
  965.     """
  966.  
  967.     fgcolor = 255, 255, 255, 255
  968.     bgcolor = 0, 0, 0, 255
  969.  
  970.     def __init__(self, experiment, tracker):
  971.  
  972.         """
  973.         Constructor
  974.  
  975.         Arguments:
  976.         experiment -- opensesame experiment
  977.         tracker -- an eyelink instance
  978.         """
  979.  
  980.         pylink.EyeLinkCustomDisplay.__init__(self)
  981.  
  982.         self.experiment = experiment
  983.         self.my_canvas = canvas(self.experiment)
  984.         self.my_keyboard = keyboard(self.experiment, timeout=0)
  985.         self.my_mouse = mouse(self.experiment)
  986.  
  987.         self.__target_beep__ = synth(self.experiment, length = 50)
  988.         self.__target_beep__done__ = synth(self.experiment, freq = 880, length = 200)
  989.         self.__target_beep__error__ = synth(self.experiment, freq = 220, length = 200)
  990.  
  991.         self.state = None
  992.  
  993.         self.imagebuffer = array.array('l')
  994.         self.pal = None
  995.         self.size = (0,0)
  996.         self.tmp_file = os.path.join(tempfile.gettempdir(), '__eyelink__.jpg')
  997.  
  998.         self.set_tracker(tracker)
  999.         self.last_mouse_state = -1
  1000.         self.experiment.eyelink_esc_pressed = False
  1001.  
  1002.     def set_tracker(self, tracker):
  1003.  
  1004.         """
  1005.         Connect the tracker to the graphics environment
  1006.  
  1007.         Arguments:
  1008.         tracker -- an eyelink instance
  1009.         """
  1010.  
  1011.         self.tracker = tracker
  1012.         self.tracker_version = tracker.getTrackerVersion()
  1013.         if(self.tracker_version >=3):
  1014.             self.tracker.sendCommand("enable_search_limits=YES")
  1015.             self.tracker.sendCommand("track_search_limits=YES")
  1016.             self.tracker.sendCommand("autothreshold_click=YES")
  1017.             self.tracker.sendCommand("autothreshold_repeat=YES")
  1018.             self.tracker.sendCommand("enable_camera_position_detect=YES")
  1019.  
  1020.     def setup_cal_display (self):
  1021.  
  1022.         """Setup the calibration display, which contains some instructions"""
  1023.  
  1024.         yc = self.my_canvas.ycenter()
  1025.         ld = 40
  1026.         self.my_canvas.clear()
  1027.         self.my_canvas.text("OpenSesame eyelink plug-in", y = yc - 5 * ld)
  1028.         self.my_canvas.text("Enter: Enter camera set-up", y = yc - 3 * ld)
  1029.         self.my_canvas.text("C: Calibration", y = yc - 2 * ld)
  1030.         self.my_canvas.text("V: Validation", y = yc - 1 * ld)
  1031.         self.my_canvas.text("Q: Exit set-up", y = yc - 0 * ld)
  1032.         self.my_canvas.text("A: Automatically adjust threshold", y = yc + 1 * ld)
  1033.         self.my_canvas.text("Up/ Down: Adjust threshold", y = yc + 2 * ld)
  1034.         self.my_canvas.text("Left/ Right: Switch camera view", y = yc + 3 * ld)
  1035.         self.my_canvas.show()
  1036.  
  1037.     def exit_cal_display(self):
  1038.  
  1039.         """Clear the display"""
  1040.  
  1041.         self.my_canvas.clear()
  1042.         self.my_canvas.show()
  1043.  
  1044.     def record_abort_hide(self):
  1045.  
  1046.         """What does it do?"""
  1047.  
  1048.         pass
  1049.  
  1050.     def clear_cal_display(self):
  1051.  
  1052.         """Clear the display"""
  1053.  
  1054.         self.my_canvas.clear()
  1055.         self.my_canvas.show()
  1056.  
  1057.  
  1058.     def erase_cal_target(self):
  1059.  
  1060.         """Is done before drawing"""
  1061.  
  1062.         pass
  1063.  
  1064.     def draw_cal_target(self, x, y):
  1065.  
  1066.         """
  1067.         Draw the calibration target
  1068.  
  1069.         Arguments:
  1070.         x -- the x-coordinate of the target
  1071.         y -- the y-coordinate of the target
  1072.         """
  1073.  
  1074.         self.my_canvas.clear()
  1075.  
  1076.         self.my_canvas.circle(x, y, r=self.experiment.eyelink.cal_target_size, fill=True)
  1077.         self.my_canvas.circle(x, y, r=2, color=self.experiment.background, fill=True)
  1078.         self.my_canvas.show()
  1079.         if self.experiment.eyelink.cal_beep:
  1080.             self.play_beep(pylink.CAL_TARG_BEEP)
  1081.  
  1082.     def play_beep(self, beepid):
  1083.  
  1084.         """
  1085.         Play a sound
  1086.  
  1087.         Arguments:
  1088.         beepid -- a pylink beep id
  1089.         """
  1090.  
  1091.         if beepid == pylink.CAL_TARG_BEEP:
  1092.             self.__target_beep__.play()
  1093.         elif beepid == pylink.CAL_ERR_BEEP or beepid == pylink.DC_ERR_BEEP:
  1094.             self.my_canvas.clear()
  1095.             self.my_canvas.text("Calibration unsuccessfull", y = self.my_canvas.ycenter() - 20)
  1096.             self.my_canvas.text("Press 'Enter' to return to menu", y = self.my_canvas.ycenter() + 20)
  1097.             self.my_canvas.show()
  1098.             self.__target_beep__error__.play()
  1099.         elif beepid == pylink.CAL_GOOD_BEEP:
  1100.             self.my_canvas.clear()
  1101.             if self.state == "calibration":
  1102.                 self.my_canvas.text("Success!", y = self.my_canvas.ycenter() - 20)
  1103.                 self.my_canvas.text("Press 'v' to validate", y = self.my_canvas.ycenter() + 20)
  1104.             elif self.state == "validation":
  1105.                 self.my_canvas.text("Success!", y = self.my_canvas.ycenter() - 20)
  1106.                 self.my_canvas.text("Press 'Enter' to return to menu", y = self.my_canvas.ycenter() + 20)
  1107.             else:
  1108.                 self.my_canvas.text("Press 'Enter' to return to menu")
  1109.             self.my_canvas.show()
  1110.             self.__target_beep__done__.play()
  1111.         else: # DC_GOOD_BEEP    or DC_TARG_BEEP
  1112.             pass
  1113.  
  1114.     def getColorFromIndex(self,colorindex):
  1115.  
  1116.         """Unused"""
  1117.  
  1118.         pass
  1119.  
  1120.     def draw_line(self, x1, y1, x2, y2, colorindex):
  1121.  
  1122.         """Unused"""
  1123.  
  1124.         pass
  1125.  
  1126.     def draw_lozenge(self,x,y,width,height,colorindex):
  1127.  
  1128.         """Unused"""
  1129.  
  1130.         pass
  1131.  
  1132.     def get_mouse_state(self):
  1133.  
  1134.         """Unused"""
  1135.  
  1136.         pass
  1137.  
  1138.     def get_input_key(self):
  1139.  
  1140.         """
  1141.         Get an input key
  1142.  
  1143.         Returns:
  1144.         A list of (keycode, moderator tuples)
  1145.         """
  1146.  
  1147.         try:
  1148.             key, time = self.my_keyboard.get_key()
  1149.         except openexp.exceptions.response_error:
  1150.             key = 'escape'
  1151.         except:
  1152.             return None
  1153.  
  1154.         if key == "return":
  1155.             keycode = pylink.ENTER_KEY
  1156.             self.state = None
  1157.         elif key == "space":
  1158.             keycode = ord(" ")
  1159.         elif key == "q":
  1160.             keycode = pylink.ESC_KEY
  1161.             self.state = None
  1162.         elif key == "c":
  1163.             keycode = ord("c")
  1164.             self.state = "calibration"
  1165.         elif key == "v":
  1166.             keycode = ord("v")
  1167.             self.state = "validation"
  1168.         elif key == "a":
  1169.             keycode = ord("a")
  1170.         elif key == "up":
  1171.             keycode = pylink.CURS_UP
  1172.         elif key == "down":
  1173.             keycode = pylink.CURS_DOWN
  1174.         elif key == "left":
  1175.             keycode = pylink.CURS_LEFT
  1176.         elif key == "right":
  1177.             keycode = pylink.CURS_RIGHT
  1178.         elif key == "escape": # escape does the same as 'q', but also marks esc_pressed
  1179.             self.experiment.eyelink_esc_pressed = True
  1180.             keycode = pylink.ESC_KEY
  1181.             self.state = None
  1182.         else:
  1183.             keycode = 0
  1184.  
  1185.         return [pylink.KeyInput(keycode, pygame.KMOD_NONE)]
  1186.  
  1187.     def exit_image_display(self):
  1188.  
  1189.         """Exit the image display"""
  1190.  
  1191.         self.clear_cal_display()
  1192.  
  1193.     def alert_printf(self,msg):
  1194.  
  1195.         """Print alert message"""
  1196.  
  1197.         print "eyelink_graphics.alert_printf(): %s" % msg
  1198.  
  1199.     def setup_image_display(self, width, height):
  1200.  
  1201.         """
  1202.         Setup the image display
  1203.  
  1204.         Arguments:
  1205.         width -- the width of the display
  1206.         height -- the height of the display
  1207.         """
  1208.  
  1209.         self.size = (width,height)
  1210.         self.clear_cal_display()
  1211.         self.last_mouse_state = -1
  1212.         self.imagebuffer = array.array('l')
  1213.  
  1214.     def image_title(self, text):
  1215.  
  1216.         """Unused"""
  1217.  
  1218.         pass
  1219.  
  1220.     def draw_image_line(self, width, line, totlines, buff):
  1221.  
  1222.         """
  1223.         Draws a single eye video frame
  1224.  
  1225.         Arguments:
  1226.         width -- the width of the video
  1227.         line -- the line nr of the current line
  1228.         totlines -- the total nr of lines in a video
  1229.         buff -- the frame buffer
  1230.         """
  1231.  
  1232.         for i in range(width):
  1233.             try:
  1234.                 self.imagebuffer.append(self.pal[buff[i]])
  1235.             except:
  1236.                 pass
  1237.  
  1238.         if line == totlines:
  1239.             bufferv = self.imagebuffer.tostring()
  1240.             img = Image.new("RGBX", self.size)
  1241.             img.fromstring(bufferv)
  1242.             img = img.resize(self.size)
  1243.             img = pygame.image.fromstring(img.tostring(), self.size, "RGBX")
  1244.             self.my_canvas.clear()
  1245.             pygame.image.save(img, self.tmp_file)
  1246.             self.my_canvas.image(self.tmp_file, scale=2.)
  1247.             self.my_canvas.show()
  1248.             self.imagebuffer = array.array('l')
  1249.  
  1250.     def set_image_palette(self, r, g, b):
  1251.  
  1252.         """Set the image palette"""
  1253.  
  1254.         self.imagebuffer = array.array('l')
  1255.         self.clear_cal_display()
  1256.         sz = len(r)
  1257.         i =0
  1258.         self.pal = []
  1259.         while i < sz:
  1260.             rf = int(b[i])
  1261.             gf = int(g[i])
  1262.             bf = int(r[i])
  1263.             self.pal.append((rf<<16) | (gf<<8) | (bf))
  1264.             i = i+1
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement