def __init__(self, win, borderThickness=.003, labelSize=0.03, pos=(0, 0), labelText="text for button", textColor='blue', borderColor='blue', buttonColor='white', buttonEnabled=False, ): # local variables super(ButtonStim, self).__init__(win) button_width = len(labelText) * .025 button_x_inner_margin = .02 button_x_outer_margin = button_x_inner_margin + borderThickness button_y_inner_margin = labelSize button_y_outer_margin = labelSize + borderThickness button_x_range = (0 - button_width / 2 + pos[0], 0 + button_width / 2 + pos[0]) self.win = win self.borderThickness = borderThickness self.labelSize = labelSize self.pos = pos self.labelText = labelText self.textColor = textColor self.borderColor = borderColor self.buttonColor = buttonColor self.buttonEnabled = buttonEnabled self._dragging = False self.mouse = event.Mouse() self.buttonSelected = False self.buttonItems = [] self.buttonBorder = BaseShapeStim(self.win, fillColor=self.borderColor, vertices=( (button_x_range[0] - button_x_outer_margin, -button_y_outer_margin + self.pos[1]), (button_x_range[0] - button_x_outer_margin, button_y_outer_margin + self.pos[1]), (button_x_range[1] + button_x_outer_margin, button_y_outer_margin + self.pos[1]), (button_x_range[1] + button_x_outer_margin, -button_y_outer_margin + self.pos[1]))) self.buttonInner = BaseShapeStim(self.win, fillColor=self.buttonColor, vertices=( (button_x_range[0] - button_x_inner_margin, -button_y_inner_margin + self.pos[1]), (button_x_range[0] - button_x_inner_margin, button_y_inner_margin + self.pos[1]), (button_x_range[1] + button_x_inner_margin, button_y_inner_margin + self.pos[1]), (button_x_range[1] + button_x_inner_margin, -button_y_inner_margin + self.pos[1]))) self.buttonInnerText = TextStim(self.win, text=self.labelText, color=self.textColor, pos=self.pos, height=self.labelSize) self.buttonItems.append(self.buttonBorder) self.buttonItems.append(self.buttonInner) self.buttonItems.append(self.buttonInnerText)
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)
class Experiment(object): linewidth = 3 radius = 0.75 fix_radius = 0.1 n_fleur_vertex = 10 refresh_rate = 60 cue_sec = 1 dot_sec = 3 n_flips_per_dots = dot_sec * refresh_rate fix_sec = 0.5 dot_speed_deg_per_sec = 1.67 dotsize_deg = 0.08 ndots = 400 dotfield_diameter_deg = 3.2 dot_coherence = 1 feedback_sec = 0.5 initial_pause_sec = 0.5 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) @property def win(self) -> visual.Window: return self.__win @win.setter def win(self, win: visual.Window) -> None: self.__win = win @win.deleter def win(self) -> None: self.__win.close() del self.__win def __prep_df(self, design: TextIO) -> pd.DataFrame: d = pd.read_csv(design) d_sub = (d.loc[d['sub']==self.sub] .sort_values(by=['block','trial']) .assign( fix_start = float('NaN'), cue_start = float('NaN'), dots_start = float('NaN'), dots_end = float('NaN'), response_time = float('NaN'), feedback_start = float('NaN'), correct = None, response_key = '', a = lambda x: x.chroma * np.cos(np.radians(x.hue)), b = lambda x: x.chroma * np.sin(np.radians(x.hue)), rgb = lambda x: cst.cielab2rgb( x.loc[:,['lightness','a','b']], # whiteXYZ=[0.9642, 1.0, 0.8521], # conversionMatrix=rgb2xyz, clip=True).tolist())) d_sub['R'] = [x[0] for x in d_sub['rgb']] d_sub['G'] = [x[1] for x in d_sub['rgb']] d_sub['B'] = [x[2] for x in d_sub['rgb']] return d_sub.drop('rgb', axis=1) @property def trialdf(self) -> pd.DataFrame: return self.__trialdf @trialdf.setter def trialdf(self, df: pd.DataFrame) -> None: self.__trialdf = df @property def io(self) -> ioHubConnection: return self.__io @io.setter def io(self, iohub_config: dict) -> None: # Start the ioHub process. 'io' can now be used during the # # experiment to access iohub devices and read iohub device events. io = launchHubServer(**iohub_config) self.__io = io @io.deleter def io(self) -> None: self.__io.quit() del self.__io @staticmethod def solicit_demographics(no_demographics) -> Tuple[str, str, str, str]: dlg = Dlg(title="Demographics") dlg.addText('''The National Institute of Health requests basic demographic information (sex, ethnicity, race, and age) for clinical or behavioral studies, to the extent that this information is provided by research participants. You are under no obligation to provide this information. If you would rather not answer these questions, you will still receive full compensation for your participation in this study and the data you provide will still be useful for our research. ''') dlg.addField('sex at birth:', choices=['Female', 'Male', 'Other', 'Rather not say']) dlg.addField('ethnicity:', choices= ['Hispanic or Latino', 'Not Hispanic or Latino', 'Rather not say']) dlg.addField('race:', choices= ['American Indian/Alaska Native', 'Asian', 'Black or African American', 'Native Hawaiian or Other Pacific Islander', 'White', 'Rather not say']) dlg.addField('age:', choices=[x for x in range(18, 51)] + ['Rather not say']) if not no_demographics: demographics = dlg.show() if dlg.OK == False: # user pressed cancel # fine to quit here, since nothing important has been opened core.quit() else: demographics = ['na', 'na', 'na', 'na'] return demographics @staticmethod def solicit_subid() -> int: dlg = Dlg(title="Participant") dlg.addField('ID:', choices=[x for x in range(0, 31)]) ID = dlg.show() if dlg.OK == False: # user pressed cancel # fine to quit here, since nothing important has been opened core.quit() return ID[0] @staticmethod def __make_vertex(angle: float, a: float = radius, b: float = radius/3) -> Tuple[float, float]: theta = math.atan2(a*math.tan(angle), b) return (a*math.cos(theta), b*math.sin(theta)) def __make_vertices(self) -> List[float]: vertices = [] vertices.extend([self.__make_vertex(angle) for angle in np.linspace(-math.pi/4, math.pi/4, num=self.n_fleur_vertex, endpoint=False)]) vertices.extend([self.__make_vertex(angle, a=self.radius/3, b=self.radius) for angle in np.linspace(math.pi/4, math.pi/2, num=self.n_fleur_vertex//2, endpoint=False)]) vertices.extend([self.__make_vertex(angle, a=self.radius/3, b=-self.radius) for angle in np.linspace(-math.pi/2, -math.pi/4, num=self.n_fleur_vertex//2, endpoint=False)]) vertices.extend([self.__make_vertex(angle, a=-self.radius) for angle in np.linspace(-math.pi/4, math.pi/4, num=self.n_fleur_vertex)]) vertices.extend([self.__make_vertex(angle, a=self.radius/3, b=-self.radius) for angle in np.linspace(math.pi/4, math.pi/2, num=self.n_fleur_vertex//2, endpoint=False)]) vertices.extend([self.__make_vertex(angle, a=self.radius/3, b=self.radius) for angle in np.linspace(-math.pi/2, -math.pi/4, num=self.n_fleur_vertex//2, endpoint=False)]) return vertices def __drawcue(self, shape) -> None: if shape == 'triangle': self.triangle.draw() elif shape == 'cross': self.line1.draw() self.line2.draw() elif shape == 'circle': self.circle.draw() else: self.fleur.draw() def __prep_dots(self, direction: float, rgb: Tuple[float, float, float]) -> None: self.dots.dir = direction self.dots.color = rgb @property def presses(self) -> dict: return self.__presses @presses.setter def presses(self, presses: Optional[dict]) -> None: if presses and presses[0].key == 'escape': sys.exit() else: self.__presses = presses # run the experiment def run(self) -> None: self.win.setMouseVisible(False) self.welcome.draw() self.win.flip() self.io.clearEvents() self.io.devices.keyboard.waitForPresses(keys=['c']) for block in self.trialdf.block.unique(): self.rest.text = f''' you are about to start part {block+1} of {self.trialdf.block.max()+1} in the experiment remember: cross and flower shapes mean categorize direction triangle and circle mean categorize color when you are categorizing direction, press "left" for upwards and "right" for downwards when you are categorizing color, press "left" for redder and "right" for greener when you are ready to begin this part, press "c" ''' self.rest.draw() self.win.flip() self.io.clearEvents() self.io.devices.keyboard.waitForPresses(keys=['c']) self.fix.draw() self.win.flip() self.waiter.start(self.initial_pause_sec) for __t in self.trialdf.query(f'block == {block}').itertuples(): trial = __t._asdict() # fixation self.fix.draw() self.waiter.complete() trial['fix_start'] = self.win.flip() self.waiter.start(self.fix_sec) self.__prep_dots(trial['direction'], (trial['R'], trial['G'], trial['B'])) # cue self.__drawcue(trial['shape']) self.fix.draw() self.waiter.complete() trial['cue_start'] = self.win.flip() self.waiter.start(self.cue_sec) # stim for flip in range(0, self.n_flips_per_dots): self.draw_list([self.dots, self.fix]) if flip == 0: self.waiter.complete() self.io.clearEvents() now = self.win.flip() self.io.sendMessageEvent(f'trial-{trial["trial"]}_flip-{flip}', category="flip", sec_time=now) if flip == 0: trial['dots_start'] = now self.presses = self.io.devices.keyboard.getPresses(keys=['left','right','escape']) if self.presses: break # record trial results (if any) and prepare for next trial trial['dots_end'] = now if self.presses: trial['response_time'] = self.presses[0].time trial['response_key'] = self.presses[0].key trial['correct'] = ( (trial['response_key'] == 'left' and ((trial['shape'] in ['triangle', 'circle'] and trial['hue'] <= 90) or (trial['shape'] in ['cross', 'fleur'] and trial['direction'] >= 0))) or (trial['response_key'] == 'right' and ((trial['shape'] in ['triangle', 'circle'] and trial['hue'] >= 90) or (trial['shape'] in ['cross', 'fleur'] and trial['direction'] <= 0)))) if trial['correct']: self.correct.draw() else: self.incorrect.draw() trial['feedback_start'] = self.win.flip() self.waiter.start(self.feedback_sec) # At the end of each trial, before getting # the next trial handler row, send the trial # variable states to iohub so they can be stored for future # reference. self.io.addTrialHandlerRecord(trial) if block == self.trialdf.block.max(): msg = ''' you have reached the end of the experiment! please go find the researcher and let them know you finished ''' else: msg = f''' congrats! you have just finished that part the time is currently {datetime.now().strftime("%H:%M")} please take a brief break when you are ready to continue, press "c" ''' self.rest.text = msg self.rest.draw() self.waiter.complete() self.win.flip() self.io.clearEvents() self.io.devices.keyboard.waitForPresses(keys=['c']) @staticmethod def draw_list(stims) -> None: for x in stims: x.draw() def instruct(self) -> None: self.win.setMouseVisible(False) text = visual.TextStim( win=self.win, text=''' in this experiment you will see a bunch of colored dots move together you will be asked to categorize either the color or the direction of motion press "c" to continue ''', pos=(0, 6), alignText="left", wrapWidth = 50) text.draw() self.win.flip() self.io.clearEvents() self.io.devices.keyboard.waitForPresses(keys=['c']) text = visual.TextStim( win=self.win, text=''' when you see these cues, categorize the motion when the dots are moving upwards, press "left" when the dots are moving downwards, press "right" press "c" to continue ''', pos=(0, 6), alignText="left", wrapWidth = 50) # motion cues self.line1.pos = (-1,0) self.line2.pos = (-1,0) self.fleur.pos = (1,0) self.dots.dir = 30 self.dots.color = cst.cielch2rgb([90,15,120], clip=True) text.draw() self.__drawcue('cross') self.__drawcue('fluer') self.win.flip() self.line1.pos = (0,0) self.line2.pos = (0,0) self.fleur.pos = (0,0) self.io.clearEvents() self.io.devices.keyboard.waitForPresses(keys=['c']) text.text = ''' here, the dots are moving upwards so you want to press "left" ''' self.io.clearEvents() while 1: self.draw_list([text, self.dots, self.fix]) self.win.flip() self.presses = self.io.devices.keyboard.getPresses(keys=['left','escape']) if self.presses: break # color cues text.text = ''' when you see these cues, categorize the color when the dots are more red, press "left" when the dots are more green, press "right press "c" to continue" ''' self.triangle.pos = (-1,0) self.circle.pos = (1,0) text.draw() self.__drawcue('triangle') self.__drawcue('circle') self.win.flip() self.io.clearEvents() self.triangle.pos = (0,0) self.circle.pos = (0,0) self.io.devices.keyboard.waitForPresses(keys=['c']) text.text = ''' here, the dots are more green so you want to press "right" ''' self.io.clearEvents() while 1: self.draw_list([text, self.dots, self.fix]) self.win.flip() self.presses = self.io.devices.keyboard.getPresses(keys=['right','escape']) if self.presses: break text.text = ''' in summary: cross and flower shapes mean categorize direction triangle and circle mean categorize color when you are categorizing direction, press "left" for upwards and "right" for downwards when you are categorizing color, press "left" for redder and "right" for greener in the actual experiment, everything will happen more quickly press "c" to continue to a regular trial ''' text.draw() self.win.flip() self.io.clearEvents() self.io.devices.keyboard.waitForPresses(keys=['c']) # fixation self.fix.draw() self.win.flip() self.waiter.start(self.fix_sec) # cue self.draw_list([self.triangle, self.fix]) self.waiter.complete() self.win.flip() self.waiter.start(self.cue_sec) # stim for flip in range(0, self.n_flips_per_dots): self.draw_list([self.dots, self.fix]) if flip == 0: self.waiter.complete() self.io.clearEvents() self.win.flip() self.presses = self.io.devices.keyboard.getPresses(keys=['right', 'escape']) if self.presses: break text.text = ''' lastly, a white dot will be visible throughout the experiment when it is visible, please keep your eyes focused on that dot press 'c' to conclude the instructions ''' self.io.clearEvents() self.draw_list([text, self.fix]) self.win.flip() self.io.clearEvents() self.io.devices.keyboard.waitForPresses(keys=['c'])
def _gen_orientation_labels( self ) -> typing.Tuple[visual.ImageStim, visual.ImageStim, visual.ImageStim, visual.ImageStim, visual.ImageStim]: """Draws and returns orientation labels, with position depending on outlines of body image and spacing depending on the font size. """ # To orient the קדימה/אחורה labels at the bottom of the images lowest_y = self.dotting_outlines[:, 1].min() # To orient the right-hand 'R' label to the right of the 'back' image rightmost_x = self.dotting_outlines[:, 0].max() # The matching 'y' value rightmost_y = self.dotting_outlines[np.argmax( self.dotting_outlines[:, 0] == rightmost_x), 1] # To orient the left-hand 'R' label to the left of the 'front' image leftmost_x = self.dotting_outlines[:, 0].min() # The matching 'y' value leftmost_y = self.dotting_outlines[np.argmax( self.dotting_outlines[:, 0] == leftmost_x), 1] # Controlling height of letters, wrap length and spacing height = params.TEXT_LABELS_SIZE * self.win.size[1] # center_label front_right_label = TextStim( win=self.win, height=height, units='pix', text='ימין', languageStyle='RTL', color='black', alignHoriz='left', pos=[ float(rightmost_x + leftmost_x) / 2 + 1.5 * params.TEXT_LABELS_SIZE * self.win.size[0], rightmost_y ], wrapWidth=height) # center_right_label front_left_label = TextStim( win=self.win, height=height, units='pix', pos=[ rightmost_x + 0.5 * params.TEXT_LABELS_SIZE * self.win.size[0], rightmost_y ], text='שמאל', languageStyle='RTL', alignHoriz='left', color='black', wrapWidth=height) # center_left_label back_left_label = TextStim( win=self.win, height=height, units='pix', pos=[ leftmost_x - 1.5 * params.TEXT_LABELS_SIZE * self.win.size[0], leftmost_y ], text='שמאל', languageStyle='RTL', color='black', wrapWidth=height) # new - back_right back_right_label = TextStim( win=self.win, height=height, units='pix', pos=[ float(rightmost_x + leftmost_x) / 2 - 2 * params.TEXT_LABELS_SIZE * self.win.size[0], rightmost_y ], text='ימין', languageStyle='RTL', color='black', wrapWidth=height) bottom_left_label = TextStim( win=self.win, height=height, units='pix', alignHoriz='center', pos=[(-self.win.size[0] * params.IMG_OFFSET[0]) - 12, lowest_y - height], text='אחורה', languageStyle='RTL', color='black', wrapWidth=height) bottom_right_label = TextStim( win=self.win, height=height, units='pix', alignHoriz='center', pos=[(self.win.size[0] * params.IMG_OFFSET[0]) - 12, lowest_y - height], text='קדימה', languageStyle='RTL', color='black', wrapWidth=height) return (front_right_label, front_left_label, back_right_label, back_left_label, bottom_left_label, bottom_right_label)
def __init__( self, win, text='Hello World', pos=(0.0, 0.0), width=None, height=None, padx=2, pady=2, units="", checked=False, name=None, autoLog=None, autoDraw=False, ): """ Button accepts all input parameters, that `~psychopy.visual.BaseVisualStim` accept, except for vertices and closeShape. :Parameters: width : int or float Width of the Rectangle (in its respective units, if specified) height : int or float Height of the Rectangle (in its respective units, if specified) """ # what local vars are defined (these are the init params) for use by # __repr__ self._initParams = dir() self._initParams.remove('self') super(Button, self).__init__(win, units=units, name=name, autoLog=False) self.__dict__['pos'] = pos self.__dict__['text'] = text self.textStim = TextStim(win, text=text, pos=self.pos, wrapWidth=width, color='Black', units=units, autoLog=autoLog) # TODO: expose content_width via TextStim autoWidth = (self.textStim._pygletTextObj._layout.content_width + 2 * padx) autoHeight = self.textStim.height + 2 * pady if width is not None and width > autoWidth: self.__dict__['width'] = width else: self.__dict__['width'] = autoWidth if height is not None and height > autoHeight: self.__dict__['height'] = height else: self.__dict__['height'] = autoHeight self.rectStim = Rect(win, pos=self.pos, width=self.width, height=self.height, units=units, autoLog=autoLog) self.normalStyle = ButtonStyle() self.checkedStyle = ButtonStyle(fillColor='#2E2EFE', borderColor='White', textColor='White') self.checked = checked # this will set style self.autoDraw = autoDraw self.__dict__['autoLog'] = (autoLog or autoLog is None and self.win.autoLog) if self.autoLog: logging.exp("Created %s = %s" % (self.name, str(self)))
class Button(BaseVisualStim): """Creates a button of given width and height, by combining a TextStim and a Rect (New in version 1.80.99 FIXME) """ def __init__( self, win, text='Hello World', pos=(0.0, 0.0), width=None, height=None, padx=2, pady=2, units="", checked=False, name=None, autoLog=None, autoDraw=False, ): """ Button accepts all input parameters, that `~psychopy.visual.BaseVisualStim` accept, except for vertices and closeShape. :Parameters: width : int or float Width of the Rectangle (in its respective units, if specified) height : int or float Height of the Rectangle (in its respective units, if specified) """ # what local vars are defined (these are the init params) for use by # __repr__ self._initParams = dir() self._initParams.remove('self') super(Button, self).__init__(win, units=units, name=name, autoLog=False) self.__dict__['pos'] = pos self.__dict__['text'] = text self.textStim = TextStim(win, text=text, pos=self.pos, wrapWidth=width, color='Black', units=units, autoLog=autoLog) # TODO: expose content_width via TextStim autoWidth = (self.textStim._pygletTextObj._layout.content_width + 2 * padx) autoHeight = self.textStim.height + 2 * pady if width is not None and width > autoWidth: self.__dict__['width'] = width else: self.__dict__['width'] = autoWidth if height is not None and height > autoHeight: self.__dict__['height'] = height else: self.__dict__['height'] = autoHeight self.rectStim = Rect(win, pos=self.pos, width=self.width, height=self.height, units=units, autoLog=autoLog) self.normalStyle = ButtonStyle() self.checkedStyle = ButtonStyle(fillColor='#2E2EFE', borderColor='White', textColor='White') self.checked = checked # this will set style self.autoDraw = autoDraw self.__dict__['autoLog'] = (autoLog or autoLog is None and self.win.autoLog) if self.autoLog: logging.exp("Created %s = %s" % (self.name, str(self))) def _setStyle(self, style): self.rectStim.lineColor = style.borderColor self.rectStim.fillColor = style.fillColor self.textStim.color = style.textColor @attributeSetter def text(self, value): """Changes the text of the Button""" self.__dict__['text'] = value # TODO: change this to attributeSetters with 1.80.99 self.textStim.setText(value) @attributeSetter def pos(self, value): """Changes the position of the Button""" self.__dict__['pos'] = value self.rectStim.pos = value self.textStim.pos = value @attributeSetter def width(self, value): """Changes the width of the Button""" self.__dict__['width'] = value # TODO: change this to attributeSetters with 1.80.99 self.rectStim.setWidth(value) # this won't work, need to construct new or wait for 1.80.99 self.textStim.wrapWidth = value @attributeSetter def height(self, value): """Changes the height of the Button""" self.__dict__['height'] = value # TODO: change this to attributeSetters with 1.80.99 self.rectStim.setHeight(value) # don't set height of text as it will change font size # self.textStim.setHeight(value) @attributeSetter def checked(self, value): self.__dict__['checked'] = value if self.checked: self._setStyle(self.checkedStyle) else: self._setStyle(self.normalStyle) def setColor(self, color, colorSpace=None, operation=''): """For Button use :meth:`~Button.fillColor` or :meth:`~Button.borderColor` or :meth:`~Button.textColor` """ raise AttributeError('Button does not support setColor method.' 'Please use fillColor, borderColor, or textColor') def contains(self, x, y=None, units=None): return self.rectStim.contains(x, y, units) def draw(self, win=None): """ Draw the stimulus in its relevant window. You must call this method after every MyWin.flip() if you want the stimulus to appear on that frame and then update the screen again. If win is specified then override the normal window of this stimulus. """ if win is None: win = self.win self.rectStim.draw(win) self.textStim.draw(win)