def _updateVertices(self): """ """ #then combine with position and convert to pix pos = convertToPix(vertices = [0,0], pos = self.pos, units=self.units, win=self.win) size = convertToPix(vertices = self.size, pos = 0, units=self.units, win=self.win) try: size=size[0] except: pass #assign to self attrbute self.__dict__['posPix'] = pos self.__dict__['sizePix'] = size self._needVertexUpdate = False
def _updateVertices(self): """Sets Stim.verticesPix from fieldPos and """ #Handle the orientation, size and location of each element in native units # radians = 0.017453292519943295 verts = numpy.zeros([self.nElements*4, 3],'d') #rotate 'width' and 'height' and find their effects on X and Y wx = -self.sizes[:,0]*numpy.cos(self.oris[:]*radians)/2 wy = self.sizes[:,0]*numpy.sin(self.oris[:]*radians)/2 hx = self.sizes[:,1]*numpy.sin(self.oris[:]*radians)/2 hy = self.sizes[:,1]*numpy.cos(self.oris[:]*radians)/2 #X verts[0::4,0] = + wx + hx#TopR verts[1::4,0] = - wx + hx#TopL verts[2::4,0] = - wx - hx#BotL verts[3::4,0] = + wx - hx#BotR #Y verts[0::4,1] = + wy + hy verts[1::4,1] = - wy + hy verts[2::4,1] = - wy - hy verts[3::4,1] = + wy - hy #Z verts[:,2] = 1#self.depths + self.fieldDepth #Now shift by fieldPos and convert to appropriate units pos = numpy.tile(self.xys+self.fieldPos, (1,4)).reshape([self.nElements*4,2]) verts[:,:2] = convertToPix(vertices = verts[:,:2], pos = pos, units=self.units, win = self.win) #assign to self attrbute self.__dict__['verticesPix'] = numpy.require(verts,requirements=['C'])#make sure it's contiguous self._needVertexUpdate = False
def height(self, height): """The height of the letters (Float/int or None = set default). Height includes the entire box that surrounds the letters in the font. The width of the letters is then defined by the font. :ref:`Operations <attrib-operations>` supported.""" # height in pix (needs to be done after units which is done during # _Base.__init__) if height is None: if self.units in defaultLetterHeight: height = defaultLetterHeight[self.units] else: msg = ("TextStim does now know a default letter height " "for units %s") raise AttributeError(msg % repr(self.units)) self.__dict__['height'] = height self._heightPix = convertToPix(pos=numpy.array([0, 0]), vertices=numpy.array([0, self.height]), units=self.units, win=self.win)[1] # need to update the font to reflect the change self.setFont(self.font, log=False) return self.__dict__['height']
def _move_cursor(self, mouse, t_remain, cursor_t): """ Method for adjusting the position of the cursor and drawing the trace. Wrote mainly to aid profiling.""" new_trace = False # Get new position from mouse if mouse.getPressed()[0]: if self.frame['lifted']: new_trace = True self.frame['start_frame_idx'] = self.frame['idx'] + 1 self.frame['lifted'] = False new_pos = convertToPix(mouse.getPos(), (0, 0), units=mouse.units, win=self.win) else: self.frame['lifted'] = True new_pos = self.frame['trace_vertices'][self.frame['idx']] # Record time at which that happened # cursor_t.append(t_remain) cursor_t[self.frame['idx']] = t_remain # Move cursor to that position and save self.frame['elements']['cursor'].pos = new_pos # For drawing trace self._draw_trace(new_pos, new_trace=new_trace)
def vertices(self): textbox = self.textbox # check we have a caret index if self.index is None or self.index > len(textbox.text): self.index = len(textbox.text) if self.index < 0: self.index = 0 # get the verts of character next to caret (chr is the next one so use # left edge unless last index then use the right of prev chr) # lastChar = [bottLeft, topLeft, **bottRight**, **topRight**] ii = self.index if textbox.vertices.shape[0] == 0: verts = self.textbox._getStartingVertices() verts[:,1] = verts[:,1] / float(textbox._pixelScaling) verts[:,1] = verts[:,1] + float(textbox._anchorOffsetY) else: if self.index >= len(textbox._lineNs): # caret is after last chr chrVerts = textbox.vertices[range((ii-1) * 4, (ii-1) * 4 + 4)] x = chrVerts[2, 0] # x-coord of left edge (of final char) else: chrVerts = textbox.vertices[range(ii * 4, ii * 4 + 4)] x = chrVerts[1, 0] # x-coord of right edge # the y locations are the top and bottom of this line y1 = textbox._lineBottoms[self.row] / textbox._pixelScaling y2 = textbox._lineTops[self.row] / textbox._pixelScaling # char x pos has been corrected for anchor already but lines haven't verts = (np.array([[x, y1], [x, y2]]) + (0, textbox._anchorOffsetY)) return convertToPix(vertices=verts, pos=textbox.pos, win=textbox.win, units=textbox.units)
def _caretVertices(self): # check we have a caret index if self.caretIndex is None or self.caretIndex > len(self._lineNs): self.caretIndex = len(self.text) # get the verts of character next to caret (chr is the next one so use # left edge unless last index then use the right of prev chr) # lastChar = [bottLeft, topLeft, **bottRight**, **topRight**] if self.caretIndex >= len(self.text): # caret is after last chr chrVerts = self._index2vertices(self.caretIndex - 1) lineN = self._lineNs[self.caretIndex - 1] x = chrVerts[2, 0] # x-coord of right edge (of final char) else: chrVerts = self._index2vertices(self.caretIndex) lineN = self._lineNs[self.caretIndex] x = chrVerts[1, 0] # x-coord of left edge # the y locations are the top and bottom of this line y1 = self._lineBottoms[lineN] / self._pixelScaling y2 = self._lineTops[lineN] / self._pixelScaling # char x pos has been corrected for anchor location already but lines # haven't verts = (np.array([[x, y1], [x, y2]]) + (0, self._anchorOffsetY)) return convertToPix(vertices=verts, pos=self.pos, win=self.win, units=self.units)
def contains(thisElementArrayStim, x, y=None, units=None): """Returns True if a point x,y is inside the stimulus' border. Can accept variety of input options: + two separate args, x and y + one arg (list, tuple or array) containing two vals (x,y) + an object with a getPos() method that returns x,y, such as a :class:`~psychopy.event.Mouse`. Returns `True` if the point is within the area defined either by its `border` attribute (if one defined), or its `vertices` attribute if there is no .border. This method handles complex shapes, including concavities and self-crossings. Note that, if your stimulus uses a mask (such as a Gaussian) then this is not accounted for by the `contains` method; the extent of the stimulus is determined purely by the size, position (pos), and orientation (ori) settings (and by the vertices for shape stimuli). See Coder demos: shapeContains.py """ # get the object in pixels if hasattr(x, 'border'): xy = x._borderPix # access only once - this is a property units = 'pix' # we can forget about the units elif hasattr(x, 'verticesPix'): # access only once - this is a property (slower to access) xy = x.verticesPix units = 'pix' # we can forget about the units elif hasattr(x, 'getPos'): xy = x.getPos() units = x.units elif type(x) in [list, tuple, np.ndarray]: xy = np.array(x) else: xy = np.array((x, y)) # try to work out what units x,y has if units is None: if hasattr(xy, 'units'): units = xy.units else: units = thisElementArrayStim.units if units != 'pix': xy = convertToPix(xy, pos=(0, 0), units=units, win=thisElementArrayStim.win) # ourself in pixels if hasattr(thisElementArrayStim, 'border'): poly = thisElementArrayStim._borderPix # e.g., outline vertices else: poly = thisElementArrayStim.verticesPix[:, :, 0: 2] # e.g., tesselated vertices return any( np.fromiter((visual.helpers.pointInPolygon(xy[0], xy[1], thisPoly) for thisPoly in poly), np.bool))
def posPix(self): """This determines the coordinates in pixels of the position for the current stimulus, accounting for pos and units. This property should automatically update if `pos` is changed""" #because this is a property getter we can check /on-access/ if it needs updating :-) if self._needVertexUpdate: self.__dict__['posPix'] = convertToPix(vertices = [0,0], pos = self.pos, units=self.units, win = self.win) self._needVertexUpdate = False return self.__dict__['posPix']
def _updateVertices(self): """Sets Stim.verticesPix and ._borderPix from pos, size, ori, flipVert, flipHoriz """ # check whether stimulus needs flipping in either direction flip = np.array([1, 1]) if hasattr(self, 'flipHoriz') and self.flipHoriz: flip[0] = -1 # True=(-1), False->(+1) if hasattr(self, 'flipVert') and self.flipVert: flip[1] = -1 # True=(-1), False->(+1) font = self.glFont # to start with the anchor is bottom left of *first line* if self._anchorY == 'top': self._anchorOffsetY = (-font.ascender / self._pixelScaling - self.padding) elif self._anchorY == 'center': self._anchorOffsetY = ( self.size[1] / 2 - (font.height / 2 - font.descender) / self._pixelScaling - self.padding) elif self._anchorY == 'bottom': self._anchorOffsetY = (self.size[1] / 2 - font.descender / self._pixelScaling) else: raise ValueError('Unexpected error for _anchorY') if self._anchorX == 'right': self._anchorOffsetX = -(self.size[0] - self.padding) / 1.0 elif self._anchorX == 'center': self._anchorOffsetX = -(self.size[0] - self.padding) / 2.0 elif self._anchorX == 'left': self._anchorOffsetX = 0 else: raise ValueError('Unexpected error for _anchorX') self.vertices = self._rawVerts + (self._anchorOffsetX, self._anchorOffsetY) vertsPix = convertToPix(vertices=self.vertices, pos=self.pos, win=self.win, units=self.units) self.__dict__['verticesPix'] = vertsPix # tight bounding box L = self.vertices[:, 0].min() R = self.vertices[:, 0].max() B = self.vertices[:, 1].min() T = self.vertices[:, 1].max() tightW = R - L Xmid = (R + L) / 2 tightH = T - B Ymid = (T + B) / 2 self.box.size = tightW, tightH self.box.pos = Xmid, Ymid self._needVertexUpdate = False
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. """ self.heightPix = convertToPix(pos = numpy.array([0,0]), vertices=numpy.array([0,height]), units=self.units, win=self.win)[1] #need to update the font to reflect the change self.setFont(self.fontname, log=False) logAttrib(self, log, 'height', height)
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. """ self.heightPix = convertToPix( pos=numpy.array([0, 0]), vertices=numpy.array([0, height]), units=self.units, win=self.win )[1] # 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 wrapWidth(self, wrapWidth): """Int/float or None (set default). The width the text should run before wrapping. :ref:`Operations <attrib-operations>` supported.""" if wrapWidth is None: if self.units in defaultWrapWidth: wrapWidth = defaultWrapWidth[self.units] else: raise AttributeError, "TextStim does now know a default wrap width for units %s" %(repr(self.units)) self.__dict__['wrapWidth'] = wrapWidth self._wrapWidthPix = convertToPix(pos = numpy.array([0, 0]), vertices=numpy.array([self.wrapWidth, 0]), units=self.units, win=self.win)[0] self._needSetText = True
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. """ self.heightPix = convertToPix(pos=numpy.array([0, 0]), vertices=numpy.array([0, height]), units=self.units, win=self.win)[1] #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 _updateVertices(self): """Sets Stim.verticesPix from fieldPos. """ # Handle the orientation, size and location of # each element in native units radians = 0.017453292519943295 # so we can do matrix rotation of coords we need shape=[n*4,3] # but we'll convert to [n,4,3] after matrix math verts = numpy.zeros([self.nElements * 4, 3], 'd') wx = -self.sizes[:, 0] * numpy.cos(self.oris[:] * radians) / 2 wy = self.sizes[:, 0] * numpy.sin(self.oris[:] * radians) / 2 hx = self.sizes[:, 1] * numpy.sin(self.oris[:] * radians) / 2 hy = self.sizes[:, 1] * numpy.cos(self.oris[:] * radians) / 2 # X vals of each vertex relative to the element's centroid verts[0::4, 0] = -wx - hx verts[1::4, 0] = +wx - hx verts[2::4, 0] = +wx + hx verts[3::4, 0] = -wx + hx # Y vals of each vertex relative to the element's centroid verts[0::4, 1] = -wy - hy verts[1::4, 1] = +wy - hy verts[2::4, 1] = +wy + hy verts[3::4, 1] = -wy + hy # set of positions across elements positions = self.xys + self.fieldPos # depth verts[:, 2] = self.depths + self.fieldDepth # rotate, translate, scale by units if positions.shape[0] * 4 == verts.shape[0]: positions = positions.repeat(4, 0) verts[:, :2] = convertToPix(vertices=verts[:, :2], pos=positions, units=self.units, win=self.win) verts = verts.reshape([self.nElements, 4, 3]) # assign to self attribute; make sure it's contiguous self.__dict__['verticesPix'] = numpy.require(verts, requirements=['C']) self._needVertexUpdate = False
def height(self, height): """Float/int or None (set default). 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. :ref:`Operations <attrib-operations>` supported.""" #height in pix (needs to be done after units which is done during _Base.__init__) if height is None: if self.units in defaultLetterHeight: height = defaultLetterHeight[self.units] else: raise AttributeError, "TextStim does now know a default letter height for units %s" %(repr(self.units)) self.__dict__['height'] = height self._heightPix = convertToPix(pos = numpy.array([0, 0]), vertices=numpy.array([0, self.height]), units=self.units, win=self.win)[1] #need to update the font to reflect the change self.setFont(self.font, log=False)
def _create_trial_res(self, trace_let, trial_time, ptt, start_t_stamp, trace_let_pix, scale, cursor_t, traces_pix): """ Creates the results dict that contains basic/essential trial info to be saved. """ self.log.debug('Creating trial results') # original data + metadata self.trial_results = {'trace_let': trace_let, 'trial_time': trial_time, 'ix_block': self.block_idx, 'ix_trial': self.trial_idx, 'ptt': ptt, 'start_t_stamp': start_t_stamp} # new/extra metadata if scale: self.trial_results['scaling_matrix'] = self.stimuli['scaling_matrix'] self.trial_results['traces_pix'] = traces_pix self.trial_results['n_traces'] = len(self.frame['traces']) self.trial_results['trial_duration'] = self.trial_settings['trial_duration'] self.trial_results['flip'] = self.stimuli['flip'] self.trial_results['the_box'] = self.frame['elements'][ 'the_box'].vertices.copy() self.trial_results['theBoxPix'] = self.frame['elements'][ 'the_box'].verticesPix self.trial_results['cursor_t'] = cursor_t if (trace_let != trace_let_pix).any(): self.trial_results['pos_t_pix'] = trace_let_pix # in matlab i think this is theRect self.trial_results['winSize'] = self.win.size self.trial_results['template'] = self.stimuli['current_stim'] stim_size = self.frame['elements']['template'].size stim_pos = self.frame['elements']['template'].pos self.trial_results['template_pix'] = convertToPix(self.stimuli['current_stim'], units='norm', pos=stim_pos, win=self.win) self.trial_results['template_size'] = stim_size self.trial_results['template_pos'] = stim_pos
def contains(self, x, y=None, units=None): """Determines if a point x,y is inside the extent of the stimulus. Can accept variety of input options: + two separate args, x and y + one arg (list, tuple or array) containing two vals (x,y) + an object with a getPos() method that returns x,y, such as a :class:`~psychopy.event.Mouse`. Returns `True` if the point is within the area defined by `vertices`. This method 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 """ #get the object in pixels if hasattr(x, 'verticesPix'): xy = x.verticesPix #access only once - this is a property (slower to access) units = 'pix' #we can forget about the units elif hasattr(x, 'getPos'): xy = x.getPos() units = x.units elif type(x) in [list, tuple, numpy.ndarray]: xy = numpy.array(x) else: xy = numpy.array((x,y)) #try to work out what units x,y has if units is None: if hasattr(xy, 'units'): units = xy.units else: units = self.units if units != 'pix': xy = convertToPix(xy, pos=0, units=units, win=self.win) # ourself in pixels selfVerts = self.verticesPix return pointInPolygon(xy[0], xy[1], poly = selfVerts)
def contains(self, x, y=None, units=None): """Determines if a point x,y is inside the extent of the stimulus. Can accept variety of input options: + two separate args, x and y + one arg (list, tuple or array) containing two vals (x,y) + an object with a getPos() method that returns x,y, such as a :class:`~psychopy.event.Mouse`. Returns `True` if the point is within the area defined by `vertices`. This method 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 """ #get the object in pixels if hasattr(x, 'verticesPix'): xy = x.verticesPix #access only once - this is a property (slower to access) units = 'pix' #we can forget about the units elif hasattr(x, 'getPos'): xy = x.getPos() units = x.units elif type(x) in [list, tuple, numpy.ndarray]: xy = numpy.array(x) else: xy = numpy.array((x, y)) #try to work out what units x,y has if units is None: if hasattr(xy, 'units'): units = xy.units else: units = self.units if units != 'pix': xy = convertToPix(xy, pos=0, units=units, win=self.win) # ourself in pixels selfVerts = self.verticesPix return pointInPolygon(xy[0], xy[1], poly=selfVerts)
def _updateVertices(self): """Sets Stim.verticesPix from pos and size """ if hasattr(self, 'vertices'): verts = self.vertices else: verts = self._verticesBase #check wheher stimulus needs flipping in either direction flip = numpy.array([1,1]) if hasattr(self, 'flipHoriz'): flip[0] = self.flipHoriz*(-2)+1#True=(-1), False->(+1) if hasattr(self, 'flipVert'): flip[1] = self.flipVert*(-2)+1#True=(-1), False->(+1) # set size and orientation verts = numpy.dot(self.size*verts*flip, self._rotationMatrix) #then combine with position and convert to pix verts = convertToPix(vertices=verts, pos=self.pos, win=self.win, units=self.units) #assign to self attrbute self.__dict__['verticesPix'] = verts self._needVertexUpdate = False self._needUpdate = True #but we presumably need to update the list
def convertParamToPix(value, win, units): """ Convert value to numpy array Parameters ---------- value : str, int, float, list, tuple Parameter value to be converted to pixels win : TestWin object A false window with necessary attributes for converting component parameters to pixels units : str Screen units Returns ------- numpy array Parameter converted to pixels in numpy array """ if isinstance(value, str): value = array(ast.literal_eval(value)) else: value = array(value) return monitorunittools.convertToPix(value, array([0, 0]), units=units, win=win) * 2
def _updateVertices(self): """Sets Stim.verticesPix from pos and size """ if hasattr(self, 'vertices'): verts = self.vertices else: verts = self._verticesBase #check wheher stimulus needs flipping in either direction flip = numpy.array([1, 1]) if hasattr(self, 'flipHoriz'): flip[0] = self.flipHoriz * (-2) + 1 #True=(-1), False->(+1) if hasattr(self, 'flipVert'): flip[1] = self.flipVert * (-2) + 1 #True=(-1), False->(+1) # set size and orientation verts = numpy.dot(self.size * verts * flip, self._rotationMatrix) #then combine with position and convert to pix verts = convertToPix(vertices=verts, pos=self.pos, win=self.win, units=self.units) #assign to self attrbute self.__dict__['verticesPix'] = verts self._needVertexUpdate = False self._needUpdate = True #but we presumably need to update the list
def _updateVertices(self): """Sets Stim.verticesPix and ._borderPix from pos, size, ori, flipVert, flipHoriz """ # check whether stimulus needs flipping in either direction flip = np.array([1, 1]) if hasattr(self, 'flipHoriz') and self.flipHoriz: flip[0] = -1 # True=(-1), False->(+1) if hasattr(self, 'flipVert') and self.flipVert: flip[1] = -1 # True=(-1), False->(+1) if hasattr(self, 'vertices'): verts = self.vertices else: verts = self._verticesBase verts = convertToPix(vertices=verts, pos=self.pos, win=self.win, units=self.units) self.__dict__['verticesPix'] = verts self.box.size = self.size self._needVertexUpdate = False
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') super(TextStim, self).__init__(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.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 height==None: if self.units in defaultLetterHeight: height = defaultLetterHeight[self.units] else: raise AttributeError, "TextStim does now know a default letter height for units %s" %(repr(self.units)) if wrapWidth==None: if self.units in defaultWrapWidth: wrapWidth = defaultWrapWidth[self.units] else: raise AttributeError, "TextStim does now know a default wrap width for units %s" %(repr(self.units)) #treat letter height and wrapWidth as vertices (in degFlatPos they should not be 'corrected') wh = convertToPix(pos = numpy.array([0,0]), vertices=numpy.array([wrapWidth,height]), units=self.units, win=self.win) self._wrapWidthPix, self.heightPix = wh #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) 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 calcSizeRendered 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 _calcPosRendered(self): """Calculate the pos of the stimulus in pixels""" self._posRendered = convertToPix(pos=self.pos, vertices=numpy.array([0, 0]), units=self.units, win=self.win)
def create_frame(self, stimuli_idx, scale=True): self.frame['elements'] = {} self.frame['elements']['template'] = create_element( 'template', win=self.win, image=utils.template_to_image( self.get_stimuli(stimuli_idx, scale=scale), f'{self.stimuli["fname"][:-4]}_{stimuli_idx}', self.paths['data']/'template_images', lineWidth=15 ), size=self.block_settings['size'] * 1.6725 # scaling ) # uncomment below to draw old template to check how the image # matches up with it # # seems to top out after like 10 or 20: # https://github.com/psychopy/psychopy/issues/818 # from psychopy.visual import ShapeStim # self.frame['elements']['old_template'] = visual.ShapeStim(win=self.win, # vertices=self.get_stimuli(stimuli_idx,scale=scale), # lineWidth=100, # closeShape=False, # interpolate=True, # ori=0, # pos=(0,0), # size=1.5, # units='norm', # fillColor='blue', # lineColor='blue', # #windingRule=True, # ) # self.frame['elements']['template'].setOpacity(0.5) self.frame['elements']['the_box'] = create_element('the_box', win=self.win, vertices=self.stimuli['the_box'] ) self.frame['elements']['trial_number'] = create_element( 'trial_number', win=self.win, text=f'Trial {self.trial_idx}/{self.block_settings["n_trials"]}', ) self.frame['elements']['cursor'] = create_element( 'cursor', win=self.win, pos=convertToPix( # so the 1.5 comes from how much the original template had to be scaled by after being converted # to norm units. the 1.6.. in the template section is how much the resulting image of the template had # to be scaled by in order to match up with the original template when drawn on top of each other self.trial_settings['cursor_start_pos'] * 1.5 * self.block_settings['size'], (0, 0), 'norm', self.win ) ) max_trace_len = 10000 # Should be more points than would ever be drawn self.frame['trace_vertices'] = np.zeros([max_trace_len, 2]) self.frame['trace_vertices'][0] = \ convertToPix(self.trial_settings['cursor_start_pos'] * 1.5 * self.block_settings['size'], (0, 0), 'norm', self.win) # TODO: clicking NOT on cursor (post startpoint press) results in no trace being drawn - fix # trace starts to get drawn after sometime - some kind of lag on recording of trace vertices.. self.frame['elements']['trace1'] = create_element( 'trace', # we will dynamically create more -> draw interupted lines win=self.win, vertices=self.frame['trace_vertices'][0:1], # initialize empty - now take first point? ) self.frame['traces'].append('trace1') self.frame['elements']['instructions'] = create_element( 'instructions', win=self.win, image=self.paths['instructions'] ) # start_point start_point_size = 0.05 self.frame['elements']['start_point'] = create_element( 'start_point', win=self.win, size=(start_point_size, start_point_size * self.win_settings['aspect_ratio']) ) self.frame['elements']['time_bar'] = create_element( 'time_bar', win=self.win )
def __init__(self, win, text, font, pos=(0, 0), units=None, letterHeight=None, size=None, color=(1.0, 1.0, 1.0), colorSpace='rgb', contrast=1, opacity=1.0, bold=False, italic=False, lineSpacing=1.0, padding=None, # gap between box and text anchor='center', alignment='left', fillColor=None, borderWidth=2, borderColor=None, flipHoriz=False, flipVert=False, editable=False, name='', autoLog=None, onTextCallback=None): """ Parameters ---------- win text font pos units letterHeight size : Specifying None gets the default size for this type of unit. Specifying [None, None] gets a TextBox that's expandable in both dimensions. Specifying [0.75, None] gets a textbox that expands in the length but fixed at 0.75 units in the width color colorSpace contrast opacity bold italic lineSpacing padding anchor alignment fillColor borderWidth borderColor flipHoriz flipVert editable name autoLog """ BaseVisualStim.__init__(self, win, units=units, name=name) self.win = win self.colorSpace = colorSpace self.color = color self.contrast = contrast self.opacity = opacity self.onTextCallback = onTextCallback # first set params needed to create font (letter sizes etc) if letterHeight is None: self.letterHeight = defaultLetterHeight[self.units] else: self.letterHeight = letterHeight # self._pixLetterHeight helps get font size right but not final layout if 'deg' in self.units: # treat deg, degFlat or degFlatPos the same scaleUnits = 'deg' # scale units are just for font resolution else: scaleUnits = self.units self._pixLetterHeight = convertToPix( self.letterHeight, pos=0, units=scaleUnits, win=self.win) self._pixelScaling = self._pixLetterHeight / self.letterHeight if size is None: size = [defaultBoxWidth[self.units], None] self.size = size # but this will be updated later to actual size self.bold = bold self.italic = italic self.lineSpacing = lineSpacing if padding is None: padding = letterHeight / 2.0 self.padding = padding self.glFont = None # will be set by the self.font attribute setter self.font = font # once font is set up we can set the shader (depends on rgb/a of font) if self.glFont.atlas.format == 'rgb': global rgbShader self.shader = rgbShader = shaders.Shader( shaders.vertSimple, shaders.fragTextBox2) else: global alphaShader self.shader = alphaShader = shaders.Shader( shaders.vertSimple, shaders.fragTextBox2alpha) self._needVertexUpdate = False # this will be set True during layout # standard stimulus params self.pos = pos self.ori = 0.0 self.depth = 0.0 # used at render time self._lines = None # np.array the line numbers for each char self._colors = None self.flipHoriz = flipHoriz self.flipVert = flipVert # params about positioning (after layout has occurred) self.anchor = anchor # 'center', 'top_left', 'bottom-center'... self.alignment = alignment # box border and fill w, h = self.size self.borderWidth = borderWidth self.borderColor = borderColor self.fillColor = fillColor self.box = Rect( win, pos=self.pos, units=self.units, lineWidth=borderWidth, lineColor=borderColor, fillColor=fillColor, opacity=self.opacity, autoLog=False, fillColorSpace=self.colorSpace) # also bounding box (not normally drawn but gives tight box around chrs) self.boundingBox = Rect( win, pos=self.pos, units=self.units, lineWidth=1, lineColor=None, fillColor=fillColor, opacity=0.1, autoLog=False) self.pallette = { # If no focus 'lineColor': borderColor, 'lineRGB': self.box.lineRGB, 'lineWidth': borderWidth, 'fillColor': fillColor, 'fillRGB': self.box.fillRGB } # then layout the text (setting text triggers _layout()) self.text = text # caret self.editable = editable self.caret = Caret(self, color=self.color, width=5) self._hasFocus = False if editable: # may yet gain focus if the first editable obj self.win.addEditable(self) self.autoLog = autoLog
def _updateVertices(self): """Sets Stim.verticesPix and ._borderPix from pos, size, ori, flipVert, flipHoriz """ # check whether stimulus needs flipping in either direction flip = np.array([1, 1]) if hasattr(self, 'flipHoriz') and self.flipHoriz: flip[0] = -1 # True=(-1), False->(+1) if hasattr(self, 'flipVert') and self.flipVert: flip[1] = -1 # True=(-1), False->(+1) font = self.glFont # to start with the anchor is bottom left of *first line* if self._anchorY == 'top': self._anchorOffsetY = (-font.ascender / self._pixelScaling - self.padding) boxOffsetY = - self.size[1] / 2.0 elif self._anchorY == 'center': self._anchorOffsetY = ( self.size[1] / 2 - (font.height / 2 - font.descender) / self._pixelScaling - self.padding ) boxOffsetY = 0 elif self._anchorY == 'bottom': self._anchorOffsetY = ( self.size[1] - (font.height / 2 + font.ascender) / self._pixelScaling ) # self._anchorOffsetY = (-font.ascender / self._pixelScaling # - self.padding) boxOffsetY = + (self.size[1]) / 2.0 else: raise ValueError('Unexpected value for _anchorY') # calculate anchor offsets (text begins on left=0, box begins center=0) if self._anchorX == 'right': self._anchorOffsetX = - self.size[0] + self.padding boxOffsetX = - self.size[0] / 2.0 elif self._anchorX == 'center': self._anchorOffsetX = - self.size[0] / 2.0 + self.padding boxOffsetX = 0 elif self._anchorX == 'left': self._anchorOffsetX = 0 + self.padding boxOffsetX = + self.size[0] / 2.0 else: raise ValueError('Unexpected value for _anchorX') self.vertices = self._rawVerts + (self._anchorOffsetX, self._anchorOffsetY) vertsPix = convertToPix(vertices=self.vertices, pos=self.pos, win=self.win, units=self.units) self.__dict__['verticesPix'] = vertsPix # tight bounding box if self.vertices.shape[0] < 1: # editable box with no letters? self.boundingBox.size = 0, 0 self.boundingBox.pos = self.pos else: L = self.vertices[:, 0].min() R = self.vertices[:, 0].max() B = self.vertices[:, 1].min() T = self.vertices[:, 1].max() tightW = R-L Xmid = (R+L)/2 tightH = T-B Ymid = (T+B)/2 # for the tight box anchor offset is included in vertex calcs self.boundingBox.size = tightW, tightH self.boundingBox.pos = self.pos + (Xmid, Ymid) # box (larger than bounding box) needs anchor offest adding self.box.pos = self.pos + (boxOffsetX, boxOffsetY) self.box.size = self.size # this might have changed from _requested self._needVertexUpdate = False
def __init__( self, win, text, font, pos=(0, 0), units='pix', letterHeight=None, size=None, color=(1.0, 1.0, 1.0), colorSpace='rgb', opacity=1.0, bold=False, italic=False, lineSpacing=1.0, padding=None, # gap between box and text anchor='center', fillColor=None, borderColor=None, flipHoriz=False, flipVert=False, editable=False, name='', autoLog=None): BaseVisualStim.__init__(self, win, units=units, name=name, autoLog=autoLog) self.win = win # first set params needed to create font (letter sizes etc) if letterHeight is None: self.letterHeight = defaultLetterHeight[units] else: self.letterHeight = letterHeight # self._pixLetterHeight helps get font size right but not final layout if 'deg' in units: # treat deg, degFlat or degFlatPos the same scaleUnits = 'deg' # scale units are just for font resolution else: scaleUnits = units self._pixLetterHeight = convertToPix(self.letterHeight, pos=0, units=scaleUnits, win=self.win) if size is None: size = (defaultBoxWidth[units], -1) self._requestedSize = size # (-1 in either dim means not constrained) self.size = size # but this will be updated later to actual size self.bold = bold self.italic = italic self.lineSpacing = lineSpacing if padding is None: padding = defaultLetterHeight[units] / 2.0 self.padding = padding self.glFont = None # will be set by the self.font attribute setter self.font = font # once font is set up we can set the shader (depends on rgb/a of font) if self.glFont.atlas.format == 'rgb': global rgbShader self.shader = rgbShader = shaders.Shader(shaders.vertSimple, shaders.fragTextBox2) else: global alphaShader self.shader = alphaShader = shaders.Shader( shaders.vertSimple, shaders.fragTextBox2alpha) # params about positioning self.anchor = anchor # 'center', 'top_left', 'bottom-center'... self._needVertexUpdate = False # this will be set True during layout # standard stimulus params self.pos = pos self.color = color self.colorSpace = colorSpace self.opacity = opacity # used at render time self._lines = None # np.array the line numbers for each char self._colors = None self.flipHoriz = flipHoriz self.flipVert = flipVert self.text = text # setting this triggers a _layout() call so do last # box border and fill self.box = Rect(win, pos=pos, width=self.size[0], height=self.size[1], units=units, lineColor=borderColor, fillColor=fillColor) self.borderColor = borderColor self.fillColor = fillColor # caret self.editable = editable self.caretIndex = None if editable: self.win.addEditable(self)
def __init__( self, win, text, font, pos=(0, 0), units=None, letterHeight=None, size=None, color=(1.0, 1.0, 1.0), colorSpace='rgb', fillColor=None, fillColorSpace=None, borderWidth=2, borderColor=None, borderColorSpace=None, contrast=1, opacity=None, bold=False, italic=False, lineSpacing=1.0, padding=None, # gap between box and text anchor='center', alignment='left', flipHoriz=False, flipVert=False, editable=False, lineBreaking='default', name='', autoLog=None, onTextCallback=None): """ Parameters ---------- win text font pos units letterHeight size : Specifying None gets the default size for this type of unit. Specifying [None, None] gets a TextBox that's expandable in both dimensions. Specifying [0.75, None] gets a textbox that expands in the length but fixed at 0.75 units in the width color colorSpace contrast opacity bold italic lineSpacing padding anchor alignment fillColor borderWidth borderColor flipHoriz flipVert editable lineBreaking: Specifying 'default', text will be broken at a set of characters defined in the module. Specifying 'uax14', text will be broken in accordance with UAX#14 (Unicode Line Breaking Algorithm). name autoLog """ BaseVisualStim.__init__(self, win, units=units, name=name) self.win = win self.colorSpace = colorSpace ColorMixin.foreColor.fset( self, color ) # Have to call the superclass directly on init as text has not been set self.onTextCallback = onTextCallback # first set params needed to create font (letter sizes etc) if letterHeight is None: self.letterHeight = defaultLetterHeight[self.units] else: self.letterHeight = letterHeight # self._pixLetterHeight helps get font size right but not final layout if 'deg' in self.units: # treat deg, degFlat or degFlatPos the same scaleUnits = 'deg' # scale units are just for font resolution else: scaleUnits = self.units self._pixLetterHeight = convertToPix(self.letterHeight, pos=0, units=scaleUnits, win=self.win) if isinstance(self._pixLetterHeight, np.ndarray): # If pixLetterHeight is an array, take the Height value self._pixLetterHeight = self._pixLetterHeight[1] self._pixelScaling = self._pixLetterHeight / self.letterHeight if size is None: size = [defaultBoxWidth[self.units], None] self.size = size # but this will be updated later to actual size self.bold = bold self.italic = italic self.lineSpacing = lineSpacing if padding is None: padding = self.letterHeight / 2.0 self.padding = padding self.glFont = None # will be set by the self.font attribute setter self.font = font # If font not found, default to Open Sans Regular and raise alert if not self.glFont: alerts.alert( 4325, self, { 'font': font, 'weight': 'bold' if self.bold is True else 'regular' if self.bold is False else self.bold, 'style': 'italic' if self.italic else '', 'name': self.name }) self.bold = False self.italic = False self.font = "Open Sans" # once font is set up we can set the shader (depends on rgb/a of font) if self.glFont.atlas.format == 'rgb': global rgbShader self.shader = rgbShader = shaders.Shader(shaders.vertSimple, shaders.fragTextBox2) else: global alphaShader self.shader = alphaShader = shaders.Shader( shaders.vertSimple, shaders.fragTextBox2alpha) self._needVertexUpdate = False # this will be set True during layout # standard stimulus params self.pos = pos self.ori = 0.0 self.depth = 0.0 # used at render time self._lines = None # np.array the line numbers for each char self._colors = None self._styles = None self.flipHoriz = flipHoriz self.flipVert = flipVert # params about positioning (after layout has occurred) self.anchor = anchor # 'center', 'top_left', 'bottom-center'... self.alignment = alignment # box border and fill w, h = self.size self.borderWidth = borderWidth self.borderColor = borderColor self.fillColor = fillColor self.contrast = contrast self.opacity = opacity self.box = Rect(win, pos=self.pos, units=self.units, lineWidth=borderWidth, lineColor=borderColor, fillColor=fillColor, opacity=self.opacity, autoLog=False, fillColorSpace=self.colorSpace) # also bounding box (not normally drawn but gives tight box around chrs) self.boundingBox = Rect(win, pos=self.pos, units=self.units, lineWidth=1, lineColor=None, fillColor=fillColor, opacity=0.1, autoLog=False) # set linebraking option if lineBreaking not in ('default', 'uax14'): raise ValueError("Unknown lineBreaking option ({}) is" "specified.".format(lineBreaking)) self._lineBreaking = lineBreaking # then layout the text (setting text triggers _layout()) self._text = '' self.text = self.startText = text if text is not None else "" # caret self.editable = editable self.caret = Caret(self, color=self.color, width=5) self.autoLog = autoLog