def drawIA(self, x, y, size, index, color, name): '''Draws square interest area in EDF and a corresponding filled box on eye-tracker display. Parameters x, y -- x and y coordinates in degrees visual angle size -- length of one edge of square in degrees visual angle index -- number to assign interest area in EDF color -- color of box drawn on eye-tracker display (0 - 15) name -- string to name interest area in EDF ''' # Convert units to eyelink space elx = deg2pix(x, self.win.monitor) + (self.sres[0] / 2.0) ely = -(deg2pix(y, self.win.monitor) - (self.sres[1] / 2.0)) elsz = deg2pix(size, self.win.monitor) / 2.0 # Make top left / bottom right coordinates for square tplf = map(round, [elx - elsz, ely - elsz]) btrh = map(round, [elx + elsz, ely + elsz]) # Construct command strings flist = [index, name, color] + tplf + btrh iamsg = '!V IAREA RECTANGLE {0} {3} {4} {5} {6} {1}'.format(*flist) bxmsg = 'draw_filled_box {3} {4} {5} {6} {2}'.format(*flist) # Send commands self.tracker.sendMessage(iamsg) self.tracker.sendCommand(bxmsg)
def _get_tobii_pos(self, p): """Convert PsychoPy coordinates to Tobii ADCS coordinates. Args: p: Gaze position (x, y) in PsychoPy coordinate systems. Returns: Gaze position in Tobii ADCS. """ if self.win.units == 'norm': return ((p[0] + 1) / 2, (p[1] - 1) / -2) elif self.win.units == 'height': return (p[0] * (self.win.size[1] / self.win.size[0]) + 0.5, -p[1] + 0.5) elif self.win.units == 'pix': return self._pix2tobii(p) elif self.win.units in ['cm', 'deg', 'degFlat', 'degFlatPos']: if self.win.units == 'cm': p_pix = (cm2pix(p[0], self.win.monitor), cm2pix(p[1], self.win.monitor)) elif self.win.units == 'deg': p_pix = (deg2pix(p[0], self.win.monitor), deg2pix(p[1], self.win.monitor)) elif self.win.units in ['degFlat', 'degFlatPos']: p_pix = (deg2pix( np.array(p), self.win.monitor, correctFlat=True)) return self._pix2tobii(p_pix) else: raise ValueError('unit ({}) is not supported'.format( self.win.units))
def get_tobii_pos(self, p): """ Convert Tobii position to PsychoPy coordinate system. :param p: Position (x, y) """ if self.win.units == 'norm': gp = ((p[0] + 1) / 2, (p[1] + 1) / 2) elif self.win.units == 'height': gp = (p[0] * self.win.size[1] / self.win.size[0] + 0.5, p[1] + 0.5) elif self.win.units == 'pix': gp = (p[0] / self.win.size[0] + 0.5, p[1] / self.win.size[1] + 0.5) elif self.win.units == 'cm': p_pix = (cm2pix(p[0], self.win.monitor), cm2pix(p[1], self.win.monitor)) gp = (p_pix[0] / self.win.size[0] + 0.5, p_pix[1] / self.win.size[1] + 0.5) elif self.win.units == 'deg': p_pix = (deg2pix(p[0], self.win.monitor), deg2pix(p[1], self.win.monitor)) gp = (p_pix[0] / self.win.size[0] + 0.5, p_pix[1] / self.win.size[1] + 0.5) elif self.win.units in ['degFlat', 'degFlatPos']: p_pix = (deg2pix(np.array(p), self.win.monitor, correctFlat=True)) gp = (p_pix[0] / self.win.size[0] + 0.5, p_pix[1] / self.win.size[1] + 0.5) else: raise ValueError('unit ({}) is not supported'.format( self.win.units)) return (gp[0], 1 - gp[1]) # flip vert
def _calcFieldCoordsRendered(self): if self.units in ['norm', 'pix','height']: self._fieldSizeRendered=self.fieldSize self._fieldPosRendered=self.fieldPos elif self.units in ['deg', 'degs']: self._fieldSizeRendered=deg2pix(self.fieldSize, self.win.monitor) self._fieldPosRendered=deg2pix(self.fieldPos, self.win.monitor) elif self.units=='cm': self._fieldSizeRendered=cm2pix(self.fieldSize, self.win.monitor) self._fieldPosRendered=cm2pix(self.fieldPos, self.win.monitor)
def _calcVerticesRendered(self): self.needVertexUpdate=False if self.units in ['norm', 'pix', 'height']: self._verticesRendered=self.vertices self._posRendered=self.pos elif self.units in ['deg', 'degs']: self._verticesRendered=deg2pix(self.vertices, self.win.monitor) self._posRendered=deg2pix(self.pos, self.win.monitor) elif self.units=='cm': self._verticesRendered=cm2pix(self.vertices, self.win.monitor) self._posRendered=cm2pix(self.pos, self.win.monitor) self._verticesRendered = self._verticesRendered * self.size
def setHeight(self,height, log=True): """Set the height of the letters (including the entire box that surrounds the letters in the font). The width of the letters is then defined by the font. """ #height in pix (needs to be done after units) if self.units=='cm': if height==None: self.height = 1.0#default text height else: self.height = height self.heightPix = cm2pix(self.height, self.win.monitor) elif self.units in ['deg', 'degs']: if height==None: self.height = 1.0 else: self.height = height self.heightPix = deg2pix(self.height, self.win.monitor) elif self.units=='norm': if height==None: self.height = 0.1 else: self.height = height self.heightPix = self.height*self.win.size[1]/2 elif self.units=='height': if height==None: self.height = 0.2 else: self.height = height self.heightPix = self.height*self.win.size[1] else: #treat units as pix if height==None: self.height = 20 else: self.height = height self.heightPix = self.height #need to update the font to reflect the change self.setFont(self.fontname, log=False) if log and self.autoLog: self.win.logOnFlip("Set %s height=%.2f" %(self.name, height), level=logging.EXP,obj=self)
def sac_detect(self, x, y, radius): """ Checks if current gaze position is outside a circular interest area. :param x: X coordinate in degrees visual angle for center of circle IA. :type x: float or int :param y: Y coordinate in degrees visual angle for center of circle IA. :type y: float or int :param radius: Radius of detection circle in degrees visual angle. :type radius: float or int :return: List of whether saccade was detected and the gaze coordinates at time of detection. In the form of [bool, [x, y]]. :rtype: bool, list """ # Convert coordinates to Eyelink space elx, ely = self.convert_coords(x, y) elsr = deg2pix(radius, self.win.monitor) if self.realconnect: # Get current gaze position gaze = self.get_gaze() # Calculate distance of gaze from circle center gdist = ((gaze[0] - elx)**2) + ((gaze[1] - ely)**2) # Compare to radius outcirc = gdist > (elsr**2) # Convert gaze to psychopy units gaze = self.convert_coords(gaze[0], gaze[1], to='psychopy') return outcirc, gaze
def _windowUnitsToPix(self, pos): """Convert user specified window units to 'pix'. This method is the inverse of `_pixToWindowUnits`. Parameters ---------- pos : ArrayLike Position `(x, y)` in 'pix' coordinates to convert. Returns ------- ndarray Position `(x, y)` in window units. """ pos = np.asarray(pos, dtype=np.float32) if self.win is None: return pos if self.win.units == 'pix': return pos elif self.win.units == 'norm': return pos * self.win.size / 2.0 elif self.win.units == 'cm': return cm2pix(pos, self.win.monitor) elif self.win.units == 'deg': return deg2pix(pos, self.win.monitor) elif self.win.units == 'height': return pos * float(self.win.size[1])
def contains(self, x, y=None): """Determines if a point x,y is inside the extent of the stimulus. Can accept: a) two args, x and y; b) one arg, as a point (x,y) that is list-like; or c) an object with a getPos() method that returns x,y, such as a mouse. Returns True if the point is within the area defined by `vertices`. This handles complex shapes, including concavities and self-crossings. Note that, if your stimulus uses a mask (such as a Gaussian blob) then this is not accounted for by the `contains` method; the extent of the stmulus is determined purely by the size, pos and orientation settings (and by the vertices for shape stimuli). See coder demo, shapeContains.py """ if self.needVertexUpdate: self._calcVerticesRendered() if hasattr(x, 'getPos'): x, y = x.getPos() elif type(x) in [list, tuple, numpy.ndarray]: x, y = x[0], x[1] if self.units in ['deg','degs']: x, y = deg2pix(numpy.array((x, y)), self.win.monitor) elif self.units == 'cm': x, y = cm2pix(numpy.array((x, y)), self.win.monitor) if self.ori: oriRadians = numpy.radians(self.ori) sinOri = numpy.sin(oriRadians) cosOri = numpy.cos(oriRadians) x0, y0 = x-self._posRendered[0], y-self._posRendered[1] x = x0 * cosOri - y0 * sinOri + self._posRendered[0] y = x0 * sinOri + y0 * cosOri + self._posRendered[1] return pointInPolygon(x, y, self)
def setHeight(self, height, log=True): """Set the height of the letters (including the entire box that surrounds the letters in the font). The width of the letters is then defined by the font. """ #height in pix (needs to be done after units) if self.units == 'cm': if height == None: self.height = 1.0 #default text height else: self.height = height self.heightPix = cm2pix(self.height, self.win.monitor) elif self.units in ['deg', 'degs']: if height == None: self.height = 1.0 else: self.height = height self.heightPix = deg2pix(self.height, self.win.monitor) elif self.units == 'norm': if height == None: self.height = 0.1 else: self.height = height self.heightPix = self.height * self.win.size[1] / 2 elif self.units == 'height': if height == None: self.height = 0.2 else: self.height = height self.heightPix = self.height * self.win.size[1] else: #treat units as pix if height == None: self.height = 20 else: self.height = height self.heightPix = self.height #need to update the font to reflect the change self.setFont(self.fontname, log=False) if log and self.autoLog: self.win.logOnFlip("Set %s height=%.2f" % (self.name, height), level=logging.EXP, obj=self)
def _windowUnits2pix(self, win_handle, pos): win = self._iohub_server._psychopy_windows.get(win_handle) win_units = win['units'] monitor = win['monitor'] pos = np.asarray(pos) if win_units == 'pix': return pos elif win_units == 'norm': return pos * win['size'] / 2.0 elif win_units == 'cm': if monitor: return cm2pix(pos, monitor['monitor']) else: # should raise exception? print2err( "iohub Mouse error: Window is using units %s but has no Monitor definition." % win_units) elif win_units == 'deg': if monitor: return deg2pix(pos, monitor['monitor']) else: # should raise exception print2err( "iohub Mouse error: Window is using units %s but has no Monitor definition." % win_units) elif win_units == 'height': return pos * float(win['size'][1])
def _calcSizeRendered(self): """Calculate the size of the stimulus in coords of the :class:`~psychopy.visual.Window` (normalised or pixels)""" if self.units in ['norm','pix', 'height']: self._sizeRendered=self.size elif self.units in ['deg', 'degs']: self._sizeRendered=deg2pix(self.size, self.win.monitor) elif self.units=='cm': self._sizeRendered=cm2pix(self.size, self.win.monitor) else: logging.ERROR("Stimulus units should be 'height', 'norm', 'deg', 'cm' or 'pix', not '%s'" %self.units)
def deg2tobii(pos, mon): ''' Converts from degrees to Tobiis coordinate system [0, 1]. Note that the Tobii coordinate system start in the upper left corner and the PsychoPy coordinate system in the center ''' # First convert from deg to pixels pos[:, 0] = deg2pix(pos[:, 0], mon, correctFlat=False) pos[:, 1] = deg2pix(pos[:, 1], mon, correctFlat=False) # Then normalize data -1,1 pos[:, 0] = pos[:, 0] / float(mon.getSizePix()[0])/2 pos[:, 1] = pos[:, 1] / float(mon.getSizePix()[1])/2 #.. finally shift to tobii coordinate system return norm2tobii(pos)
def _calcSizeRendered(self): """DEPRECATED in 1.80.00. This funtionality is now handled by _updateVertices() and verticesPix""" #raise DeprecationWarning, "_calcSizeRendered() was deprecated in 1.80.00. This funtionality is nowhanded by _updateVertices() and verticesPix" if self.units in ['norm','pix', 'height']: self._sizeRendered=copy.copy(self.size) elif self.units in ['deg', 'degs']: self._sizeRendered=deg2pix(self.size, self.win.monitor) elif self.units=='cm': self._sizeRendered=cm2pix(self.size, self.win.monitor) else: logging.ERROR("Stimulus units should be 'height', 'norm', 'deg', 'cm' or 'pix', not '%s'" %self.units)
def _calcPosRendered(self): """DEPRECATED in 1.80.00. This funtionality is now handled by _updateVertices() and verticesPix""" #raise DeprecationWarning, "_calcSizeRendered() was deprecated in 1.80.00. This funtionality is now handled by _updateVertices() and verticesPix" if self.units in ['norm', 'pix', 'height']: self._posRendered = copy.copy(self.pos) elif self.units in ['deg', 'degs']: self._posRendered = deg2pix(self.pos, self.win.monitor) elif self.units == 'cm': self._posRendered = cm2pix(self.pos, self.win.monitor)
def __init__(self, win, actors, duration_frames, attn_video_size): """ Initialize class :param win: window to use :param actors: actors to show :param duration_frames: how long to show (in frames) """ self.win = win self.actors = actors self.duration_frames = duration_frames self.left_roi=visual.Rect(self.win, width=550, height=750, units='pix') self.left_roi.pos=[deg2pix(-12,win.monitor),deg2pix(0,win.monitor)] self.left_roi.lineColor = [1, -1, -1] self.left_roi.lineWidth = 10 self.right_roi=visual.Rect(self.win, width=550, height=750, units='pix') self.right_roi.pos=[deg2pix(12,win.monitor),deg2pix(0,win.monitor)] self.right_roi.lineColor = [1, -1, -1] self.right_roi.lineWidth = 10 self.attn_video=MovieStimulus(self.win, '', '', 'attn.mpg', attn_video_size)
def _windowUnits2pix(self, pos): if self.win.units == "pix": return pos elif self.win.units == "norm": return pos * self.win.size / 2.0 elif self.win.units == "cm": return cm2pix(pos, self.win.monitor) elif self.win.units == "deg": return deg2pix(pos, self.win.monitor) elif self.win.units == "height": return pos * float(self.win.size[1])
def _windowUnits2pix(self, pos): if self.win.units == 'pix': return pos elif self.win.units == 'norm': return pos * self.win.size / 2.0 elif self.win.units == 'cm': return cm2pix(pos, self.win.monitor) elif self.win.units == 'deg': return deg2pix(pos, self.win.monitor) elif self.win.units == 'height': return pos * float(self.win.size[1])
def _calcPosRendered(self): """Calculate the pos of the stimulus in pixels""" if self.units == 'pix': self._posRendered = self.pos elif self.units == 'cm': self._posRendered = cm2pix(self.pos, self.win.monitor) elif self.units =='deg': self._posRendered = deg2pix(self.pos, self.win.monitor) elif self.units == 'norm': self._posRendered = self.pos * self.win.size/2.0 elif self.units == 'height': self._posRendered = self.pos * self.win.size[1]
def _calcPosRendered(self): """Calculate the pos of the stimulus in pixels""" if self.units == 'pix': self._posRendered = self.pos elif self.units == 'cm': self._posRendered = cm2pix(self.pos, self.win.monitor) elif self.units == 'deg': self._posRendered = deg2pix(self.pos, self.win.monitor) elif self.units == 'norm': self._posRendered = self.pos * self.win.size / 2.0 elif self.units == 'height': self._posRendered = self.pos * self.win.size[1]
def convert_coords(self, x, y, to='eyelink'): """ Converts from degrees visual angle units to EyeLink Pixel units. :param x: X coordinate in visual angle. :type x: float or int :param y: Y coordinate in viusal angle. :type y: float or int :param to: Direction of conversion. Options: 'eyelink' or 'psychopy'. :return: Two values in order x, y """ if to == 'eyelink': # Convert coordinates to Eyelink space elx = deg2pix(x, self.win.monitor) + self.scenter[0] ely = -(deg2pix(y, self.win.monitor) - self.scenter[1]) elif to == 'psychopy': elx = pix2deg(x - self.scenter[0], self.win.monitor) ely = pix2deg(-(y - self.scenter[1]), self.win.monitor) return [elx, ely]
def _calcSizeRendered(self): """DEPRECATED in 1.80.00. This funtionality is now handled by _updateVertices() and verticesPix""" #raise DeprecationWarning, "_calcSizeRendered() was deprecated in 1.80.00. This funtionality is nowhanded by _updateVertices() and verticesPix" if self.units in ['norm', 'pix', 'height']: self._sizeRendered = copy.copy(self.size) elif self.units in ['deg', 'degs']: self._sizeRendered = deg2pix(self.size, self.win.monitor) elif self.units == 'cm': self._sizeRendered = cm2pix(self.size, self.win.monitor) else: logging.ERROR( "Stimulus units should be 'height', 'norm', 'deg', 'cm' or 'pix', not '%s'" % self.units)
def _get_tobii_pos(self, p, units=None): """Convert PsychoPy coordinates to Tobii ADCS coordinates. Args: p: Gaze position (x, y) in PsychoPy coordinate systems. units: The PsychoPy coordinate system of p. Returns: Gaze position in Tobii ADCS. For example: (0,0). """ if units is None: units = self.win.units if units == "norm": return (p[0] / 2 + 0.5, p[1] / -2 + 0.5) elif units == "height": return (p[0] * (self.win.size[1] / self.win.size[0]) + 0.5, -p[1] + 0.5) elif units == "pix": return self._pix2tobii(p) elif units in ["cm", "deg", "degFlat", "degFlatPos"]: if units == "cm": p_pix = (cm2pix(p[0], self.win.monitor), cm2pix(p[1], self.win.monitor)) elif units == "deg": p_pix = ( deg2pix(p[0], self.win.monitor), deg2pix(p[1], self.win.monitor), ) elif units in ["degFlat", "degFlatPos"]: p_pix = deg2pix(np.array(p), self.win.monitor, correctFlat=True) p_pix = tuple(round(pos) for pos in p_pix) return self._pix2tobii(p_pix) else: raise ValueError("unit ({}) is not supported".format(units))
def fix_check(self, size, ftime, button): """ Checks that fixation is maintained for certain time. :param size: Length of one side of box in degrees visual angle. :type size: float or int :param ftime: Length of time to check for fixation in seconds. :type ftime: float :param button: Key to press to recalibrate eye-tracker. :type button: char """ # Calculate Fix check borders size = deg2pix(size, self.win.monitor) / 2.0 xbdr = [self.scenter[0] - size, self.scenter[0] + size] ybdr = [self.scenter[1] - size, self.scenter[1] + size] # Set status message & Draw box self.set_status('Fixation Check') bxmsg = 'draw_box {} {} {} {} 1'.format(xbdr[0], ybdr[0], xbdr[1], ybdr[1]) self.tracker.sendCommand(bxmsg) # Begin recording self.tracker.startRecording(0, 0, 1, 1) # Begin polling fixtime = time.clock() while self.realconnect: # only start check loop if real connection # Check for recalibration button keys = event.getKeys(button) if keys: self.tracker.stopRecording() self.calibrate() break gaze = self.get_gaze() # Are we in the box? if xbdr[0] < gaze[0] < xbdr[1] and ybdr[0] < gaze[1] < ybdr[1]: # Have we been in the box long enough? if (time.clock() - fixtime) > ftime: self.tracker.stopRecording() break else: # Reset clock if not in box fixtime = time.clock()
def _addRuntimeInfoToDisplayConfig(self): if self not in Display._enabled_display_instances: Display._enabled_display_instances.append(self) display_config = self.getConfiguration() runtime_info = display_config.get("runtime_info", None) if runtime_info is None: runtime_info = self._getRuntimeInfoByIndex(self.device_number) display_config["runtime_info"] = runtime_info self._createPsychopyCalibrationFile() pixel_width = runtime_info["pixel_width"] pixel_height = runtime_info["pixel_height"] phys_width = display_config["physical_dimensions"]["width"] phys_height = display_config["physical_dimensions"]["height"] phys_unit_type = display_config["physical_dimensions"]["unit_type"] # add pixels_per_degree to runtime info ppd_x = deg2pix( 1.0, self._psychopy_monitor ) # math.tan(math.radians(0.5))*2.0*viewing_distance*pixel_width/phys_width ppd_y = deg2pix( 1.0, self._psychopy_monitor ) # math.tan(math.radians(0.5))*2.0*viewing_distance*pixel_height/phys_height runtime_info["pixels_per_degree"] = ppd_x, ppd_y self._calculateCoordMappingFunctions(pixel_width, pixel_height, phys_unit_type, phys_width, phys_height) left, top, right, bottom = runtime_info["bounds"] coord_left, coord_top = self._pixel2DisplayCoord(left, top, self.device_number) coord_right, coord_bottom = self._pixel2DisplayCoord(right, bottom, self.device_number) runtime_info["coordinate_bounds"] = coord_left, coord_top, coord_right, coord_bottom
def drawIA(self, x, y, size, index, color, name): """ Draws square interest area in EDF and a corresponding filled box on eye-tracker display. :param x: X coordinate in degrees visual angle for center of check area. :type x: float or int :param y: Y coordinate in degrees visual angle for center of check area. :type y: float or int :param size: length of one edge of square in degrees visual angle. :type size: float or int :param index: number to assign interest area in EDF :type index: int :param color: color of box drawn on eye-tracker display (0 - 15) :type color: int :param name: Name interest area in EDF :type name: str """ # Convert units to eyelink space elx = deg2pix(x, self.win.monitor) + (self.sres[0] / 2.0) ely = -(deg2pix(y, self.win.monitor) - (self.sres[1] / 2.0)) elsz = deg2pix(size, self.win.monitor) / 2.0 # Make top left / bottom right coordinates for square tplf = map(round, [elx - elsz, ely - elsz]) btrh = map(round, [elx + elsz, ely + elsz]) # Construct command strings flist = [index, name, color] + tplf + btrh iamsg = '!V IAREA RECTANGLE {0} {3} {4} {5} {6} {1}'.format(*flist) bxmsg = 'draw_filled_box {3} {4} {5} {6} {2}'.format(*flist) # Send commands self.tracker.sendMessage(iamsg) self.tracker.sendCommand(bxmsg)
def __init__(self, win, balloonID, color, maxPumps, gainpoints, initsize, gainsize, mon): self.win = win self.balloonID = balloonID self.color = color self.maxPumps = maxPumps self.points = gainsize self.initSize = initsize # units 'deg' self.initSizeX = 3.5 self.initSizeY = 5 self.gainSize = gainsize # units 'deg' self.mon = mon self.pump = visual.ImageStim(win, image="assets/pump.png", pos=[0, 0], size=[10, 10]) self.explosionSound = sound.Sound(value='assets\explode.wav') self.explosionSound.setVolume(1.0) self.pumpSound = sound.Sound(value='assets\pump4.wav') self.pumpSound.setVolume(1.0) self.pump.pos = [0, pix2deg(-690 + (deg2pix(self.pump.size[1], mon) / 2), mon)] self.stimuli = visual.ImageStim(win, image="assets/bal_blue.png", pos=[0,pix2deg(-400+(deg2pix(self.initSizeY,mon)/2),mon)],size=[self.initSizeX,self.initSizeY])
def draw_ia(self, x, y, size, index, color, name): """ Draws square interest area in EDF and a corresponding filled box on eye-tracker display. Must be called after :py:func:`set_trialid` for interest areas to appear in the EDF. :param x: X coordinate in degrees visual angle for center of check area. :type x: float or int :param y: Y coordinate in degrees visual angle for center of check area. :type y: float or int :param size: length of one edge of square in degrees visual angle. :type size: float or int :param index: number to assign interest area in EDF :type index: int :param color: color of box drawn on eye-tracker display (0 - 15) :type color: int :param name: Name of interest area in EDF :type name: str """ # Convert units to eyelink space elx, ely = self.convert_coords(x, y) elsz = deg2pix(size, self.win.monitor) / 2.0 # Make top left / bottom right coordinates for square tplf = map(round, [elx - elsz, ely - elsz]) btrh = map(round, [elx + elsz, ely + elsz]) # Construct command strings flist = [index, name, color] + tplf + btrh iamsg = '!V IAREA RECTANGLE {0} {3} {4} {5} {6} {1}'.format(*flist) bxmsg = 'draw_filled_box {3} {4} {5} {6} {2}'.format(*flist) # Send commands self.tracker.sendMessage(iamsg) self.tracker.sendCommand(bxmsg)
def _calcXYsRendered(self): if self.units in ['norm','pix','height']: self._XYsRendered=self.xys elif self.units in ['deg', 'degs']: self._XYsRendered=deg2pix(self.xys, self.win.monitor) elif self.units=='cm': self._XYsRendered=cm2pix(self.xys, self.win.monitor)
def degcoord2pix(self, degx, degy, display_index=None): if display_index == self.getIndex(): return psychopy2displayPix( deg2pix(degx, self._psychopy_monitor), cm2pix(degy, self._psychopy_monitor) ) return degx, degy
def fix_check_locs(self, xcenters, ycenters, size, ftime, button): """ Like fix_check, check but checks multiple boxes, returns the fixated box number. Checks that fixation is maintained for 'ftime'. :param size: Length of one side of box in degrees visual angle. :type size: float or int :param ftime: Length of time to check for fixation in seconds. :type ftime: float :param button: Key to press to recalibrate eye-tracker. :type button: char """ # Calculate Fix check borders size = deg2pix(size, self.win.monitor) / 2.0 mBoundaries = _np.zeros((len(xcenters), 4)) for i, (x, y) in enumerate(zip(xcenters, ycenters)): mBoundaries[i, :] = [x - size, x + size] + [y - size, y + size] # Set status message & Draw box self.set_status('Fixation Check') bxmsg = 'draw_box {} {} {} {} 1'.format(xbdr[0], ybdr[0], xbdr[1], ybdr[1]) self.tracker.sendCommand(bxmsg) # Begin recording self.tracker.startRecording(0, 0, 1, 1) # Begin polling fixtime = time.clock() while self.realconnect: # only start check loop if real connection # Check for recalibration button keys = event.getKeys(button) if keys: self.tracker.stopRecording() self.calibrate() break gaze = self.get_gaze() # what a box? box, inBox = 0, False for x0, x1, y0, y1 in mBoundaries: if x0 < gaze[0] < x1 and y0 < gaze[1] < y1: inBox = True box += 1 break # long enough? if inBox: if (time.clock() - fixtime) > ftime: self.tracker.stopRecording() break else: # Reset clock if not in box fixtime = time.clock() return box
def doCalibration(self, calibrationPoints=[(0.5, 0.5), (0.1, 0.9), (0.1, 0.1), (0.9, 0.9), (0.9, 0.1)], calinRadius=2.0, caloutRadius=None, moveFrames=60): if self.eyetracker is None: return # set default if caloutRadius is None: caloutRadius = calinRadius * 20.0 if calibrationPoints is None: calibrationPoints = [(0.5, 0.5), (0.1, 0.9), (0.1, 0.1), (0.9, 0.9), (0.9, 0.1)] self.points = np.random.permutation(calibrationPoints) # Make the "outer" circle self.calout = psychopy.visual.Circle(self.win, radius=caloutRadius, lineColor=(0, 1.0, 0), fillColor=(0.5, 1.0, 0.5), units='pix', autoDraw=True, pos=self.acsd2pix(self.points[-1])) # Make a dummy message self.calmsg = psychopy.visual.TextStim(self.win, color=0.0, units='norm', height=0.07, pos=(0.0, -0.5)) # Put the eye tracker into the calibration state self.initcalibration_completed = False print "Start new calibration..." self.eyetracker.StartCalibration(callback=self.on_calib_start) while not self.initcalibration_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") self.deletecalibration_completed = False # Clear out previous calibrations (tobii scanners # sometimes store these across many sessions) print "Delete old calibration..." self.eyetracker.ClearCalibration(callback=self.on_calib_deleted) while not self.deletecalibration_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") # Draw instructions and wait for space key self.calmsg.text = ("Please focus your eyes on the green dot, and " "follow it with your eyes when it moves. This " "will help us calibrate the eye tracker.\n" "Press space when you're ready.") self.calmsg.draw() self.win.flip() psychopy.event.waitKeys(keyList=['space']) # Go through the calibration points for self.point_index in range(len(self.points)): # The dot starts at the previous point self.calout.pos = \ self.acsd2pix((self.points[self.point_index - 1][0], self.points[self.point_index - 1][1])) # The steps for the movement is new - old divided by frames self.step = (self.acsd2pix((self.points[self.point_index][0], self.points[self.point_index][1])) - self.calout.pos) / moveFrames # Create a tobii 2D class p = Point2D() # Add the X and Y coordinates to the tobii point p.x, p.y = self.points[self.point_index] # Move the point in position (smooth pursuit) for frame in range(moveFrames): self.calout.pos += self.step # draw & flip self.win.flip() # Shrink the outer point (gaze fixation) for frame in range(moveFrames / 2): self.calout.radius -= (caloutRadius - calinRadius) / (moveFrames / 2) self.win.flip() # Add this point to the tobii psychopy.core.wait(1) # first wait to let the eyes settle (MIN 0.5) self.add_point_completed = False # this gets updated by callback self.eyetracker.AddCalibrationPoint(p, callback=self.on_add_completed) # While this point is being added, do nothing: while not self.add_point_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") psychopy.core.wait(0.5) # wait before continuing # Reset the radius of the large circle self.calout.radius = caloutRadius # After calibration, make sure the stimuli aren't drawn self.calout.autoDraw = False self.calout = None # The following two will be set by the tobii SDK self.computeCalibration_completed = False self.computeCalibration_succeeded = False # Do the computation self.eyetracker.ComputeCalibration(self.on_calib_compute) while not self.computeCalibration_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") self.eyetracker.StopCalibration(None) self.win.flip() # Now we retrieve the calibration data self.getcalibration_completed = False self.calib = self.eyetracker.GetCalibration(self.on_calib_response) while not self.getcalibration_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") if not self.computeCalibration_succeeded: # computeCalibration failed. self.calmsg.text = ("Not enough data was collected " "(Retry:[r] Abort:[ESC])") elif self.calib is None: # no calibration data self.calmsg.text = ("No calibration data " "(Retry:[r] Abort:[ESC])") else: # calibration seems to have worked out points = {} for data in self.calib.plot_data: points[data.true_point] = {'left': data.left, 'right': data.right} if len(points) == 0: # no points in the calibration results self.calmsg.text = ("No calibration data " "(Retry:[r] Abort:[ESC])") else: # draw the calibration result for p, d in points.iteritems(): psychopy.visual.Circle(self.win, radius=calinRadius, fillColor=(1, 1, 1), units='pix', pos=(p.x - 0.5, 0.5 - p.y)).draw() if d['left'].status == 1: psychopy.visual.Line(self.win, units='pix', lineColor='yellow', start=(self.acsd2pix((p.x, p.y))), end=(self.acsd2pix((d['left']. map_point.x, d['left']. map_point.y))) ).draw() if d['right'].status == 1: psychopy.visual.Line(self.win, units='pix', lineColor='blue', start=(self.acsd2pix((p.x, p.y))), end=(self.acsd2pix((d['right']. map_point.x, d['right']. map_point.y))) ).draw() for p in self.points: psychopy.visual.Circle(self.win, radius=calinRadius, fillColor=1, units='pix', pos=self.acsd2pix(p), ).draw() psychopy.visual.Circle(self.win, units='pix', lineColor=-0.5, radius=deg2pix(0.9, self.win.monitor), pos=self.acsd2pix(p), ).draw() self.calmsg.text = ("Accept calibration results\n" "(Accept:[a] Retry:[r] Abort:[ESC])") # Update the screen, then wait for response self.calmsg.draw() self.win.flip() self.response = psychopy.event.waitKeys(keyList=['a', 'r', 'escape']) if 'a' in self.response: retval = 'accept' elif 'r' in self.response: retval = 'retry' elif 'escape' in self.response: retval = 'abort' return retval
def _calcPosRendered(self): """DEPRECATED in 1.80.00. This funtionality is now handled by _updateVertices() and verticesPix""" #raise DeprecationWarning, "_calcSizeRendered() was deprecated in 1.80.00. This funtionality is now handled by _updateVertices() and verticesPix" if self.units in ['norm','pix', 'height']: self._posRendered= copy.copy(self.pos) elif self.units in ['deg', 'degs']: self._posRendered=deg2pix(self.pos, self.win.monitor) elif self.units=='cm': self._posRendered=cm2pix(self.pos, self.win.monitor)
mon.setWidth(52.71) # cm myWin = visual.Window([1000, 1000], units='deg', monitor=mon, color=(-1, -1, -1), checkTiming=True) fps = myWin.getActualFrameRate() # sometimes this call fails... event.globalKeys.clear() event.globalKeys.add(key='q', func=core.quit) # global quit globalClock = clock.Clock() kb = keyboard.Keyboard() # create kb object # let's do some calculation before going further speedFrame = speedDeg / fps # how many deg/frame speedPixFrame = monitorunittools.deg2pix(speedFrame, mon) dotSizePix = monitorunittools.deg2pix( dotSize, mon) # calculate dotSizePix for DotStim object nDots = round(np.pi * fieldRadius**2 * dotDensity) # calcuate nDots maxFrames = int(round(maxDur / myWin.monitorFramePeriod)) # define trial handler stimList = [] for t in dirRange: for d in stimDir: stimList.append({'dirRange': t, 'direction': d}) trials = data.TrialHandler(trialList=stimList, nReps=nTrialsPerCond) # ====== define stimulus components ======= # define fixation fixation = circle.Circle(win=myWin,
def _calcDotsXYRendered(self): if self.units in ['norm','pix', 'height']: self._dotsXYRendered=self._dotsXY elif self.units in ['deg','degs']: self._dotsXYRendered=deg2pix(self._dotsXY, self.win.monitor) elif self.units=='cm': self._dotsXYRendered=cm2pix(self._dotsXY, self.win.monitor)
def fixCheck(self, size, ftime, button): '''Checks that fixation is maintained for certain time. Parameters size -- length of one side of box in degrees visual angle ftime -- length of time to check for fixation in seconds button -- key to press to recalibrate eye-tracker ''' # Calculate Fix check borders cenX = self.sres[0] / 2.0 cenY = self.sres[1] / 2.0 size = deg2pix(size, self.win.monitor) / 2.0 xbdr = [cenX - size, cenX + size] ybdr = [cenY - size, cenY + size] # Set status message & Draw box self.setStatus('Fixation Check') bxmsg = 'draw_box {} {} {} {} 1'.format(xbdr[0], ybdr[0], xbdr[1], ybdr[1]) self.tracker.sendCommand(bxmsg) # Begin recording self.tracker.startRecording(0, 0, 1, 1) # Check which eye is being recorded eye_used = self.tracker.eyeAvailable() RIGHT_EYE = 1 LEFT_EYE = 0 # Begin polling keys = [] fixtime = time.clock() while self.realconnect: # only start check loop if real connection # Check for recalibration button keys = event.getKeys(button) if keys: self.tracker.stopRecording() self.calibrate() break # Grab latest sample sample = self.tracker.getNewestSample() # Extract gaze coordinates if eye_used == RIGHT_EYE: gaze = sample.getRightEye().getGaze() else: gaze = sample.getLeftEye().getGaze() # Are we in the box? if xbdr[0] < gaze[0] < xbdr[1] and ybdr[0] < gaze[1] < ybdr[1]: # Have we been in the box long enough? if (time.clock() - fixtime) > ftime: self.tracker.stopRecording() break else: # Reset clock if not in box fixtime = time.clock()
def updateBalloonPos(self): return [0, pix2deg(-400 + (deg2pix(self.stimuli.size[1], self.mon) / 2), self.mon)]
def fixCheck(self, size, ftime, button): """ Checks that fixation is maintained for certain time. :param size: Length of one side of box in degrees visual angle. :type size: float or int :param ftime: Length of time to check for fixation in seconds. :type ftime: int :param button: Key to press to recalibrate eye-tracker. :type button: char """ # Calculate Fix check borders cenX = self.sres[0] / 2.0 cenY = self.sres[1] / 2.0 size = deg2pix(size, self.win.monitor) / 2.0 xbdr = [cenX - size, cenX + size] ybdr = [cenY - size, cenY + size] # Set status message & Draw box self.setStatus('Fixation Check') bxmsg = 'draw_box {} {} {} {} 1'.format(xbdr[0], ybdr[0], xbdr[1], ybdr[1]) self.tracker.sendCommand(bxmsg) # Begin recording self.tracker.startRecording(0, 0, 1, 1) # Check which eye is being recorded eye_used = self.tracker.eyeAvailable() RIGHT_EYE = 1 LEFT_EYE = 0 # Begin polling keys = [] fixtime = time.clock() while self.realconnect: # only start check loop if real connection # Check for recalibration button keys = event.getKeys(button) if keys: self.tracker.stopRecording() self.calibrate() break # Grab latest sample sample = self.tracker.getNewestSample() # Extract gaze coordinates if eye_used == RIGHT_EYE: gaze = sample.getRightEye().getGaze() else: gaze = sample.getLeftEye().getGaze() # Are we in the box? if xbdr[0] < gaze[0] < xbdr[1] and ybdr[0] < gaze[1] < ybdr[1]: # Have we been in the box long enough? if (time.clock() - fixtime) > ftime: self.tracker.stopRecording() break else: # Reset clock if not in box fixtime = time.clock()
def __init__(self, win, text="Hello World", font="", pos=(0.0, 0.0), depth=0, rgb=None, color=(1.0, 1.0, 1.0), colorSpace='rgb', opacity=1.0, contrast=1.0, units="", ori=0.0, height=None, antialias=True, bold=False, italic=False, alignHoriz='center', alignVert='center', fontFiles=[], wrapWidth=None, flipHoriz=False, flipVert=False, name='', autoLog=True): """ :Parameters: win: A :class:`Window` object. Required - the stimulus must know where to draw itself text: The text to be rendered height: Height of the characters (including the ascent of the letter and the descent) antialias: boolean to allow (or not) antialiasing the text bold: Make the text bold (better to use a bold font name) italic: Make the text italic (better to use an actual italic font) alignHoriz: The horizontal alignment ('left', 'right' or 'center') alignVert: The vertical alignment ('top', 'bottom' or 'center') fontFiles: A list of additional files if the font is not in the standard system location (include the full path) wrapWidth: The width the text should run before wrapping flipHoriz : boolean Mirror-reverse the text in the left-right direction flipVert : boolean Mirror-reverse the text in the up-down direction """ #what local vars are defined (these are the init params) for use by __repr__ self._initParams = dir() self._initParams.remove('self') BaseVisualStim.__init__(self, win, units=units, name=name, autoLog=False) self.useShaders = win._haveShaders #use shaders if available by default, this is a good thing self._needUpdate = True self.alignHoriz = alignHoriz self.alignVert = alignVert self.antialias = antialias self.bold = bold self.italic = italic self.text = '' #NB just a placeholder - real value set below self.depth = depth self.ori = ori self.wrapWidth = wrapWidth self.flipHoriz = flipHoriz self.flipVert = flipVert self._pygletTextObj = None self.pos = numpy.array(pos, float) #height in pix (needs to be done after units which is done during _Base.__init__) if self.units == 'cm': if height == None: self.height = 1.0 #default text height else: self.height = height self.heightPix = cm2pix(self.height, win.monitor) elif self.units in ['deg', 'degs']: if height == None: self.height = 1.0 else: self.height = height self.heightPix = deg2pix(self.height, win.monitor) elif self.units == 'norm': if height == None: self.height = 0.1 else: self.height = height self.heightPix = self.height * win.size[1] / 2 elif self.units == 'height': if height == None: self.height = 0.2 else: self.height = height self.heightPix = self.height * win.size[1] else: #treat units as pix if height == None: self.height = 20 else: self.height = height self.heightPix = self.height if self.wrapWidth == None: if self.units in ['height', 'norm']: self.wrapWidth = 1 elif self.units in ['deg', 'degs']: self.wrapWidth = 15 elif self.units == 'cm': self.wrapWidth = 15 elif self.units in ['pix', 'pixels']: self.wrapWidth = 500 if self.units == 'norm': self._wrapWidthPix = self.wrapWidth * win.size[0] / 2 elif self.units == 'height': self._wrapWidthPix = self.wrapWidth * win.size[0] elif self.units in ['deg', 'degs']: self._wrapWidthPix = deg2pix(self.wrapWidth, win.monitor) elif self.units == 'cm': self._wrapWidthPix = cm2pix(self.wrapWidth, win.monitor) elif self.units in ['pix', 'pixels']: self._wrapWidthPix = self.wrapWidth #generate the texture and list holders self._listID = GL.glGenLists(1) if not self.win.winType == "pyglet": #pygame text needs a surface to render to self._texID = GL.GLuint() GL.glGenTextures(1, ctypes.byref(self._texID)) self.colorSpace = colorSpace if rgb != None: logging.warning( "Use of rgb arguments to stimuli are deprecated. Please use color and colorSpace args instead" ) self.setColor(rgb, colorSpace='rgb', log=False) else: self.setColor(color, log=False) self._calcPosRendered() for thisFont in fontFiles: pyglet.font.add_file(thisFont) self.setFont(font, log=False) self.opacity = float(opacity) self.contrast = float(contrast) self.setText( text, log=False ) #self.width and self.height get set with text and calcSizeRednered is called self._needUpdate = True #set autoLog (now that params have been initialised) self.autoLog = autoLog if autoLog: logging.exp("Created %s = %s" % (self.name, str(self)))
def __init__( self, no_demographics = False, task = 'test'): if task=='main': self.sub = self.solicit_subid() demographics = self.solicit_demographics(no_demographics) iohub_config = { 'experiment_code': 'color', 'datastore_name': node(), 'session_code': task, 'experiment_info': { 'version': str(git.repo.fun.rev_parse(git.Repo(), 'HEAD'))[0:6]}, 'session_info': { 'user_variables': { 'date': datetime.now().strftime("%d-%m-%Y_%H-%M-%S"), 'sub': self.sub, 'sex': demographics[0], 'ethnicity': demographics[1], 'race': demographics[2], 'age': demographics[3]}}} else: iohub_config = {} self.sub = 0 self.trialdf = self.__prep_df(os.path.join('stimuli','design.csv')) self.io = iohub_config # Inform the ioHub server about the TrialHandler # randomization already done (hence 'sequential') self.io.createTrialHandlerRecordTable(data.TrialHandler( [x for x in (self.trialdf.T.to_dict()).values()], nReps=1, method='sequential')) self.mon = monitors.Monitor("default", distance=60.96) # create a window to draw in self.win = visual.Window( size=(1920, 1080), fullscr=True, allowGUI=False, winType='pyglet', blendMode='avg', useFBO=True, units="deg", monitor=self.mon, # gamma = [r.gamma, g.gamma, b.gamma], color='black') if task == 'main': runinfo = RunTimeInfo(verbose=True, userProcsDetailed=True, win=self.win, refreshTest=True) with open(os.path.join('data-raw', f'sub-{self.sub}_task-{task}_runinfo.pkl'), 'xb') as f:pickle.dump(runinfo, f) self.fix = visual.Circle(win=self.win, radius=self.fix_radius, size=1, fillColor="white") # cues for color self.triangle = visual.Polygon(win=self.win, radius=self.radius, lineColor="gray", lineWidth=self.linewidth) self.circle = visual.Circle(win=self.win, radius=self.radius, lineColor="gray", lineWidth=self.linewidth) # cues for direction self.line1 = visual.Line(win=self.win, lineWidth=self.linewidth, start=(-math.sqrt(3)/2, math.sqrt(3)/2), end=(math.sqrt(3)/2, -math.sqrt(3)/2), lineColor="gray", size=self.radius) self.line2 = visual.Line(win=self.win, lineWidth=self.linewidth, start=(-math.sqrt(3)/2, -math.sqrt(3)/2), end=(math.sqrt(3)/2, math.sqrt(3)/2), lineColor="gray", size=self.radius) self.fleur = visual.ShapeStim(win=self.win, size=self.radius, lineColor="gray", lineWidth=self.linewidth, vertices=self.__make_vertices()) self.dots = WrappedDot( win=self.win, units='deg', fieldShape="circle", dotSize=deg2pix(self.dotsize_deg, self.mon), dotLife=-1, coherence=self.dot_coherence, nDots=self.ndots, fieldSize=self.dotfield_diameter_deg, speed=self.dot_speed_deg_per_sec / self.refresh_rate) self.correct = TextStim(self.win, text = "CORRECT", color="green") self.incorrect = TextStim(self.win, text = "INCORRECT", color="red") self.welcome = TextStim(self.win, text= ''' welcome to the experiment Press "c" to continue ''', wrapWidth=50, alignText="left") self.rest = TextStim( self.win, pos=(0, 6), alignText="left", wrapWidth=50) self.waiter = clock.StaticPeriod(screenHz=self.refresh_rate)
## create files filename = data_dir + os.sep + '%s_%s' % (expInfo['participant'], expInfo['date']) filename_short = data_dir + os.sep + expInfo['participant'] output = "subject_id,trial,coherence_trial,pressed_rdk,expected_rdk,rt_rdk, accuracy_rdk, dots_coord\n" running_filename = filename_short + '_TEMP.txt' ## * Hardware ##def monitor mon1 = monitors.Monitor('testMonitor') mon1.setDistance(50) #cm mon1.setWidth(30) #cm mon1.setSizePix([800, 600]) mon1.saveMon() ppd = int(deg2pix(1, mon1, correctFlat=False)) ## ppd for monitor= mon1 ## keyboard kb = keyboard.Keyboard() # initialize keyboards ## * Stimuli #create a window to draw in win = visual.Window(fullscr=False, size=(800, 600), monitor=mon1, color=[0, 0, 0], colorSpace='rgb255', units='deg') win.setMouseVisible(False) #hide the mouse cursor!!! ## Instructions
def __init__(self, params=None, reportobj=None, subroutines=[]): logging.LogFile(params['cwd'] + os.sep + params['logfile'], level=logging.INFO, filemode='w') logging.LogFile(level=logging.ERROR) logging.info('Using Python ' + sys.version) self.args = sys.argv self.params = self.update_params(self.args, params) self.win = None self.model = None self.routines = subroutines self.thisExp = None self.name = params['name'] self.cwd = params['cwd'] self.reportobj = reportobj if params['monitor'] not in monitors.getAllMonitors(): logging.error('monitor not found: ' + params.monitor) logging.info('available monitors: ' + ' '.join(monitors.getAllMonitors())) logging.info('Define monitor in monitor center.') logging.info('To launch monitor center, type:\n' 'python -m psychopy.monitors.MonitorCenter') core.quit() else: self.monitor = monitors.Monitor( '', width=None, distance=self.params['viewing_distance'], gamma=None, notes=None, useBits=None, verbose=True, currentCalib=None, autoLog=True) self.monitor.setSizePix((1920, 1280)) self.monitor.setWidth(54.15) logging.exp('using monitor: ' + self.params['monitor'] + ' with viewing_distance=' + str(self.params['viewing_distance']) + ' cm\n' + ' resolution (pixel/deg): ' + str(deg2pix(1, self.monitor))) # TODO: change screen_pixel_size back to calculation from monitor # TODO: change the monitor definition width so that screen_pixel_size=0.282 if 'screen_pixel_size' not in self.params['model']: self.params['model']['screen_pixel_size'] = pix2cm( 1, self.monitor) * 10.0 #self.params['model']['screen_pixel_size'] = 0.282 self.params['model'][ 'viewing_distance'] = self.params['viewing_distance'] / 2.54 self.expInfo = params['exp_info'] self.expInfo['date'] = data.getDateStr() # Ensure that relative paths start from the same directory as this script _thisDir = os.path.dirname(os.path.abspath(__file__)) os.chdir(_thisDir) # Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc runsubject = self.params['runsubject'] if runsubject: self.filename = self.cwd + os.sep + u'data' + os.sep + '%s_%s_%s' % ( self.expInfo['participant'], params['name'], self.expInfo['date']) else: self.filename = self.cwd + os.sep + u'data' + os.sep + 'model-' + params[ 'expName']
def _calcPosRendered(self): """Calculate the pos of the stimulus in coords of the :class:`~psychopy.visual.Window` (normalised or pixels)""" if self.units in ['pix', 'pixels', 'height', 'norm']: self._posRendered=self.pos elif self.units in ['deg', 'degs']: self._posRendered=deg2pix(self.pos, self.win.monitor) elif self.units=='cm': self._posRendered=cm2pix(self.pos, self.win.monitor)
def doCalibration(self, calibrationPoints=[(0.5, 0.5), (0.1, 0.9), (0.1, 0.1), (0.9, 0.9), (0.9, 0.1)], calinRadius=2.0, caloutRadius=None, moveFrames=60): if self.eyetracker is None: return # set default if caloutRadius is None: caloutRadius = calinRadius * 20.0 if calibrationPoints is None: calibrationPoints = [(0.5, 0.5), (0.1, 0.9), (0.1, 0.1), (0.9, 0.9), (0.9, 0.1)] self.points = np.random.permutation(calibrationPoints) # Make the "outer" circle self.calout = psychopy.visual.Circle(self.win, radius=caloutRadius, lineColor=(0, 1.0, 0), fillColor=(0.5, 1.0, 0.5), units='pix', autoDraw=True, pos=self.acsd2pix( self.points[-1])) # Make a dummy message self.calmsg = psychopy.visual.TextStim(self.win, color=0.0, units='norm', height=0.07, pos=(0.0, -0.5)) # Put the eye tracker into the calibration state self.initcalibration_completed = False print "Start new calibration..." self.eyetracker.StartCalibration(callback=self.on_calib_start) while not self.initcalibration_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") self.deletecalibration_completed = False # Clear out previous calibrations (tobii scanners # sometimes store these across many sessions) print "Delete old calibration..." self.eyetracker.ClearCalibration(callback=self.on_calib_deleted) while not self.deletecalibration_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") # Draw instructions and wait for space key self.calmsg.text = ("Please focus your eyes on the green dot, and " "follow it with your eyes when it moves. This " "will help us calibrate the eye tracker.\n" "Press space when you're ready.") self.calmsg.draw() self.win.flip() psychopy.event.waitKeys(keyList=['space']) # Go through the calibration points for self.point_index in range(len(self.points)): # The dot starts at the previous point self.calout.pos = \ self.acsd2pix((self.points[self.point_index - 1][0], self.points[self.point_index - 1][1])) # The steps for the movement is new - old divided by frames self.step = (self.acsd2pix((self.points[self.point_index][0], self.points[self.point_index][1])) - self.calout.pos) / moveFrames # Create a tobii 2D class p = Point2D() # Add the X and Y coordinates to the tobii point p.x, p.y = self.points[self.point_index] # Move the point in position (smooth pursuit) for frame in range(moveFrames): self.calout.pos += self.step # draw & flip self.win.flip() # Shrink the outer point (gaze fixation) for frame in range(moveFrames / 2): self.calout.radius -= (caloutRadius - calinRadius) / (moveFrames / 2) self.win.flip() # Add this point to the tobii psychopy.core.wait( 1) # first wait to let the eyes settle (MIN 0.5) self.add_point_completed = False # this gets updated by callback self.eyetracker.AddCalibrationPoint(p, callback=self.on_add_completed) # While this point is being added, do nothing: while not self.add_point_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") psychopy.core.wait(0.5) # wait before continuing # Reset the radius of the large circle self.calout.radius = caloutRadius # After calibration, make sure the stimuli aren't drawn self.calout.autoDraw = False self.calout = None # The following two will be set by the tobii SDK self.computeCalibration_completed = False self.computeCalibration_succeeded = False # Do the computation self.eyetracker.ComputeCalibration(self.on_calib_compute) while not self.computeCalibration_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") self.eyetracker.StopCalibration(None) self.win.flip() # Now we retrieve the calibration data self.getcalibration_completed = False self.calib = self.eyetracker.GetCalibration(self.on_calib_response) while not self.getcalibration_completed: psychopy.core.wait(0.1) if psychopy.event.getKeys(keyList=['escape']): raise KeyboardInterrupt("You interrupted the script.") if not self.computeCalibration_succeeded: # computeCalibration failed. self.calmsg.text = ("Not enough data was collected " "(Retry:[r] Abort:[ESC])") elif self.calib is None: # no calibration data self.calmsg.text = ("No calibration data " "(Retry:[r] Abort:[ESC])") else: # calibration seems to have worked out points = {} for data in self.calib.plot_data: points[data.true_point] = { 'left': data.left, 'right': data.right } if len(points) == 0: # no points in the calibration results self.calmsg.text = ("No calibration data " "(Retry:[r] Abort:[ESC])") else: # draw the calibration result for p, d in points.iteritems(): psychopy.visual.Circle(self.win, radius=calinRadius, fillColor=(1, 1, 1), units='pix', pos=(p.x - 0.5, 0.5 - p.y)).draw() if d['left'].status == 1: psychopy.visual.Line( self.win, units='pix', lineColor='yellow', start=(self.acsd2pix((p.x, p.y))), end=(self.acsd2pix( (d['left'].map_point.x, d['left'].map_point.y)))).draw() if d['right'].status == 1: psychopy.visual.Line( self.win, units='pix', lineColor='blue', start=(self.acsd2pix((p.x, p.y))), end=(self.acsd2pix( (d['right'].map_point.x, d['right'].map_point.y)))).draw() for p in self.points: psychopy.visual.Circle( self.win, radius=calinRadius, fillColor=1, units='pix', pos=self.acsd2pix(p), ).draw() psychopy.visual.Circle( self.win, units='pix', lineColor=-0.5, radius=deg2pix(0.9, self.win.monitor), pos=self.acsd2pix(p), ).draw() self.calmsg.text = ("Accept calibration results\n" "(Accept:[a] Retry:[r] Abort:[ESC])") # Update the screen, then wait for response self.calmsg.draw() self.win.flip() self.response = psychopy.event.waitKeys(keyList=['a', 'r', 'escape']) if 'a' in self.response: retval = 'accept' elif 'r' in self.response: retval = 'retry' elif 'escape' in self.response: retval = 'abort' return retval
def run(self, runmodel=None, genstim=False, trialparams={}, params={}, loopstate={}): self.update_params(trialparams, loopstate) resp = Response() filename = '' # If we have a window then either we are generating stimuli or # running a human subject experiment if self.win is not None: for component in self.components: if genstim: filename = self.get_filename(trialparams, loopstate) component.start(trialparams, loopstate) else: component.run(trialparams, loopstate) left, top = params['model']['view_pos'] width, height = params['model']['view_size'] ################### #diff_x,diff_y = params['diff_size'] diff_x, diff_y = 1.0, 1.0 # TODO: remove all this diff_size code width_window, height_window = self.win.size center_left = int(deg2pix(left, self.win.monitor)) center_top = int(deg2pix(top, self.win.monitor)) upper_left = center_left - int(0.5 * width) upper_top = center_top + int(0.5 * height) if width % 2 == 0: # width is even number of pixels lower_right = upper_left + width lower_bottom = upper_top - height else: lower_right = upper_left + (width + 1) lower_bottom = upper_top - (height + 1) # convert to normalized coords upper_left_norm = upper_left / (0.5 * width_window) upper_top_norm = upper_top / (0.5 * height_window) lower_right_norm = lower_right / (0.5 * width_window) lower_bottom_norm = lower_bottom / (0.5 * height_window) rect = [ upper_left_norm, upper_top_norm, lower_right_norm * diff_x, lower_bottom_norm * diff_y ] extra = 2 # extra space to draw lines around rect if not self.viewport: self.viewport = visual.Rect(win=self.win, width=width + extra, height=height + extra, autoLog=None, units='pixels', lineWidth=1, lineColor='yellow', lineColorSpace='rgb', fillColor=None, fillColorSpace='rgb', pos=(center_left, center_top), ori=0.0, opacity=0.5, contrast=0.8, depth=1000, interpolate=False, name=None, autoDraw=True) else: self.viewport.setPos((center_left, center_top)) self.win.flip() if genstim: model_stimulus = self.win._getFrame(rect=None, buffer='front') model_stimulus = np.array(model_stimulus) center_height = int(height_window / 2) + center_top center_width = int(width_window / 2) + center_left top_height = int(center_height - height / 2) bottom_height = int(center_height + height / 2) top_width = int(center_width - width / 2) bottom_width = int(center_width + width / 2) model_stimulus = model_stimulus[top_height:bottom_height, top_width:bottom_width, :] model_stimulus = Image.fromarray(model_stimulus) if np.shape(model_stimulus)[1] != width or np.shape( model_stimulus)[0] != height: print(np.shape(model_stimulus)) import pdb pdb.set_trace() # TODO: Make it so this exception gets caught logging.error('problem with model window') raise Exception( 'Error in calculating shape of model window.') if 'model' in os.path.join(params['cwd'], 'stimuli' + os.sep + filename): import pdb pdb.set_trace() logging.info('saving: ' + filename) model_stimulus.save( os.path.join(params['cwd'], 'stimuli' + os.sep + filename), "PNG") keys = [] else: keys = event.waitKeys(maxWait=self.timeout, keyList=None) resp = Response(key=keys[0]) for component in self.components: component.end() else: filename = self.get_filename(trialparams, loopstate) # if we don't have a target_contrast in the parameters # then we need to load an image and get its target_contrast if 'target_contrast' not in params['model']: target_key, target_val = params['target_identifier'] target_condition = Params(trialparams) target_condition.update({target_key: target_val}) target_filename = self.get_filename(target_condition, loopstate) # load stim from filename im = Image.open( os.path.join(params['cwd'], 'stimuli' + os.sep + target_filename)).convert('L') im_array = np.frombuffer(im.tobytes(), dtype=np.uint8) width, height = im.size im_array = im_array.reshape((height, width)) contrast = runmodel.process(data=im_array) runmodel.target_contrast = contrast logging.exp('Generating target_contrast from ' + target_filename) logging.exp('Using target_contrast = ' + str(contrast)) params['model']['target_contrast'] = contrast runmodel.update_decision_params() params['model']['decision_K'] = runmodel.decision_K params['model']['decision_sigma'] = runmodel.decision_sigma logging.exp('***Using decision_K = ' + str(runmodel.decision_K)) logging.exp('***Using decision_sigma = ' + str(runmodel.decision_sigma)) # load stim from filename define StimulusNotFound and raise it im = Image.open( os.path.join(params['cwd'], 'stimuli' + os.sep + filename)).convert('L') im_array = np.frombuffer(im.tobytes(), dtype=np.uint8) width, height = im.size im_array = im_array.reshape((height, width)) print('processing:', filename) save_conv_filename = None if params['saveimages']: #save_conv_filename = 'images/'+filename[:-4]+'-convolved.png' save_conv_filename = filename[:-4] correct_answer, incorrect_answer = self.get_answer( trialparams, loopstate) # if we are saving additional plots we also want to pass the # corresponding target data to the runmodel.runsponse im_array_target = None if save_conv_filename: target_key, target_val = params['target_identifier'] target_condition = Params(trialparams) target_condition.update({target_key: target_val}) target_filename = self.get_filename(target_condition, loopstate) # load stim from filename im_target = Image.open( os.path.join(params['cwd'], 'stimuli' + os.sep + target_filename)).convert('L') im_array_target = np.frombuffer(im_target.tobytes(), dtype=np.uint8) width_target, height_target = im_target.size im_array_target = im_array_target.reshape( (height_target, width_target)) # TODO: if we are generating all the plots then read the target data # from the file using the 'target_identifier' from params # then pass into runmodel.response key, prob, contrast = runmodel.response( data=im_array, correct_answer=correct_answer, incorrect_answer=incorrect_answer, save_conv_filename=save_conv_filename, target_data=im_array_target) correct = False if key == correct_answer: correct = True resp = Response(key=key, rt=0, correct=correct, prob=prob, contrast=contrast) #keys = ['left'] if resp.key == 'escape': core.quit() return resp
def __init__(self, win, text="Hello World", font="", pos=(0.0,0.0), depth=0, rgb=None, color=(1.0,1.0,1.0), colorSpace='rgb', opacity=1.0, contrast=1.0, units="", ori=0.0, height=None, antialias=True, bold=False, italic=False, alignHoriz='center', alignVert='center', fontFiles=[], wrapWidth=None, flipHoriz=False, flipVert=False, name='', autoLog=True): """ :Parameters: win: A :class:`Window` object. Required - the stimulus must know where to draw itself text: The text to be rendered height: Height of the characters (including the ascent of the letter and the descent) antialias: boolean to allow (or not) antialiasing the text bold: Make the text bold (better to use a bold font name) italic: Make the text italic (better to use an actual italic font) alignHoriz: The horizontal alignment ('left', 'right' or 'center') alignVert: The vertical alignment ('top', 'bottom' or 'center') fontFiles: A list of additional files if the font is not in the standard system location (include the full path) wrapWidth: The width the text should run before wrapping flipHoriz : boolean Mirror-reverse the text in the left-right direction flipVert : boolean Mirror-reverse the text in the up-down direction """ BaseVisualStim.__init__(self, win, units=units, name=name, autoLog=autoLog) self.useShaders = win._haveShaders #use shaders if available by default, this is a good thing self._needUpdate = True self.alignHoriz = alignHoriz self.alignVert = alignVert self.antialias = antialias self.bold=bold self.italic=italic self.text='' #NB just a placeholder - real value set below self.depth=depth self.ori=ori self.wrapWidth=wrapWidth self.flipHoriz = flipHoriz self.flipVert = flipVert self._pygletTextObj=None self.pos= numpy.array(pos, float) #height in pix (needs to be done after units which is done during _Base.__init__) if self.units=='cm': if height==None: self.height = 1.0#default text height else: self.height = height self.heightPix = cm2pix(self.height, win.monitor) elif self.units in ['deg', 'degs']: if height==None: self.height = 1.0 else: self.height = height self.heightPix = deg2pix(self.height, win.monitor) elif self.units=='norm': if height==None: self.height = 0.1 else: self.height = height self.heightPix = self.height*win.size[1]/2 elif self.units=='height': if height==None: self.height = 0.2 else: self.height = height self.heightPix = self.height*win.size[1] else: #treat units as pix if height==None: self.height = 20 else: self.height = height self.heightPix = self.height if self.wrapWidth ==None: if self.units in ['height','norm']: self.wrapWidth=1 elif self.units in ['deg', 'degs']: self.wrapWidth=15 elif self.units=='cm': self.wrapWidth=15 elif self.units in ['pix', 'pixels']: self.wrapWidth=500 if self.units=='norm': self._wrapWidthPix= self.wrapWidth*win.size[0]/2 elif self.units=='height': self._wrapWidthPix= self.wrapWidth*win.size[0] elif self.units in ['deg', 'degs']: self._wrapWidthPix= deg2pix(self.wrapWidth, win.monitor) elif self.units=='cm': self._wrapWidthPix= cm2pix(self.wrapWidth, win.monitor) elif self.units in ['pix', 'pixels']: self._wrapWidthPix=self.wrapWidth #generate the texture and list holders self._listID = GL.glGenLists(1) if not self.win.winType=="pyglet":#pygame text needs a surface to render to self._texID = GL.GLuint() GL.glGenTextures(1, ctypes.byref(self._texID)) self.colorSpace=colorSpace if rgb!=None: logging.warning("Use of rgb arguments to stimuli are deprecated. Please use color and colorSpace args instead") self.setColor(rgb, colorSpace='rgb', log=False) else: self.setColor(color, log=False) self._calcPosRendered() for thisFont in fontFiles: pyglet.font.add_file(thisFont) self.setFont(font, log=False) self.opacity = float(opacity) self.contrast = float(contrast) self.setText(text, log=False) #self.width and self.height get set with text and calcSizeRednered is called self._needUpdate = True