def labelHeight(self, value): if isinstance(value, layout.Vector): # If given a Size, use it self._labelHeight = value else: # Otherwise, convert to a Size object self._labelHeight = layout.Size([None, value], units=self.units, win=self.win)
def _getMarkerParams(self): """ Calculates location and size of marker based on own location and size """ # Calculate pos pos = self._ratingToPos(self.rating or 0) # Get size (and correct for norm) size = layout.Size([min(self._size.pix)] * 2, 'pix', self.win) # Store marker details self.markerParams = { 'units': self.units, 'pos': pos, 'size': size * np.array(self._markerSizeMultiplier) + layout.Size(self._markerSizeAddition, self.units, self.win), }
def test_unit_mismatch(self): """ Test that a given stimulus can be drawn without error in all combinations of stimulus units x window units and checking that it looks the same as when both units are pix. """ # Test all unit types apart from None and "" unitTypes = layout.unitTypes[2:] # Create window (same size as was used for other tests) win = visual.Window(self.obj.win.size, pos=self.obj.win.pos, monitor="testMonitor") # Create object obj = copy(self.obj) obj.win = win # Create model image (pix units for both) win.units = 'pix' obj.units = 'pix' obj.draw() filename = Path(utils.TESTS_DATA_PATH) / "test_unit_mismatch.png" win.getMovieFrame(buffer='back').save(filename) if hasattr(obj, "_size"): # Get model sizes targetSizes = {units: getattr(obj._size, units) for units in unitTypes} # Flip screen win.flip() # Iterate through window and object units for winunits in unitTypes: for objunits in unitTypes: # Create a window and object win.units = winunits obj.units = objunits # Draw object obj.draw() # Compare appearance utils.compareScreenshot(filename, win, tag=f"{winunits}X{objunits}") if hasattr(obj, "_size"): # Compare reported size assert layout.Size(obj.size, obj.units, obj.win) == layout.Size(targetSizes[objunits], objunits, obj.win) # Flip screen win.flip() # Close window win.close() # Delete model image filename.unlink()
def labelWrapWidth(self, value): if value is None: pass elif isinstance(value, layout.Vector): # If given a Size, use it self._labelWrapWidth = value else: # Otherwise, convert to a Size object self._labelWrapWidth = layout.Size([value, None], units=self.units, win=self.win)
def test_alignment(self): # Define classic use cases exemplars = [ "top left", "top center", "top right", "center left", "center center", "center right", "bottom left", "bottom center", "bottom right", ] # Some potentially problematic use cases which should be handled tykes = [ "center", "centre", "centre centre", "someword", "more than two words", ] # Get intial textbox params initParams = {} for param in [ "units", "fillColor", "color", "padding", "letterHeight", "alignment", "text" ]: initParams[param] = getattr(self.textbox, param) # Test for case in exemplars + tykes: for units in layout.unitTypes: # Setup textbox self.textbox.text = "A PsychoPy zealot knows a smidge of wx but JavaScript is the question." self.textbox.fillColor = "white" self.textbox.color = "black" self.textbox.padding = 0 self.textbox.letterHeight = layout.Size((0, 10), units="pix", win=self.win) # Set test attributes self.textbox.units = units self.textbox.alignment = case # Draw and compare self.textbox.draw() #self.win.getMovieFrame(buffer='back').save(Path(utils.TESTS_DATA_PATH) / f"textbox_{self.textbox._lineBreaking}_align_{case.replace(' ', '_')}.png") utils.compareScreenshot( Path(utils.TESTS_DATA_PATH) / f"textbox_{self.textbox._lineBreaking}_align_{case.replace(' ', '_')}.png", self.win, crit=20) self.win.flip() # Reset initial params for param, value in initParams.items(): setattr(self.textbox, param, value)
def _getLineParams(self): """ Calculates location and size of the line based on own location and size """ # Store line details self.lineParams = { 'units': self.units, 'pos': self.pos, 'size': self._extent * np.array(self._lineSizeMultiplier) + layout.Size(self._lineSizeAddition, self.units, self.win) }
def _getTickParams(self): """ Calculates the locations of the line, tickLines and labels from the rating info """ # If categorical, create tick values from labels if self.categorical: self.ticks = np.arange(len(self.labels)) self.granularity = 1.0 # Calculate positions xys = self._ratingToPos(self.ticks) # Get size (and correct for norm) size = layout.Size([min(self._extent.pix)] * 2, 'pix', self.win) # Store tick details self.tickParams = { 'units': self.units, 'xys': xys, 'sizes': np.tile( getattr(size, self.units) * np.array(self._tickSizeMultiplier) + np.array(self._tickSizeAddition), (len(self.ticks), 1)), }
def __init__( self, win, ticks=(1, 2, 3, 4, 5), labels=None, startValue=None, pos=(0, 0), size=None, units=None, flip=False, ori=0, style='rating', styleTweaks=[], granularity=0, readOnly=False, labelColor='White', markerColor='Red', lineColor='White', colorSpace='rgb', opacity=None, font='Helvetica Bold', depth=0, name=None, labelHeight=None, labelWrapWidth=None, autoDraw=False, autoLog=True, # Synonyms color=False, fillColor=False, borderColor=False): """ Parameters ---------- win : psychopy.visual.Window Into which the scale will be rendered ticks : list or tuple A set of values for tick locations. If given a list of numbers then these determine the locations of the ticks (the first and last determine the endpoints and the rest are spaced according to their values between these endpoints. labels : a list or tuple The text to go with each tick (or spaced evenly across the ticks). If you give 3 labels but 5 tick locations then the end and middle ticks will be given labels. If the labels can't be distributed across the ticks then an error will be raised. If you want an uneven distribution you should include a list matching the length of ticks but with some values set to None pos : XY pair (tuple, array or list) size : w,h pair (tuple, array or list) The size for the scale defines the area taken up by the line and the ticks. This also controls whether the scale is horizontal or vertical. units : the units to interpret the pos and size flip : bool By default the labels will be below or left of the line. This puts them above (or right) granularity : int or float The smallest valid increments for the scale. 0 gives a continuous (e.g. "VAS") scale. 1 gives a traditional likert scale. Something like 0.1 gives a limited fine-grained scale. labelColor / color : Color of the labels according to the color space markerColor / fillColor : Color of the marker according to the color space lineColor / borderColor : Color of the line and ticks according to the color space font : font name autodraw : depth : name : autoLog : """ # what local vars are defined (these are the init params) for use by # __repr__ self._initParams = dir() super(Slider, self).__init__(name=name, autoLog=False) self.win = win if ticks is None: self.ticks = None else: self.ticks = np.array(ticks) self.labels = labels # Set pos and size via base method as objects don't yet exist to layout self.units = units WindowMixin.pos.fset(self, pos) if size is None: size = layout.Size((1, 0.1), 'height', self.win) WindowMixin.size.fset(self, size) # Set multiplier and additions to each component's size self._markerSizeMultiplier = (1, 1) self._markerSizeAddition = (0, 0) self._lineSizeMultiplier = (1, 1) self._lineSizeAddition = (0, 0) self._tickSizeMultiplier = (1, 1) self._tickSizeAddition = (0, 0) self.granularity = granularity self.colorSpace = colorSpace self.color = color if color is not False else labelColor self.fillColor = fillColor if fillColor is not False else markerColor self.borderColor = borderColor if borderColor is not False else lineColor self.opacity = opacity self.font = font self.autoDraw = autoDraw self.depth = depth self.name = name self.autoLog = autoLog self.readOnly = readOnly self.ori = ori self.flip = flip self.startValue = self.markerPos = startValue self.rt = None self.history = [] self.marker = None self.line = None self.tickLines = None self.labelWrapWidth = labelWrapWidth self.labelHeight = labelHeight or min(self.size) / 2 self._updateMarkerPos = True self._dragging = False self.mouse = event.Mouse(win=win) self._mouseStateClick = None # so we can rule out long click probs self._mouseStateXY = None # so we can rule out long click probs self.validArea = None # Create elements self._createElements() self.styleTweaks = [] self.style = style self.styleTweaks += styleTweaks self._layout() # some things must wait until elements created self.contrast = 1.0 # set autoLog (now that params have been initialised) self.autoLog = autoLog if autoLog: logging.exp("Created %s = %s" % (self.name, repr(self))) self.status = NOT_STARTED self.responseClock = core.Clock()
def extent(self, value): self._extent = layout.Size(self.extent, self.units, self.win)
def contains_overlaps(testType): for units in ['pix', 'height', 'norm', 'cm', 'deg']: win.units = units # Create shape to test with vertices = [(0.05, 0.24), (0.05, -0.24), (-0.05, -0.24), (-0.05, 0.24)] shape = visual.ShapeStim(win, vertices=vertices, autoLog=False) # Create circles to show where each point is on screen testPoints = [] for p in points: testPoints.append( visual.Circle(win, size=layout.Size((10, 10), 'pix', win), pos=p, units=units, autoLog=False)) # Try each point / posture combo for i in range(len(postures)): # Set shape attrs shape.setOri(postures[i]['ori'], log=False) shape.setSize(getattr(postures[i]['size'], units), log=False) shape.setPos(getattr(postures[i]['pos'], units), log=False) shape.draw() # Test each point for j in range(len(testPoints)): pointMarker = testPoints[j] p = points[j] # Check contains / overlap if testType == 'contains': res = shape.contains(getattr(p, units)) # test for two parameters x = getattr(p, units)[0] y = getattr(p, units)[1] assert (shape.contains(x, y) == res) elif testType == 'overlaps': res = shape.overlaps(testPoints[j]) else: raise ValueError('Invalid value for parameter `testType`.') # Is the point within the shape? Green == yes, red == no. pointMarker.setFillColor('green' if res else 'red', log=False) pointMarker.draw() # Assert if res != correctResults[i][j]: # Output debug image pointMarker.setBorderColor( 'green' if correctResults[i][j] else 'red', log=False) for marker in testPoints: marker.draw() shape.draw() win.flip() win.screenshot.save( Path(utils.TESTS_DATA_PATH) / f"{testType}_error_local_{i}_{j}.png") # Raise error print(res, points[j], i, j) raise AssertionError( dbgStr % (testType, units, postures[i]['ori'], shape._size, shape._pos, points[j], correctResults[i][j])) # Un-highlight marker pointMarker.draw() win.flip()
unitDist = 0.2 sqrt2 = sqrt(2) points = [ layout.Position((0, 0), 'height', win), layout.Position((0, unitDist), 'height', win), layout.Position((0, unitDist * 2), 'height', win), layout.Position(((unitDist / sqrt2), (unitDist / sqrt2)), 'height', win), layout.Position((unitDist * sqrt2, 0), 'height', win), layout.Position((unitDist * sqrt2, unitDist * sqrt2), 'height', win) ] postures = [{ 'ori': 0, 'size': layout.Size((1.0, 1.0), 'height', win), 'pos': layout.Position((0, 0), 'height', win) }, { 'ori': 0, 'size': layout.Size((1.0, 2.0), 'height', win), 'pos': layout.Position((0, 0), 'height', win) }, { 'ori': 45, 'size': layout.Size((1.0, 1.0), 'height', win), 'pos': layout.Position((0, 0), 'height', win) }, { 'ori': 45, 'size': layout.Size((2.0, 2.0), 'height', win), 'pos': layout.Position((0, 0), 'height', win) }, { 'ori': 0,
def __init__(self, win, newPos=None, visible=True, leftLimit=None, topLimit=None, rightLimit=None, bottomLimit=None, showLimitBox=False, clickOnUp=False, pointer=None, name=None, autoLog=None): """Class for customizing the appearance and behavior of the mouse. Use a custom mouse for extra control over the pointer appearance and function. It's probably slower to render than the regular system mouse. Create your `visual.Window` before creating a CustomMouse. Parameters ---------- win : required, `visual.Window` the window to which this mouse is attached visible : **True** or False makes the mouse invisible if necessary newPos : **None** or [x,y] gives the mouse a particular starting position leftLimit : left edge of a virtual box within which the mouse can move topLimit : top edge of virtual box rightLimit : right edge of virtual box bottomLimit : lower edge of virtual box showLimitBox : default is False display the boundary within which the mouse can move. pointer : The visual display item to use as the pointer; must have .draw() and setPos() methods. If your item has .setOpacity(), you can alter the mouse's opacity. clickOnUp : when to count a mouse click as having occurred default is False, record a click when the mouse is first pressed down. True means record a click when the mouse button is released. """ # what local vars are defined (these are the init params) for use by # __repr__ self._initParams = dir() self._initParams.remove('self') super(CustomMouse, self).__init__(name=name, autoLog=False) self.autoLog = False # set properly at end of init self.win = win self.mouse = event.Mouse(win=self.win) # maybe inheriting from Mouse would be easier? its not that simple self.getRel = self.mouse.getRel self.getWheelRel = self.mouse.getWheelRel self.mouseMoved = self.mouse.mouseMoved # FAILS self.mouseMoveTime = self.mouse.mouseMoveTime self.getPressed = self.mouse.getPressed self.clickReset = self.mouse.clickReset # ??? self._pix2windowUnits = self.mouse._pix2windowUnits self._windowUnits2pix = self.mouse._windowUnits2pix # the graphic to use as the 'mouse' icon (pointer) if pointer: self.pointer = pointer else: self.pointer = vm = visual.ShapeStim( win, vertices=[ [-0.5, 0.5], [-0.5, -0.35], [-0.3, -0.15], [-0.1, -0.5], [0.025, -0.425], [-0.175, -0.1], [0.1, -0.1], [-0.5, 0.5], ], fillColor="white", lineColor="black", lineWidth=1, anchor="top left", size=layout.Size((20, 20), 'pix', win), ) self.mouse.setVisible(False) # hide the actual (system) mouse self.visible = visible # the custom (virtual) mouse self.leftLimit = self.rightLimit = None self.topLimit = self.bottomLimit = None self.setLimit(leftLimit=leftLimit, topLimit=topLimit, rightLimit=rightLimit, bottomLimit=bottomLimit) self.showLimitBox = showLimitBox self.lastPos = None self.prevPos = None if newPos is not None: self.lastPos = newPos else: self.lastPos = self.mouse.getPos() # for counting clicks: self.clickOnUp = clickOnUp self.wasDown = False # state of mouse 1 frame prior to current frame self.clicks = 0 # how many mouse clicks since last reset self.clickButton = 0 # which button to count clicks for; 0 = left # set autoLog now that params have been initialised wantLog = autoLog is None and self.win.autoLog self.__dict__['autoLog'] = autoLog or wantLog if self.autoLog: logging.exp("Created %s = %s" % (self.name, str(self)))