def createNewMonitoredDevice(self,device_class_name,deviceConfig): #print2err("#### createNewMonitoredDevice: ",device_class_name) self._all_device_config_errors=dict() try: device_instance=None device_config=None device_event_ids=None event_classes=None device_instance_and_config=self.addDeviceToMonitor(device_class_name,deviceConfig) if device_instance_and_config: device_instance,device_config,device_event_ids,event_classes=device_instance_and_config DeviceConstants.addClassMapping(device_instance.__class__) EventConstants.addClassMappings(device_instance.__class__,device_event_ids,event_classes) else: print2err('## Device was not started by the ioHub Server: ',device_class_name) raise ioHubError("Device config validation failed") except: print2err("Error during device creation ....") printExceptionDetailsToStdErr() raise ioHubError("Error during device creation ....") # Update DataStore Structure if required. try: if self.emrt_file is not None: self.emrt_file.updateDataStoreStructure(device_instance,event_classes) except: print2err("Error while updating datastore for device addition:",device_instance,device_event_ids) printExceptionDetailsToStdErr() self.log("Adding ioServer and DataStore event listeners......") # add event listeners for saving events if self.emrt_file is not None: if device_config['save_events']: device_instance._addEventListener(self.emrt_file,device_event_ids) self.log("DataStore listener for device added: device: %s eventIDs: %s"%(device_instance.__class__.__name__,device_event_ids)) #print2err("DataStore listener for device added: device: %s eventIDs: %s"%(device_instance.__class__.__name__,device_event_ids)) else: #print2err("DataStore saving disabled for device: %s"%(device_instance.__class__.__name__,)) self.log("DataStore saving disabled for device: %s"%(device_instance.__class__.__name__,)) else: #print2err("DataStore Not Evabled. No events will be saved.") self.log("DataStore Not Enabled. No events will be saved.") # Add Device Monitor for Keyboard or Mouse device type deviceDict=ioServer.deviceDict iohub=self if device_class_name in ('Mouse','Keyboard'): if Computer.system == 'win32': if self._hookDevice is None: iohub.log("Creating pyHook Monitors....") #print2err("Creating pyHook Monitor....") class pyHookDevice(object): def __init__(self): import pyHook self._hookManager=pyHook.HookManager() self._mouseHooked=False self._keyboardHooked=False if device_class_name == 'Mouse': #print2err("Hooking Mouse.....") self._hookManager.MouseAll = deviceDict['Mouse']._nativeEventCallback self._hookManager.HookMouse() self._mouseHooked=True elif device_class_name == 'Keyboard': #print2err("Hooking Keyboard.....") self._hookManager.KeyAll = deviceDict['Keyboard']._nativeEventCallback self._hookManager.HookKeyboard() self._keyboardHooked=True #iohub.log("WindowsHook PumpEvents Periodic Timer Created.") def _poll(self): import pythoncom # PumpWaitingMessages returns 1 if a WM_QUIT message was received, else 0 if pythoncom.PumpWaitingMessages() == 1: raise KeyboardInterrupt() #print2err("Creating pyHook Monitor......") self._hookDevice=pyHookDevice() hookMonitor=DeviceMonitor(self._hookDevice,0.00375) self.deviceMonitors.append(hookMonitor) #print2err("Created pyHook Monitor.") else: #print2err("UPDATING pyHook Monitor....") if device_class_name == 'Mouse' and self._hookDevice._mouseHooked is False: #print2err("Hooking Mouse.....") self._hookDevice._hookManager.MouseAll = deviceDict['Mouse']._nativeEventCallback self._hookDevice._hookManager.HookMouse() self._hookDevice._mouseHooked=True elif device_class_name == 'Keyboard' and self._hookDevice._keyboardHooked is False: #print2err("Hooking Keyboard.....") self._hookDevice._hookManager.KeyAll = deviceDict['Keyboard']._nativeEventCallback self._hookDevice._hookManager.HookKeyboard() self._hookDevice._keyboardHooked=True #print2err("Finished Updating pyHook Monitor.") elif Computer.system == 'linux2': # TODO: consider switching to xlib-ctypes implementation of xlib # https://github.com/garrybodsworth/pyxlib-ctypes from .devices import pyXHook if self._hookManager is None: #iohub.log("Creating pyXHook Monitors....") self._hookManager = pyXHook.HookManager() self._hookManager._mouseHooked=False self._hookManager._keyboardHooked=False if device_class_name == 'Keyboard': #print2err("Hooking Keyboard.....") self._hookManager.HookKeyboard() self._hookManager.KeyDown = deviceDict['Keyboard']._nativeEventCallback self._hookManager.KeyUp = deviceDict['Keyboard']._nativeEventCallback self._hookManager._keyboardHooked=True elif device_class_name == 'Mouse': #print2err("Hooking Mouse.....") self._hookManager.HookMouse() self._hookManager.MouseAllButtonsDown = deviceDict['Mouse']._nativeEventCallback self._hookManager.MouseAllButtonsUp = deviceDict['Mouse']._nativeEventCallback self._hookManager.MouseAllMotion = deviceDict['Mouse']._nativeEventCallback self._hookManager._mouseHooked=True #print2err("Starting pyXHook.HookManager.....") self._hookManager.start() #iohub.log("pyXHook Thread Created.") #print2err("pyXHook.HookManager thread created.") else: #iohub.log("Updating pyXHook Monitor....") if device_class_name == 'Keyboard' and self._hookManager._keyboardHooked is False: #print2err("Hooking Keyboard.....") self._hookManager.HookKeyboard() self._hookManager.KeyDown = deviceDict['Keyboard']._nativeEventCallback self._hookManager.KeyUp = deviceDict['Keyboard']._nativeEventCallback self._hookManager._keyboardHooked=True if device_class_name == 'Mouse' and self._hookManager._mouseHooked is False: #print2err("Hooking Mouse.....") self._hookManager.HookMouse() self._hookManager.MouseAllButtonsDown = deviceDict['Mouse']._nativeEventCallback self._hookManager.MouseAllButtonsUp = deviceDict['Mouse']._nativeEventCallback self._hookManager.MouseAllMotion = deviceDict['Mouse']._nativeEventCallback self._hookManager._mouseHooked=True #iohub.log("Finished Updating pyXHook Monitor....") else: # OSX if self._hookDevice is None: self._hookDevice=[] if device_class_name == 'Mouse' and 'Mouse' not in self._hookDevice: #print2err("Hooking OSX Mouse.....") mouseHookMonitor=DeviceMonitor(deviceDict['Mouse'],0.004) self.deviceMonitors.append(mouseHookMonitor) deviceDict['Mouse']._CGEventTapEnable(deviceDict['Mouse']._tap, True) self._hookDevice.append('Mouse') #print2err("Done Hooking OSX Mouse.....") if device_class_name == 'Keyboard' and 'Keyboard' not in self._hookDevice: #print2err("Hooking OSX Keyboard.....") kbHookMonitor=DeviceMonitor(deviceDict['Keyboard'],0.004) self.deviceMonitors.append(kbHookMonitor) deviceDict['Keyboard']._CGEventTapEnable(deviceDict['Keyboard']._tap, True) self._hookDevice.append('Keyboard') #print2err("DONE Hooking OSX Keyboard.....") return [device_class_name, device_config['name'], device_instance._getRPCInterface()]
def _processDeviceEventIteration(self): for device in self.devices: try: events=device._getNativeEventBuffer() #if events and len(events)>0: # ioHub.print2err("_processDeviceEventIteration.....", device._event_listeners) while len(events)>0: evt=events.popleft() e=device._getIOHubEventObject(evt) if e is not None: for l in device._getEventListeners(e[DeviceEvent.EVENT_TYPE_ID_INDEX]): l._handleEvent(e) except: printExceptionDetailsToStdErr() print2err("Error in processDeviceEvents: ", device, " : ", len(events), " : ", e) print2err("Event type ID: ",e[DeviceEvent.EVENT_TYPE_ID_INDEX], " : " , EventConstants.getName(e[DeviceEvent.EVENT_TYPE_ID_INDEX])) print2err("--------------------------------------")
class GazepointPsychopyCalibrationGraphics(object): IOHUB_HEARTBEAT_INTERVAL = 0.050 CALIBRATION_POINT_LIST = [(0.5, 0.5), (0.1, 0.1), (0.9, 0.1), (0.9, 0.9), (0.1, 0.9)] _keyboard_key_index = EC.getClass(EC.KEYBOARD_RELEASE).CLASS_ATTRIBUTE_NAMES.index('key') def __init__(self, eyetrackerInterface, calibration_args): self._eyetracker = eyetrackerInterface self.screenSize = eyetrackerInterface._display_device.getPixelResolution() self.width = self.screenSize[0] self.height = self.screenSize[1] self._ioKeyboard = None self._msg_queue = [] self._lastCalibrationOK = False display = self._eyetracker._display_device self._device_config = self._eyetracker.getConfiguration() self._calibration_args = self._device_config.get('calibration') unit_type = self.getCalibSetting('unit_type') if unit_type is None: unit_type = display.getCoordinateType() self._calibration_args['unit_type'] = unit_type color_type = self.getCalibSetting('color_type') if color_type is None: color_type = display.getColorSpace() self._calibration_args['color_type'] = color_type calibration_methods = dict(THREE_POINTS=3, FIVE_POINTS=5, NINE_POINTS=9, THIRTEEN_POINTS=13) cal_type = self.getCalibSetting('type') if cal_type in calibration_methods: num_points = calibration_methods[cal_type] if num_points == 3: GazepointPsychopyCalibrationGraphics.CALIBRATION_POINT_LIST = [(0.5, 0.1), (0.1, 0.9), (0.9, 0.9)] elif num_points == 5: GazepointPsychopyCalibrationGraphics.CALIBRATION_POINT_LIST = [(0.5, 0.5), (0.1, 0.1), (0.9, 0.1), (0.9, 0.9), (0.1, 0.9)] elif num_points == 9: GazepointPsychopyCalibrationGraphics.CALIBRATION_POINT_LIST = [(0.5, 0.5), (0.1, 0.5), (0.9, 0.5), (0.1, 0.1), (0.5, 0.1), (0.9, 0.1), (0.9, 0.9), (0.5, 0.9), (0.1, 0.9)] elif num_points == 13: GazepointPsychopyCalibrationGraphics.CALIBRATION_POINT_LIST = [(0.5, 0.5), (0.1, 0.5), (0.9, 0.5), (0.1, 0.1), (0.5, 0.1), (0.9, 0.1), (0.9, 0.9), (0.5, 0.9), (0.1, 0.9), (0.25, 0.25), (0.25, 0.75), (0.75, 0.75), (0.75, 0.25) ] if display.getCoordinateType() != unit_type: raise RuntimeWarning("ioHub GP3 Warning: display.getCoordinateType() != " "self.getCalibSetting('unit_type'): {} {}".format(display.getCoordinateType(), unit_type)) self.window = visual.Window( self.screenSize, monitor=display.getPsychopyMonitorName(), units=unit_type, fullscr=True, allowGUI=False, screen=display.getIndex(), color=self.getCalibSetting(['screen_background_color']), colorSpace=color_type) self.window.flip(clearBuffer=True) self._createStim() self._registerEventMonitors() self._lastMsgPumpTime = currentTime() self.clearAllEventBuffers() def getCalibSetting(self, setting): if isinstance(setting, str): setting = [setting, ] calibration_args = self._calibration_args if setting: for s in setting[:-1]: calibration_args = calibration_args.get(s) return calibration_args.get(setting[-1]) def clearAllEventBuffers(self): self._eyetracker._iohub_server.eventBuffer.clear() for d in self._eyetracker._iohub_server.devices: d.clearEvents() def _registerEventMonitors(self): kbDevice = None if self._eyetracker._iohub_server: for dev in self._eyetracker._iohub_server.devices: if dev.__class__.__name__ == 'Keyboard': kbDevice = dev if kbDevice: eventIDs = [] for event_class_name in kbDevice.__class__.EVENT_CLASS_NAMES: eventIDs.append(getattr(EC, convertCamelToSnake(event_class_name[:-5], False))) self._ioKeyboard = kbDevice self._ioKeyboard._addEventListener(self, eventIDs) else: print2err( 'Warning: GazePoint Cal GFX could not connect to Keyboard device for events.') def _unregisterEventMonitors(self): if self._ioKeyboard: self._ioKeyboard._removeEventListener(self) def _handleEvent(self, event): event_type_index = DeviceEvent.EVENT_TYPE_ID_INDEX if event[event_type_index] == EC.KEYBOARD_PRESS: ek = event[self._keyboard_key_index] if isinstance(ek, bytes): ek = ek.decode('utf-8') if ek == ' ': self._msg_queue.append('SPACE_KEY_ACTION') self.clearAllEventBuffers() elif ek == 'escape': self._msg_queue.append('QUIT') self.clearAllEventBuffers() def MsgPump(self): # keep the psychopy window happy ;) if currentTime() - self._lastMsgPumpTime > self.IOHUB_HEARTBEAT_INTERVAL: # try to keep ioHub from being blocked. ;( if self._eyetracker._iohub_server: for dm in self._eyetracker._iohub_server.deviceMonitors: dm.device._poll() self._eyetracker._iohub_server.processDeviceEvents() self._lastMsgPumpTime = currentTime() def getNextMsg(self): if len(self._msg_queue) > 0: msg = self._msg_queue[0] self._msg_queue = self._msg_queue[1:] return msg def _createStim(self): """ outer_diameter: 35 outer_stroke_width: 5 outer_fill_color: [255,255,255] outer_line_color: [255,255,255] inner_diameter: 5 inner_stroke_width: 0 inner_color: [0,0,0] inner_fill_color: [0,0,0] inner_line_color: [0,0,0] calibration_prefs=self._eyetracker.getConfiguration()['calibration']['target_attributes'] """ color_type = self.getCalibSetting('color_type') unit_type = self.getCalibSetting('unit_type') lwidth = self.getCalibSetting(['target_attributes', 'outer_stroke_width']) radius = self.getCalibSetting(['target_attributes', 'outer_diameter']) / 2.0 fcolor = self.getCalibSetting(['target_attributes', 'outer_fill_color']) lcolor = self.getCalibSetting(['target_attributes', 'outer_line_color']) self.calibrationPointOUTER = visual.Circle(self.window, pos=(0, 0), name='CP_OUTER', radius=radius, lineWidth=lwidth, fillColor=fcolor, lineColor=lcolor, opacity=1.0, interpolate=False, edges=64, units=unit_type, colorSpace=color_type) lwidth = self.getCalibSetting(['target_attributes', 'inner_stroke_width']) radius = self.getCalibSetting(['target_attributes', 'inner_diameter']) / 2.0 fcolor = self.getCalibSetting(['target_attributes', 'inner_fill_color']) lcolor = self.getCalibSetting(['target_attributes', 'inner_line_color']) self.calibrationPointINNER = visual.Circle(self.window, pos=(0, 0), name='CP_INNER', radius=radius, lineWidth=lwidth, fillColor=fcolor, lineColor=lcolor, opacity=1.0, interpolate=False, edges=64, units=unit_type, colorSpace=color_type) instuction_text = 'Press SPACE to Start Calibration; ESCAPE to Exit.' self.textLineStim = visual.TextStim(self.window, text=instuction_text, pos=(0, 0), height=36, color=(0, 0, 0), colorSpace='rgb255', units='pix', wrapWidth=self.width * 0.9) def runCalibration(self): """Run calibration sequence """ instuction_text = 'Press SPACE to Start Calibration.' self.showSystemSetupMessageScreen(instuction_text) target_delay = self.getCalibSetting('target_delay') target_duration = self.getCalibSetting('target_duration') cal_target_list = self.CALIBRATION_POINT_LIST randomize_points = self.getCalibSetting('randomize') if randomize_points is True: # Randomize all but first target position. cal_target_list = self.CALIBRATION_POINT_LIST[1:] import random random.seed(None) random.shuffle(cal_target_list) cal_target_list.insert(0, self.CALIBRATION_POINT_LIST[0]) self._eyetracker._gp3set('CALIBRATE_SHOW', STATE=0) self._eyetracker._gp3set('CALIBRATE_START', STATE=0) self._eyetracker._gp3set('CALIBRATE_CLEAR') self._eyetracker._gp3set('SCREEN_SIZE', X=0, Y=0, WIDTH=self.width, HEIGHT=self.height) #print2err("Set GP3 SCREEN_SIZE: ", self._eyetracker._waitForAck('SCREEN_SIZE')) # Inform GazePoint of target list to be used for p in cal_target_list: x, y = p self._eyetracker._gp3set('CALIBRATE_ADDPOINT', X=x, Y=y) self._eyetracker._waitForAck('CALIBRATE_ADDPOINT') left, top, right, bottom = self._eyetracker._display_device.getCoordBounds() w, h = right - left, top - bottom self.clearCalibrationWindow() # Start drawing calibration points self._eyetracker._gp3set('CALIBRATE_SHOW', STATE=0) self._eyetracker._gp3set('CALIBRATE_START', STATE=1) # seems like gps expects animation at state of every target pos. i = 0 for pt in cal_target_list: # Convert GazePoint normalized positions to psychopy window unit positions # by using iohub display/window getCoordBounds. x, y = left + w * pt[0], bottom + h * (1.0 - pt[1]) self.clearAllEventBuffers() # Target animate / delay animate_enable = self.getCalibSetting(['target_attributes', 'animate', 'enable']) animate_expansion_ratio = self.getCalibSetting(['target_attributes', 'animate', 'expansion_ratio']) animate_contract_only = self.getCalibSetting(['target_attributes', 'animate', 'contract_only']) start_time = currentTime() while currentTime()-start_time <= target_delay: if animate_enable and i > 0: t = (currentTime() - start_time) / target_delay v1 = cal_target_list[i-1] v2 = pt t = 60.0 * ((1.0 / 10.0) * t ** 5 - (1.0 / 4.0) * t ** 4 + (1.0 / 6.0) * t ** 3) mx, my = ((1.0 - t) * v1[0] + t * v2[0], (1.0 - t) * v1[1] + t * v2[1]) moveTo = left + w * mx, bottom + h * (1.0 - my) self.drawCalibrationTarget(moveTo, reset=False) else: self.drawCalibrationTarget((x, y), False) self.getNextMsg() self.MsgPump() self.drawCalibrationTarget((x, y)) start_time = currentTime() outer_diameter = self.getCalibSetting(['target_attributes', 'outer_diameter']) inner_diameter = self.getCalibSetting(['target_attributes', 'inner_diameter']) while currentTime()-start_time <= target_duration: elapsed_time = currentTime()-start_time d = t = None if animate_contract_only: # Change target size from outer diameter to inner diameter over target_duration seconds. t = elapsed_time / target_duration d = outer_diameter - t * (outer_diameter - inner_diameter) self.calibrationPointOUTER.radius = d / 2 self.calibrationPointOUTER.draw() self.calibrationPointINNER.draw() self.window.flip(clearBuffer=True) elif animate_expansion_ratio not in [1, 1.0]: if elapsed_time <= target_duration/2: # In expand phase t = elapsed_time / (target_duration/2) d = outer_diameter + t * (outer_diameter*animate_expansion_ratio - outer_diameter) else: # In contract phase t = (elapsed_time-target_duration/2) / (target_duration/2) d = outer_diameter*animate_expansion_ratio - t * (outer_diameter*animate_expansion_ratio - inner_diameter) if d: self.calibrationPointOUTER.radius = d / 2 self.calibrationPointOUTER.draw() self.calibrationPointINNER.draw() self.window.flip(clearBuffer=True) #print2err(inner_diameter, " ", outer_diameter, " ", t, " ", d) self.getNextMsg() self.MsgPump() gevent.sleep(0.001) self.clearCalibrationWindow() self.clearAllEventBuffers() i += 1 instuction_text = "Calibration Complete. Press 'SPACE' key to continue." self.showSystemSetupMessageScreen(instuction_text) def clearCalibrationWindow(self): self.window.flip(clearBuffer=True) def showSystemSetupMessageScreen(self, text_msg='Press SPACE to Start Calibration; ESCAPE to Exit.'): self.clearAllEventBuffers() while True: self.textLineStim.setText(text_msg) self.textLineStim.draw() self.window.flip() msg = self.getNextMsg() if msg == 'SPACE_KEY_ACTION': self.clearAllEventBuffers() return True elif msg == 'QUIT': self.clearAllEventBuffers() return False self.MsgPump() gevent.sleep(0.001) def drawDefaultTarget(self): self.calibrationPointOUTER.radius = self.getCalibSetting(['target_attributes', 'outer_diameter']) / 2.0 self.calibrationPointOUTER.setLineColor(self.getCalibSetting(['target_attributes', 'outer_line_color'])) self.calibrationPointOUTER.setFillColor(self.getCalibSetting(['target_attributes', 'outer_fill_color'])) self.calibrationPointOUTER.lineWidth = int(self.getCalibSetting(['target_attributes', 'outer_stroke_width'])) self.calibrationPointINNER.radius = self.getCalibSetting(['target_attributes', 'inner_diameter']) / 2.0 self.calibrationPointINNER.setLineColor(self.getCalibSetting(['target_attributes', 'inner_line_color'])) self.calibrationPointINNER.setFillColor(self.getCalibSetting(['target_attributes', 'inner_fill_color'])) self.calibrationPointINNER.lineWidth = int(self.getCalibSetting(['target_attributes', 'inner_stroke_width'])) self.calibrationPointOUTER.draw() self.calibrationPointINNER.draw() return self.window.flip(clearBuffer=True) def drawCalibrationTarget(self, tp, reset=True): self.calibrationPointOUTER.setPos(tp) self.calibrationPointINNER.setPos(tp) if reset: return self.drawDefaultTarget() else: self.calibrationPointOUTER.draw() self.calibrationPointINNER.draw() return self.window.flip(clearBuffer=True)