def __init__(self, bar, button_text, button_size, location, callback=None): super(Button, self).__init__() super(EnvAgent, self).__init__() self.bar = bar self.size = button_size self.button_text = button_text self.button_rtext_a = message(button_text, "button_active", blit_txt=False) self.button_rtext_i = message(button_text, "button_inactive", blit_txt=False) self.frame_i = Rectangle(button_size[0], button_size[1], fill=None, stroke=(5, (255, 255, 255))) self.frame_a = Rectangle(button_size[0], button_size[1], fill=None, stroke=(5, (150, 255, 150))) self.active = False self.location = location self.text_location = (self.location[0] + self.size[0] // 2, self.location[1] + self.size[1] // 2) self.create_boundary() self.callback = callback
def __init__(self, y_pos, bar_length, bar_height, handle_radius, bar_fill, handle_fill): BoundaryInspector.__init__(self) EnvAgent.__init__(self) self.boundaries = {} self.pos = (P.screen_c[0] - bar_length // 2, y_pos) # upper-left self.message_pos = (P.screen_c[0], y_pos - 50) self.__handle_pos = (self.pos[0], self.pos[1] + bar_height // 2) self.handle_color = handle_fill self.handle_radius = handle_radius self.handle_stroke = None self.handle_boundary = None self.bar = None self.bar_size = (bar_length, bar_height) self.bar_color = bar_fill self.bar_stroke = None self.show_increment_ticks = True self.show_increment_text = False self.increment_count = None self.increments = [] self.increment_by = 1 self.increment_surfs = {} self.lower_bound = None self.upper_bound = None self.handle = Ellipse(self.handle_radius * 2, fill=self.handle_color, stroke=self.handle_stroke) self.bar = Rectangle(self.bar_size[0], self.bar_size[1], fill=self.bar_color, stroke=self.bar_stroke) self.add_boundary("handle", [ (self.pos[0] + self.handle_radius, self.pos[1]), self.handle_radius ], CIRCLE_BOUNDARY) self.msg = message("How many corners did the dot traverse?", "default", blit_txt=False) self.lb_msg = None self.ub_msg = None self.ok_text = message("OK", blit_txt=False) self.ok_inactive_button = Rectangle(100, 50, stroke=(1, (255, 255, 255)), fill=(125, 125, 125)).render() self.ok_active_button = Rectangle(100, 50, stroke=(1, (255, 255, 255)), fill=(5, 175, 45)).render() self.button_active = False self.button_pos = (P.screen_c[0], y_pos + bar_height + 50) button_upper_left = (self.button_pos[0] - 50, self.button_pos[1] - 25) button_botton_right = (self.button_pos[0] + 50, self.button_pos[1] + 25) self.add_boundary("button", (button_upper_left, button_botton_right), RECT_BOUNDARY) self.response = None
def construct_placeholder(self): stroke = [self.rect_thickness, WHITE, STROKE_CENTER] # width = None # height = None # Horizontal/vertical indicates directionality of placeholders length if self.box_alignment == VERTICAL: width, height = self.rect_short_side, self.rect_long_side else: height, width = self.rect_short_side, self.rect_long_side return Rectangle(width=width, height=height, stroke=stroke)
def __init__(self, data): self.media_type = IMAGE_FILE self.height = None self.width = None self.duration = None if data.text: # todo: make style optional self.contents = message(data.text.string, data.text.style, align="center", blit_txt=False) elif data.drawbject: d = data.drawbject if d.shape == "rectangle": self.contents = Rectangle(d.width, d.height, d.stroke, d.fill).render() if d.shape == "ellipse": self.contents = Ellipse(d.width, d.height, d.stroke, d.fill).render() if d.shape == "annulus": self.contents = Annulus(d.diameter, d.ring_width, d.stroke, d.fill).render() else: self.media_type = data.file.media_type if self.is_audio: self.duration = data.file self.contents = AudioClip( join(P.resources_dir, "audio", data.file.filename)) else: # If asset is image file, import and scale for current screen size (animations # originally hard-coded at 1920x1080 so we scale relative to that) img = Image.open(join(P.image_dir, data.file.filename)) target_size = (P.screen_x, (P.screen_x / 16.0) * 9 ) # corrected for aspect ratio scaled_size = scale(img.size, (1920, 1080), target_size, center=False) self.contents = np.asarray( img.resize(scaled_size, Image.BILINEAR)) try: self.height = self.contents.height self.width = self.contents.width except AttributeError: try: self.height = self.contents.shape[0] self.width = self.contents.shape[1] except AttributeError: pass # ie. audio file
def setup(self): # Generate messages to be displayed during experiment self.err_msgs = {} if P.saccade_response_cond: self.err_msgs['eye'] = "Moved eyes too soon!" self.err_msgs['key'] = "Please respond with eye movements only." self.err_msgs['early'] = self.err_msgs[ 'key'] # for convenience in logic else: self.err_msgs['eye'] = "Moved eyes!" self.err_msgs['key'] = "Please respond with the spacebar only." self.err_msgs['early'] = "Responded too soon!" # Stimulus sizes self.target_width = deg_to_px( 0.5, even=True) # diameter of target circle (0.5 degrees) self.cue_size = deg_to_px( 0.5, even=True) # size of asterisk/fixations (0.5 degrees) self.box_size = deg_to_px( 0.8, even=True) # size of placeholder boxes (0.8 degrees) # Stimulus Drawbjects self.box = Rectangle(self.box_size, stroke=(2, WHITE)).render() self.cross_r = FixationCross(self.cue_size, 2, fill=RED).render() self.cross_w = FixationCross(self.cue_size, 2, fill=WHITE).render() self.circle = Circle(self.target_width, fill=WHITE).render() self.asterisk = SquareAsterisk(self.cue_size, 2, fill=WHITE).render() # Layout of stimuli # offset between centre of boxes and centre of screen, in degrees offset_size_deg = P.dm_offset_size if P.development_mode else 7.0 self.offset_size = deg_to_px(offset_size_deg) self.target_locs = { TOP: (P.screen_c[0], P.screen_c[1] - self.offset_size), RIGHT: (P.screen_c[0] + self.offset_size, P.screen_c[1]), BOTTOM: (P.screen_c[0], P.screen_c[1] + self.offset_size), LEFT: (P.screen_c[0] - self.offset_size, P.screen_c[1]) } # prepare all animation locations for both rotation directions and starting axes self.animation_frames = 15 animation_duration = 300 # ms self.frame_duration = animation_duration / self.animation_frames rotation_increment = (pi / 2) / self.animation_frames cx, cy = P.screen_c self.frames = { V_START_AXIS: { ROT_CW: [], ROT_CCW: [] }, H_START_AXIS: { ROT_CW: [], ROT_CCW: [] } } for i in range(0, self.animation_frames): l_x_cw = -self.offset_size * cos(i * rotation_increment) l_y_cw = self.offset_size * sin(i * rotation_increment) l_x_ccw = self.offset_size * cos(i * rotation_increment) l_y_ccw = -self.offset_size * sin(i * rotation_increment) cw_locs = [(cx + l_x_cw, cy + l_y_cw), (cx - l_x_cw, cy - l_y_cw)] ccw_locs = [(cx + l_x_ccw, cy - l_y_ccw), (cx - l_x_ccw, cy + l_y_ccw)] self.frames[H_START_AXIS][ROT_CW].append(ccw_locs) self.frames[H_START_AXIS][ROT_CCW].append(cw_locs) self.frames[V_START_AXIS][ROT_CW].insert(0, cw_locs) self.frames[V_START_AXIS][ROT_CCW].insert(0, ccw_locs) # Define keymap for ResponseCollector self.keymap = KeyMap( "speeded response", # Name ["spacebar"], # UI Label ["spacebar"], # Data Label [SDLK_SPACE] # SDL2 Keycode ) # Boundaries for stimuli self.fixation_boundary = deg_to_px( 3.0) # radius of 3 degrees of visual angle self.el.add_boundary("drift_correct", [P.screen_c, self.fixation_boundary], CIRCLE_BOUNDARY)
def setup(self): # Set up custom text styles for the experiment self.txtm.add_style('instructions', 18, [255, 255, 255, 255]) self.txtm.add_style('error', 18, [255, 0, 0, 255]) self.txtm.add_style('tiny', 12, [255, 255, 255, 255]) self.txtm.add_style('small', 14, [255, 255, 255, 255]) # Pre-render shape stimuli dot_stroke = [P.dot_stroke, P.dot_stroke_col, STROKE_OUTER ] if P.dot_stroke > 0 else None self.tracker_dot = Ellipse(P.dot_size, stroke=dot_stroke, fill=P.dot_color).render() self.origin_active = Ellipse(P.origin_size, fill=self.origin_active_color).render() self.origin_inactive = Ellipse( P.origin_size, fill=self.origin_inactive_color).render() # If capture figures mode, generate, view, and optionally save some figures if P.capture_figures_mode: self.fig_dir = os.path.join(P.resources_dir, "figures") self.capture_figures() self.quit() # Initialize participant ID and session options, reloading ID if it already exists self.session = TraceLabSession() self.user_id = self.session.user_id # Once session initialized, show loading screen and finish setup self.loading_msg = message("Loading...", "default", blit_txt=False) fill() blit(self.loading_msg, 5, P.screen_c) flip() # Scale UI size variables to current screen resolution P.btn_s_pad = scale((P.btn_s_pad, 0), (1920, 1080))[0] P.y_pad = scale((0, P.y_pad), (1920, 1080))[1] # Initialize messages and response buttons for control trials control_fail_txt = "Please keep your finger on the start area for the complete duration." self.control_fail_msg = message(control_fail_txt, 'error', blit_txt=False) ctrl_buttons = ["1", "2", "3", "4", "5"] self.control_bar = ButtonBar(buttons=[(i, P.btn_size, None) for i in ctrl_buttons], button_size=P.btn_size, screen_margins=P.btn_s_pad, y_offset=P.y_pad, message_txt=P.control_q) # Initialize 'next trial' button button_x = 250 if self.handedness == LEFT_HANDED else P.screen_x - 250 button_y = P.screen_y - 100 self.next_trial_msg = message(P.next_trial_message, 'default', blit_txt=False) self.next_trial_box = Rectangle(300, 75, stroke=(2, (255, 255, 255), STROKE_OUTER)) self.next_trial_button_loc = (button_x, button_y) bounds = [(button_x - 150, button_y - 38), (button_x + 150, button_y + 38)] self.add_boundary("next trial button", bounds, RECT_BOUNDARY) # Initialize instructions and practice button bar for each condition self.instruction_files = { PHYS: { 'text': "physical_group_instructions.txt", 'frames': "physical_key_frames" }, MOTR: { 'text': "imagery_group_instructions.txt", 'frames': "imagery_key_frames" }, CTRL: { 'text': "control_group_instructions.txt", 'frames': "control_key_frames" } } self.practice_instructions = message(P.practice_instructions, "instructions", align="center", blit_txt=False) practice_buttons = [('Replay', [200, 100], self.practice), ('Practice', [200, 100], self.__practice__), ('Begin', [200, 100], any_key)] self.practice_button_bar = ButtonBar(practice_buttons, [200, 100], P.btn_s_pad, P.y_pad, finish_button=False) # Import all pre-generated figures needed for the current session figures = list(set(self.trial_factory.exp_factors["figure_name"])) figures.append(P.practice_figure) for f in figures: if f != "random": ui_request() fig_path = os.path.join(P.resources_dir, "figures", f) self.test_figures[f] = TraceLabFigure(fig_path)
def setup(self): # Bandit Variables self.high_payout_baseline = 12 self.low_payout_baseline = 8 self.total_score = None self.penalty = -5 # Stimulus Sizes thick_rect_border = deg_to_px(0.5) thin_rect_border = deg_to_px(0.1) star_size = deg_to_px(0.6) star_thickness = deg_to_px(0.1) square_size = deg_to_px(3) text_size = deg_to_px(0.65) large_text_size = deg_to_px(0.85) # Generate bandit colours from colour wheel self.bandit_colour_combos = [] if P.blocks_per_experiment > 4: msg = ( "Only 4 sets of colours available, experiment script must be modified if more" "than 4 blocks total are wanted.") raise RuntimeError(msg) for angle in [0, 45, 90, 135]: combo = [const_lum[angle], const_lum[angle + 180]] self.bandit_colour_combos.append(combo) random.shuffle(self.bandit_colour_combos) # Stimulus Drawbjects self.thick_rect = Rectangle( square_size, stroke=[thick_rect_border, WHITE, STROKE_CENTER]) self.thin_rect = Rectangle( square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER]) self.left_bandit = Rectangle( square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER]) self.right_bandit = Rectangle( square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER]) self.neutral_box = self.thin_rect.render() self.star = Asterisk(star_size, star_thickness, fill=WHITE) self.star_cueback = Asterisk(star_size * 2, star_thickness * 2, fill=WHITE) self.star_muted = Asterisk(star_size, star_thickness, fill=GREY) self.probe = Ellipse(int(0.75 * square_size), fill=WHITE).render() # Layout box_offset = deg_to_px(8.0) self.left_box_loc = (P.screen_c[0] - box_offset, P.screen_c[1]) self.right_box_loc = (P.screen_c[0] + box_offset, P.screen_c[1]) # Timing # Note: cotoa = cue-offset target-onset asynchrony self.cotoa_min = 700 # ms self.cotoa_max = 1000 # ms self.feedback_exposure_period = 1.25 # sec # EyeLink Boundaries fix_bounds = [P.screen_c, square_size / 2] self.el.add_boundary('fixation', fix_bounds, CIRCLE_BOUNDARY) # Experiment Messages self.txtm.styles[ 'default'].font_size = text_size # re-define default font size in degrees self.txtm.add_style("score up", large_text_size, PASTEL_GREEN) self.txtm.add_style("score down", large_text_size, PASTEL_RED) self.txtm.add_style("timeout", large_text_size, WHITE) err_txt = "{0}\n\nPress any key to continue." lost_fixation_txt = err_txt.format( "Eyes moved! Please keep your eyes on the asterisk.") too_soon_txt = err_txt.format( "Responded too soon! Please wait until the 'go' signal to " "make a response.") probe_timeout_txt = err_txt.format( "No response detected! Please answer louder or faster.") bandit_timeout_txt = err_txt.format("Bandit selection timed out!") wrong_response_txt = err_txt.format( "Wrong response type!\nPlease make vocal responses " "to probes and keypress responses to bandits.") self.err_msgs = { 'fixation': message(lost_fixation_txt, align='center', blit_txt=False), 'too_soon': message(too_soon_txt, align='center', blit_txt=False), 'probe_timeout': message(probe_timeout_txt, 'timeout', align='center', blit_txt=False), 'bandit_timeout': message(bandit_timeout_txt, 'timeout', align='center', blit_txt=False), 'wrong_response': message(wrong_response_txt, align='center', blit_txt=False) } # Initialize separate ResponseCollectors for probe and bandit responses self.probe_rc = ResponseCollector(uses=[RC_AUDIO, RC_KEYPRESS]) self.bandit_rc = ResponseCollector(uses=[RC_AUDIO, RC_KEYPRESS]) # Initialize ResponseCollector keymap self.keymap = KeyMap( 'bandit_response', # Name ['z', '/'], # UI labels ["left", "right"], # Data labels [sdl2.SDLK_z, sdl2.SDLK_SLASH] # SDL2 Keysyms ) # Add practice block of 20 trials to start of experiment if P.run_practice_blocks: self.insert_practice_block(1, trial_counts=20)
def setup(self): # Stimulus sizes thick_rect_border = deg_to_px(0.5) thin_rect_border = deg_to_px(0.1) star_size = deg_to_px(0.6) star_thickness = deg_to_px(0.1) square_size = deg_to_px(3) large_text_size = 0.65 # Stimulus drawbjects self.thick_rect = Rectangle( square_size, stroke=[thick_rect_border, WHITE, STROKE_CENTER]) self.thin_rect = Rectangle( square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER]) self.neutral_box = self.thin_rect.render() self.star = Asterisk(star_size, star_thickness, fill=WHITE) self.star_cueback = Asterisk(star_size * 2, star_thickness * 2, fill=WHITE) self.go = FixationCross(star_size, star_thickness, fill=BLACK) self.go.render() self.nogo = FixationCross(star_size, star_thickness, fill=BLACK, rotation=45) self.nogo.render() self.left_bandit = Ellipse(int(0.75 * square_size)) self.right_bandit = Ellipse(int(0.75 * square_size)) self.probe = Ellipse(int(0.75 * square_size)) # Layout box_offset = deg_to_px(8.0) self.left_box_loc = (P.screen_c[0] - box_offset, P.screen_c[1]) self.right_box_loc = (P.screen_c[0] + box_offset, P.screen_c[1]) # Set cotoa self.cotoa = 800 # ms self.feedback_exposure_period = 1.25 # sec # Bandit payout variables self.high_payout_baseline = 12 self.low_payout_baseline = 8 self.total_score = None self.penalty = -5 # Generate colours from colour wheel self.target_colours = [const_lum[0], const_lum[120], const_lum[240]] random.shuffle(self.target_colours) # Assign to bandits & neutral probe self.high_value_colour = self.target_colours[0] self.low_value_colour = self.target_colours[1] self.neutral_value_colour = self.target_colours[2] # EyeLink Boundaries fix_bounds = [P.screen_c, square_size / 2] self.el.add_boundary('fixation', fix_bounds, CIRCLE_BOUNDARY) # Initialize response collectors self.probe_rc = ResponseCollector(uses=RC_KEYPRESS) self.bandit_rc = ResponseCollector(uses=RC_KEYPRESS) # Initialize ResponseCollector keymaps self.bandit_keymap = KeyMap( 'bandit_response', # Name ['z', '/'], # UI labels ["left", "right"], # Data labels [sdl2.SDLK_z, sdl2.SDLK_SLASH] # SDL2 Keysyms ) self.probe_keymap = KeyMap('probe_response', ['spacebar'], ["pressed"], [sdl2.SDLK_SPACE]) # Experiment Messages self.txtm.add_style("payout", large_text_size, WHITE) self.txtm.add_style("timeout", large_text_size, WHITE) err_txt = "{0}\n\nPress any key to continue." lost_fixation_txt = err_txt.format( "Eyes moved! Please keep your eyes on the asterisk.") probe_timeout_txt = err_txt.format( "No response detected! Please respond as fast and as accurately as possible." ) bandit_timeout_txt = err_txt.format("Bandit selection timed out!") response_on_nogo_txt = err_txt.format( "\'nogo\' signal (x) presented\nPlease only respond when you see " "the \'go\' signal (+).") self.err_msgs = { 'fixation': message(lost_fixation_txt, align='center', blit_txt=False), 'probe_timeout': message(probe_timeout_txt, 'timeout', align='center', blit_txt=False), 'bandit_timeout': message(bandit_timeout_txt, 'timeout', align='center', blit_txt=False), 'response_on_nogo': message(response_on_nogo_txt, align='center', blit_txt=False) } self.rest_break_txt = err_txt.format( "Whew! that was tricky eh? Go ahead and take a break before continuing." ) self.end_of_block_txt = "You're done the first task! Please buzz the researcher to let them know!" # Insert bandit block if P.run_practice_blocks: self.insert_practice_block(1, trial_counts=P.trials_bandit_block)