def prepare_spatial_array(self): spatial_array_locs = list(self.spatial_array_locs) random.shuffle(spatial_array_locs) self.item_locs = [] jitter = [x * 0.1 for x in range(-3, 4)] for i in range(0, self.set_size): x_jitter = deg_to_px(random.choice(jitter)) y_jitter = deg_to_px(random.choice(jitter)) loc = spatial_array_locs.pop() loc[0], loc[1] = loc[0] + x_jitter, loc[1] + y_jitter self.item_locs.append(loc) spatial_array = [] if self.present_absent == PRESENT: self.target_loc = self.item_locs.pop() spatial_array.append([self.target_item, self.target_loc]) for loc in self.item_locs: distractor = random.choice(self.distractors) spatial_array.append([distractor, loc]) return spatial_array
def construct_cue(self): self.cue_seg_len = deg_to_px(1.7) self.cue_seg_thick = deg_to_px(0.4) canvas_size = [self.cue_seg_len, self.cue_seg_len] canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) surface = aggdraw.Draw(canvas) pen = aggdraw.Pen(WHITE, self.cue_seg_len) # Regardless of orientation, two segments remain the same xy = [(0, 0, 0, self.cue_seg_len), (0, 0, self.cue_seg_len, 0)] # Add missing segment, dependent on orientation if self.box_alignment == VERTICAL: xy.append((self.cue_seg_len, self.cue_seg_len, 0, self.cue_seg_len)) else: xy.append((self.cue_seg_len, 0, self.cue_seg_len, self.cue_seg_len)) for seg in xy: surface.line(seg, pen) surface.flush() return np.asarray(canvas)
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_diameter = deg_to_px(1.0) self.cue_seg_len = deg_to_px(1.7) self.cue_seg_thick = deg_to_px(0.4) self.rect_long_side = deg_to_px(11.4) self.rect_short_side = deg_to_px(1.7) self.rect_thickness = deg_to_px(0.2) self.fix_width = deg_to_px(1.0) self.fix_thickness = deg_to_px(0.2) # Stimulus drawbjects # NOTE: too many properties of placeholders & cue vary trial-by-trial, easier to construct during trial_prep() self.fix = FixationCross(size=self.fix_width, thickness=self.fix_thickness, fill=RED) self.target = Circle(diameter=self.target_diameter, fill=WHITE) # Offset between center of placeholders & fixation offset = deg_to_px(4.8) # Use offsets to establish possible stimulus locations self.locations = { "left": [P.screen_c[0] - offset, P.screen_c[1]], 'right': [P.screen_c[0] + offset, P.screen_c[1]], 'top': [P.screen_c[0], P.screen_c[1] - offset], 'bottom': [P.screen_c[0], P.screen_c[1] + offset], 'top_left': [P.screen_c[0] - offset, P.screen_c[1] - offset], 'top_right': [P.screen_c[0] + offset, P.screen_c[1] - offset], 'bottom_left': [P.screen_c[0] - offset, P.screen_c[1] + offset], 'bottom_right': [P.screen_c[0] + offset, P.screen_c[1] + offset] } # Define keymap for ResponseCollector self.keymap = KeyMap( "speeded response", # Name ["spacebar"], # UI Label ["spacebar"], # Data Label [SDLK_SPACE] # SDL2 Keycode ) # Instantiate boundary inspector to handle drift checks & target acquisitions (for saccade responses) self.bi = BoundaryInspector() self.gaze_boundary = deg_to_px(3.0) # Radius of 3.0º of visual angle self.bi.add_boundary(label="drift_correct", bounds=[P.screen_c, self.gaze_boundary], shape="Circle")
def render_texture(self, texture_figure, orientation=None): grid_size = (deg_to_px(self.stim_size) + self.stim_pad) * 2 stim_offset = self.stim_pad // 2 dc = Image.new('RGB', (grid_size, grid_size), NEUTRAL_COLOR[:3]) stroke_width = 2 #px grid_cell_size = deg_to_px(self.bg_element_size + self.bg_element_pad) grid_cell_count = grid_size // grid_cell_size stim_offset += (grid_size % grid_cell_size) // 2 # split grid_size %% cells over pad # Visual Representation of the Texture Rendering Logic # <-------G--------> # _______________ ^ # | O | | O = element_offset, ie. 1/2 bg_element_padding # | _____ | | E = element (ie. circle, square, D, etc.) # | | | | | G = one grid length # | O | E | O | G # | |_____| | | # | | | # |_______O_______| | # v element_offset = self.bg_element_pad // 2 # so as to apply padding equally on all sides of bg elements for col in range(0, grid_cell_count): for row in range(0, grid_cell_count): ui_request() top_out = int(row * grid_cell_size + element_offset + stim_offset) top_in = top_out + stroke_width # ie. top_inner left_out = int(col * grid_cell_size + element_offset + stim_offset) left_in = left_out+ stroke_width bottom_out = int(top_out + deg_to_px(self.bg_element_size)) bottom_in = bottom_out - stroke_width right_out = int(left_out + deg_to_px(self.bg_element_size)) right_in = right_out - stroke_width if texture_figure == CIRCLE: ImageDraw.Draw(dc, 'RGB').ellipse((left_out, top_out, right_out, bottom_out), WHITE[:3]) ImageDraw.Draw(dc, 'RGB').ellipse((left_in, top_in, right_in, bottom_in), NEUTRAL_COLOR[:3]) elif texture_figure == SQUARE: ImageDraw.Draw(dc, 'RGB').rectangle((left_out, top_out, right_out, bottom_out), WHITE[:3]) ImageDraw.Draw(dc, 'RGB').rectangle((left_in, top_in, right_in, bottom_in), NEUTRAL_COLOR[:3]) elif texture_figure is False: half_el_width = int(0.5 * deg_to_px(self.bg_element_size)) rect_right = right_out - half_el_width ImageDraw.Draw(dc, 'RGB', ).ellipse((left_out, top_out, right_out, bottom_out), WHITE[:3]) ImageDraw.Draw(dc, 'RGB', ).ellipse((left_in, top_in, right_in, bottom_in), NEUTRAL_COLOR[:3]) ImageDraw.Draw(dc, 'RGB', ).rectangle((left_out, top_out, rect_right, bottom_out), WHITE[:3]) ImageDraw.Draw(dc, 'RGB', ).rectangle((left_in, top_in, rect_right, bottom_in), NEUTRAL_COLOR[:3]) dc = dc.resize((grid_size // 2, grid_size // 2), Image.ANTIALIAS) dc = dc.rotate(orientation) return dc
def present_spatial_array(self, spatial_array): fill() blit(self.fixation, registration=5, location=P.screen_c) if P.development_mode: c = kld.Annulus(deg_to_px(1.0), deg_to_px(0.1), fill=(255, 0, 0, 255)) blit(c, registration=5, location=self.target_loc) for item in spatial_array: blit(item[0], registration=5, location=item[1]) flip()
def present_target(self): msg = "This is your target!" msg_loc = [P.screen_c[0], P.screen_c[1] - deg_to_px(2.0)] fill() message(msg, location=msg_loc, registration=5) blit(self.target_item, location=P.screen_c, registration=5) flip()
def setup(self): # Stimulus sizes fixation_size = deg_to_px(0.32) fixation_thickness = deg_to_px(0.08) cue_size = deg_to_px(0.64) cue_thickness = deg_to_px(0.08) arrow_tail_len = deg_to_px(0.48) arrow_tail_width = deg_to_px(0.15) arrow_head_len = deg_to_px(0.25) arrow_head_width = deg_to_px(0.45, even=True) arrow_dimensions = [ arrow_tail_len, arrow_tail_width, arrow_head_len, arrow_head_width ] # Stimuli self.warning_tone = Tone(50, 'sine', frequency=2000, volume=0.5) self.fixation = kld.FixationCross(fixation_size, fixation_thickness, fill=WHITE) self.cue = kld.Asterisk(cue_size, thickness=cue_thickness, fill=WHITE, spokes=8) self.arrow_r = kld.Arrow(*arrow_dimensions, fill=WHITE) self.arrow_l = kld.Arrow(*arrow_dimensions, fill=WHITE, rotation=180) self.arrow_r.render() self.arrow_l.render() # Layout self.height_offset = deg_to_px(1.3) self.flanker_offset = arrow_tail_len + arrow_head_len + deg_to_px(0.16) self.above_loc = (P.screen_c[0], P.screen_c[1] - self.height_offset) self.below_loc = (P.screen_c[0], P.screen_c[1] + self.height_offset) self.cue_locations = {'above': self.above_loc, 'below': self.below_loc} # Insert practice block (when applicable) if P.run_practice_blocks: self.insert_practice_block(block_nums=1, trial_counts=24) self.instructions_presented = False
def setup(self): # Stimulus sizes self.stim_pad = deg_to_px(self.stim_pad) self.txtm.add_style('q_and_a', 48, WHITE) # Generate masks, stimuli, and fixations for the experiment self.__generate_masks() self.__generate_stimuli() self.__generate_fixations() self.trial_start_msg = message("Press any key to advance...", 'default', blit_txt=False)
def font_size(self, size): if isinstance(size, str): unit = ''.join([i for i in size if not (i.isdigit() or i == '.')]) if len(unit): if unit not in ['pt', 'px', 'deg']: raise ValueError( "Font size unit must be either 'pt', 'px', or 'deg'") self.__font_size_units = unit size = float(''.join( [i for i in size if (i.isdigit() or i == '.')])) else: size = float(size) if self.__font_size_units == 'px': self._font_size = int(size * self.scale_factor) elif self.__font_size_units == 'deg': self._font_size = int(deg_to_px(size) * self.scale_factor) else: self._font_size = int(size) TTF_CloseFont(self.__font) self.__font = TTF_OpenFont(self.__fontpath, self._font_size)
def draw_nback_illustration(self, yloc, target): stims = { "A": message("A", 'stream', blit_txt=False), "B": message("B", 'stream', blit_txt=False), "C": message("C", 'stream', blit_txt=False), "->": self.arrow.render() } stim_sequence = ['A', '->', 'B', '->', 'C', '->', 'B', '->', 'B'] stim_spacing = deg_to_px(2.5) for i in range(0, len(stim_sequence)): s = stim_sequence[i] midpoint = len(stim_sequence) / 2.0 + 0.5 x_offset = int(stim_spacing * (i + 1 - midpoint)) y_offset = 4 if s == '->' else 0 # fixes arrow offsets blit(stims[s], 5, (P.screen_c[0] + x_offset, yloc + y_offset)) if (i + 2) / 2.0 == target: blit(self.target_box, 5, (P.screen_c[0] + x_offset + 1, yloc + 3))
def __generate_masks(self): smaller_than_screen = True while smaller_than_screen: self.maximum_mask_size += 1 new_max_mask_px = deg_to_px(self.maximum_mask_size) + self.mask_blur_width * 4 + 2 if new_max_mask_px > P.screen_y: smaller_than_screen = False self.maximum_mask_size -= 1 for size in self.trial_factory.exp_factors['mask_size']: if size > self.maximum_mask_size: e_str = "The maximum mask size this monitor can support is {0} degrees.".format(self.maximum_mask_size) raise ValueError(e_str) clear() msg = message("Rendering masks...", "q_and_a", blit_txt=False) blit(msg, 5, P.screen_c) flip() self.masks = {} for size in self.trial_factory.exp_factors['mask_size']: ui_request() self.masks["{0}_{1}".format(CENTRAL, size)] = self.render_mask(size, CENTRAL).render() self.masks["{0}_{1}".format(PERIPHERAL, size)] = self.render_mask(size, PERIPHERAL).render()
def render_mask(self, diameter, mask_type): MASK_COLOR = NEUTRAL_COLOR diameter = deg_to_px(diameter) blur_width = self.mask_blur_width if mask_type != "none": if mask_type == PERIPHERAL: bg_width = P.screen_x * 2 bg_height = P.screen_y * 2 inner_fill = TRANSPARENT outer_fill = OPAQUE elif mask_type == CENTRAL: bg_width = diameter + blur_width * 4 + 2 bg_height = bg_width inner_fill = OPAQUE outer_fill = TRANSPARENT # Create solid background bg = Image.new('RGB', (bg_width, bg_height), MASK_COLOR[:3]) # Create an alpha mask r = diameter // 2 x1 = (bg_width // 2) - r y1 = (bg_height // 2) - r x2 = (bg_width // 2) + r y2 = (bg_height // 2) + r alpha_mask = Image.new('L', (bg_width, bg_height), outer_fill) ImageDraw.Draw(alpha_mask).ellipse((x1, y1, x2, y2), fill=inner_fill) alpha_mask = alpha_mask.filter( ImageFilter.GaussianBlur(blur_width) ) # Apply mask to background and render bg.putalpha(alpha_mask) mask = aggdraw_to_numpy_surface(bg) return mask
def render_figure(self, figure_shape, orientation): stim_size_px = deg_to_px(self.stim_size) half_pad = self.stim_pad pad = 2 * self.stim_pad tl_fig = pad br_fig = 2 * stim_size_px rect_right = br_fig - stim_size_px dc_size = (stim_size_px + half_pad) * 2 dc = Image.new('L', (dc_size, dc_size), 0) if figure_shape == CIRCLE: ImageDraw.Draw(dc, 'L').ellipse((tl_fig, tl_fig, br_fig, br_fig), 255) if figure_shape == SQUARE: ImageDraw.Draw(dc, 'L').rectangle((tl_fig, tl_fig, br_fig, br_fig), 255) if figure_shape is False: ImageDraw.Draw(dc, 'L').ellipse((tl_fig, tl_fig, br_fig, br_fig), 255) ImageDraw.Draw(dc, 'L').rectangle((tl_fig, tl_fig, rect_right, br_fig), 255) if orientation > 0: dc = dc.rotate(orientation) cookie_cutter = dc.resize((dc_size // 2, dc_size // 2), Image.ANTIALIAS) return cookie_cutter
def trial_prep(self): # Determing the starting locations of the two target shapes if self.t1_location == "left": t1_x = self.left_x t2_x = self.right_x else: t1_x = self.right_x t2_x = self.left_x # Set shapes for t1 and t2 if self.t1_shape == "a": self.t1 = self.diamond_a self.t2 = self.diamond_b self.t1_line = self.line_b self.t2_line = self.line_a else: self.t1 = self.diamond_b self.t2 = self.diamond_a self.t1_line = self.line_a self.t2_line = self.line_b self.t1_pos = (t1_x, P.screen_c[1]) self.t2_pos = (t2_x, P.screen_c[1]) # Initialize start/end positions and animation paths if self.toj_type == "motion": self.start_offset = P.screen_y/4 + deg_to_px(random.uniform(-2, 2)) end_offset = deg_to_px(5.0) if self.upper_target == "t2": self.start_offset *= -1 end_offset *= -1 self.t1_reg = 8 self.t2_reg = 2 else: self.t1_reg = 2 self.t2_reg = 8 t1_start = (t1_x, P.screen_c[1]-self.start_offset) t1_end = (t1_x, P.screen_c[1]+end_offset) self.t1_path = Animation(t1_start, t1_end, self.motion_duration) t2_offset = self.t1_path.motion_per_ms[1] * self.t1_t2_soa t2_start = (t2_x, P.screen_c[1]+self.start_offset+t2_offset) t2_end = (t2_x, P.screen_c[1]-end_offset+t2_offset) self.t2_path = Animation(t2_start, t2_end, self.motion_duration) print(self.upper_target, self.t1_location, self.t1_t2_soa, self.start_offset, t2_offset) print("t1 start: {0} end: {1}".format(t1_start, t1_end)) print("t2 start: {0} end: {1}".format(t2_start, t2_end)) # Set up colour probe and colour wheel self.wheel.rotation = random.randrange(0, 360, 1) self.wheel.render() self.probe.fill = self.wheel.color_from_angle(random.randrange(0, 360, 1)) self.probe.render() # Determine the probe location for the trial self.probe_location = self.probe_locs.pop() self.probe_pos = self.probe_positions[self.probe_location] # Calculate when t1 onset and t2 off are going to be based on motion if self.toj_type == "motion": self.t1_on = (1/self.t1_path.motion_per_ms[1])*self.start_offset self.t2_off = (1/self.t1_path.motion_per_ms[1])*(self.start_offset+end_offset) else: self.t1_on = self.random_interval(700, 1200) self.t2_off = self.t1_on + self.t1_t2_soa-1 + 300 # Add timecourse of events to EventManager events = [] events.append([self.t1_on, 't1_on']) events.append([events[-1][0] + 200, 'probe_off']) events.append([events[-2][0] + self.t1_t2_soa-1, 't2_on']) events.append([self.t2_off, 't2_off']) for e in events: self.evm.register_ticket(ET(e[1], e[0]))
def setup(self): # Stimulus sizes fixation_size = deg_to_px(0.32) fixation_thickness = deg_to_px(0.08) cue_size = deg_to_px(0.64) cue_thickness = deg_to_px(0.08) arrow_tail_len = deg_to_px(0.48) arrow_tail_width = deg_to_px(0.15) arrow_head_len = deg_to_px(0.25) arrow_head_width = deg_to_px(0.45, even=True) arrow_dimensions = [ arrow_tail_len, arrow_tail_width, arrow_head_len, arrow_head_width ] # Stimuli self.warning_tone = Tone(50.1, 'sine', frequency=2000, volume=0.5) self.fixation = kld.FixationCross(fixation_size, fixation_thickness, fill=BLACK) self.cue = kld.Asterisk(cue_size, thickness=cue_thickness, fill=BLACK, spokes=8) self.arrow_r = kld.Arrow(*arrow_dimensions, fill=BLACK) self.arrow_l = kld.Arrow(*arrow_dimensions, fill=BLACK, rotation=180) self.arrow_r.render() self.arrow_l.render() # Layout self.height_offset = deg_to_px(1.3) self.height_jitter = deg_to_px(0.04) self.flanker_offset = arrow_tail_len + arrow_head_len + deg_to_px(0.16) self.ev_offsets = { 'above': -(self.height_jitter * 4), 'below': self.height_jitter * 4 } self.above_loc = (P.screen_c[0], P.screen_c[1] - self.height_offset) self.below_loc = (P.screen_c[0], P.screen_c[1] + self.height_offset) self.cue_locations = {'above': self.above_loc, 'below': self.below_loc} # Add text styles for PVT and block messages self.txtm.add_style("PVT", '1.5deg', color=RED) self.txtm.add_style("block_msg", '0.6deg', color=BLACK) # If it's the first session, insert practice blocks with feedback if P.run_practice_blocks and P.session_number == 1: ANTI_only = ['ANTI-valid', 'ANTI-invalid', 'ANTI-none'] ANTI_EV = copy(ANTI_only) * 2 + ['EV-above', 'EV-below' ] * 3 # 1/2 ANTI, 1/2 EV ANTI_EV_AV = copy(ANTI_EV) + ['AV'] * 6 # 1/3 ANTI, 1/3 EV, 1/3 AV self.insert_practice_block(1, trial_counts=16, factor_mask={'trial_type': ANTI_only}) self.insert_practice_block(2, trial_counts=32, factor_mask={'trial_type': ANTI_EV}) self.insert_practice_block(3, trial_counts=48, factor_mask={'trial_type': ANTI_EV_AV})
def setup(self): self.fix_size = deg_to_px(0.8) self.fix_thickness = deg_to_px(0.1) self.item_size = deg_to_px(0.8) self.fixation = kld.FixationCross(size=self.fix_size, thickness=self.fix_thickness, fill=WHITE) # Create array of possible stimulus locations for spatial search # When it comes time to present stimuli, a random jitter will be applied # to each item presented. locs = [] offset = deg_to_px(3.0) for x in range(0, 3): for y in range(0, 3): locs.append( [P.screen_c[0] + offset * x, P.screen_c[1] + offset * y]) locs.append( [P.screen_c[0] - offset * x, P.screen_c[1] - offset * y]) locs.append( [P.screen_c[0] + offset * x, P.screen_c[1] - offset * y]) locs.append( [P.screen_c[0] - offset * x, P.screen_c[1] + offset * y]) locs.sort() self.spatial_array_locs = list(loc for loc, _ in itertools.groupby(locs)) self.spatial_array_locs.remove([P.screen_c[0], P.screen_c[1]]) coinflip = random.choice([True, False]) if coinflip: self.present_key, self.absent_key = 'z', '/' self.keymap = KeyMap("response", ['z', '/'], [PRESENT, ABSENT], [sdl2.SDLK_z, sdl2.SDLK_SLASH]) else: self.present_key, self.absent_key = '/', 'z' self.keymap = KeyMap("response", ['/', 'z'], [PRESENT, ABSENT], [sdl2.SDLK_SLASH, sdl2.SDLK_z]) self.anykey_txt = "{0}\n\nPress any key to continue..." self.general_instructions = ( "In this experiment you will see a series of items, amongst these items\n" "a target item may, or may not, be presented.\n If you see the target item " "press the '{0}' key, if the target item wasn't presented press the '{1}' instead.\n\n" "The experiment will begin with a few practice rounds to give you a sense of the task." ).format(self.present_key, self.absent_key) self.spatial_instructions = ( "Searching in Space!\n\nIn these trials you will see a bunch of items scattered" "around the screen.\nIf one of them is the target, press the '{0}' key as fast as possible." "\nIf none of them are the target, press the '{1}' key as fast as possible." ).format(self.present_key, self.absent_key) self.temporal_instructions = ( "Searching in Time!\n\nIn these trials you will see a series of items presented one\n" "at a time, centre-screen. If one of these items is the target, press the '{0}' key.\n" "If, by the end, none of them was the target, press the '{1}' key instead." ).format(self.present_key, self.absent_key) # Setup search conditions (blocked) self.spatial_conditions = [[HIGH, LOW], [HIGH, HIGH], [LOW, HIGH], [LOW, LOW]] random.shuffle(self.spatial_conditions) self.temporal_conditions = [[HIGH, LOW], [HIGH, HIGH], [LOW, HIGH], [LOW, LOW]] random.shuffle(self.temporal_conditions) self.practice_conditions = [[HIGH, LOW], [HIGH, HIGH], [LOW, HIGH], [LOW, LOW]] random.shuffle(self.practice_conditions) self.search_type = random.choice([SPACE, TIME]) if P.run_practice_blocks: self.insert_practice_block( block_nums=[1, 2, 3, 4], trial_counts=P.trials_per_practice_block) general_msg = self.anykey_txt.format(self.general_instructions) self.blit_msg(general_msg, "left") any_key()
def create_stimuli(self, tdSimilarity, ddSimilarity): # Setup target properties if P.condition == 'line': self.target_angle = random.randint(0, 179) self.target_height = deg_to_px(0.1) self.target_fill = WHITE self.target_item = kld.Rectangle(width=self.item_size, height=self.target_height, fill=WHITE, rotation=self.target_angle) if tdSimilarity == HIGH: self.ref_angle = self.target_angle else: self.ref_angle = self.target_angle + 90 self.mask = kld.Asterisk(size=self.item_size, thickness=self.target_height, fill=WHITE, spokes=12) else: self.target_height = self.item_size self.palette = kld.ColorWheel(diameter=self.item_size) self.target_angle = random.randint(0, 359) self.target_colour = self.palette.color_from_angle( self.target_angle) self.target_item = kld.Rectangle(width=self.item_size, height=self.target_height, fill=self.target_colour) if tdSimilarity == HIGH: self.ref_angle = self.target_angle else: self.ref_angle = self.target_angle + 180 self.mask = kld.Rectangle(width=self.item_size, fill=WHITE) # Setup distractor(s) properties if ddSimilarity == HIGH: padding = [random.choice([-20, 20])] else: padding = [-40, -20, 20, 40] self.distractors = [] if P.condition == 'line': for p in padding: rotation = self.ref_angle + p self.distractors.append( kld.Rectangle(width=self.item_size, height=self.target_height, fill=WHITE, rotation=rotation)) else: for p in padding: colour = self.palette.color_from_angle(self.ref_angle + p) self.distractors.append( kld.Rectangle(width=self.item_size, height=self.target_height, fill=colour))
def setup(self): # Initialize text styles self.txtm.add_style('normal', '0.7deg') self.txtm.add_style('title', '1.0deg') self.txtm.add_style('stream', '1.5deg') # Stimulus sizes mask_size = deg_to_px(1.5) mask_thick = deg_to_px(0.25) acc_size = deg_to_px(0.5) acc_offset = deg_to_px(3.0) arrow_tail_l = deg_to_px(0.5) arrow_tail_w = deg_to_px(0.2) arrow_head_l = deg_to_px(0.3) arrow_head_w = deg_to_px(0.5, even=True) box_size = deg_to_px(2.0) box_stroke = deg_to_px(0.2) # Generate shape stimuli for instructions self.arrow = kld.Arrow(arrow_tail_l, arrow_tail_w, arrow_head_l, arrow_head_w) self.arrow.fill = P.default_color self.target_box = kld.Rectangle(box_size, stroke=[box_stroke, P.default_color]) # Generate shape stimuli for task self.accuracy_rect = kld.Rectangle(acc_offset, acc_offset + acc_size * 2, fill=P.default_color) self.accuracy_mask = kld.Rectangle(acc_offset + 2, acc_offset, fill=P.default_fill_color) self.mask = kld.Asterisk(mask_size, mask_thick, fill=P.default_color) # Select random letters from the alphabet and render for use in task alphabet = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') random.shuffle(alphabet) self.letter_set = alphabet[:P.set_size] self.letters = {} for letter in self.letter_set: self.letters[letter] = message(letter, style='stream', blit_txt=False) # Initialize thought probes self.probe_condition = P.condition_map[P.condition] self.probe = self._init_probe(self.probe_condition) # Determine proportion of non-nback trials is_nback = self.trial_factory.exp_factors['is_target'] self.nback_rate = sum(is_nback) / len(is_nback) # Determine order of difficulty levels (counterbalancing) self.first_nback = random.choice([1, 2]) # Show task instructions and example thought probe self.instructions() # Add practice blocks to start of task if P.run_practice_blocks: self.insert_practice_block(1, trial_counts=48) self.insert_practice_block(2, trial_counts=48)
def setup(self): self.log_f = open( join(P.local_dir, "logs", "P{0}_log_f.txt".format(P.participant_id)), "w+") header = { "target_presentation_behavior": P.target_presentation_behavior, "target_removal_behavior": P.target_removal_behavior, "fixation_interval": P.fixation_interval, "disc_timeout_interval": P.disc_timeout_interval, "drift_correct_initial_persist": P.drift_correct_initial_persist, "final_disc_timeout_interval": P.final_disc_timeout_interval } if P.development_mode: for k in header: self.log_f.write("{0}: {1}\n".format(k, header[k])) self.max_amplitude = deg_to_px(self.max_amplitude_deg) self.min_amplitude = deg_to_px(self.min_amplitude_deg) self.disc_diameter = deg_to_px(self.disc_diameter_deg) self.display_margin = int(self.disc_diameter * 1.5) self.search_disc_proto = Annulus(self.disc_diameter, int(self.disc_diameter * 0.25), (2, WHITE), BLACK) # if P.inter_disc_interval and P.persist_to_exit_saccade: # raise RuntimeError("P.inter_disc_interval and P.persist_to_exit_saccade cannot both be set.") r = drift_correct_target().width * self.fixation_boundary_tolerance self.el.add_boundary(INITIAL_FIXATION, [P.screen_c, r], CIRCLE_BOUNDARY) fill(P.default_fill_color) self.txtm.add_style("msg", 32, WHITE) self.txtm.add_style("err", 64, WHITE) self.txtm.add_style("disc test", 48, (245, 165, 5)) self.txtm.add_style("tny", 12) self.looked_away_msg = message("Looked away too soon.", "err", blit_txt=False) message("Loading, please hold...", "msg", flip_screen=True) if not P.testing: scale_images = False # for i in range(1, self.trial_factory.num_values("bg_image")): for i in range(1, 10): ui_request() image_key = "wally_0{0}".format(i) # there are 3 sizes of image included by default; if none match the screen res, choose 1080p then scale image_f = join(P.image_dir, image_key, "{0}x{1}.jpg".format(*P.screen_x_y)) for k, v in imp.load_source( "*", join(P.image_dir, image_key, "average_color.txt")).__dict__.iteritems(): if k == "avg_color": avg_color = v if k == "avg_luminance": avg_luminance = v with open(join(P.image_dir, image_key, "average_color.txt")) as color_f: avg_color = eval(color_f.read()) if not isfile(image_f): image_f = join(P.image_dir, image_key, "1920x1080.jpg") scale_images = True img_ns = NpS(image_f) self.backgrounds[image_key] = ([ image_key, img_ns, avg_color, avg_luminance, message(image_key, blit_txt=False) ]) if scale_images: self.backgrounds[image_key][1].scale(P.screen_x_y) self.backgrounds[image_key][1] = self.backgrounds[image_key][ 1].render()
def setup(self): # Font styles for feedback self.txtm.add_style(label='correct', color=WHITE) self.txtm.add_style(label='incorrect', color=RED) # Stimulus properties # # Placeholder(s) placeholder_size = deg_to_px(2.0) uncued_thick = deg_to_px(0.2) cued_thick = deg_to_px(0.5) self.cued_stroke = [cued_thick, WHITE, STROKE_CENTER] self.uncued_stroke = [uncued_thick, WHITE, STROKE_CENTER] # Tone tone_type = 'sine' tone_duration = 50 # ms tone_volume = 0.6 # moderate volume # Fixation fix_size = deg_to_px(0.8) fix_thick = deg_to_px(0.1) # Target target_size = deg_to_px(0.8) # Stimlus construction self.fixation = kld.FixationCross(size=fix_size, thickness=fix_thick, fill=WHITE) self.audio_tone = Tone(duration=tone_duration, wave_type=tone_type, volume=tone_volume) self.target = kld.Circle(diameter=target_size, fill=WHITE) self.box_left = kld.Rectangle(width=placeholder_size, stroke=self.uncued_stroke) self.box_right = kld.Rectangle(width=placeholder_size, stroke=self.uncued_stroke) # Stimulus locations offset = deg_to_px(4.0) if P.development_mode else deg_to_px(8.0) # Normal offset too wide for my laptop self.locs = { TOP_LEFT: [P.screen_c[0] - offset, P.screen_c[1] - offset], TOP_RIGHT: [P.screen_c[0] + offset, P.screen_c[1] - offset], BOTTOM_LEFT: [P.screen_c[0] - offset, P.screen_c[1] + offset], BOTTOM_RIGHT: [P.screen_c[0] + offset, P.screen_c[1] + offset] } coinflip = random.choice([True, False]) if coinflip: self.left_key, self.right_key = 'up', 'down' self.keymap = KeyMap( "response", ['left', 'right'], [LEFT, RIGHT], [sdl2.SDLK_UP, sdl2.SDLK_DOWN] ) else: self.left_key, self.right_key = 'down', 'up' self.keymap = KeyMap( "response", ['left', 'right'], [LEFT, RIGHT], [sdl2.SDLK_DOWN, sdl2.SDLK_UP] ) factor_mask = {'cue_type': ['vis_left', 'vis_right', 'temporal', 'temporal', 'no_cue', 'no_cue'], 'tone_trial': [True, False]} self.ctoa_practice = [100, 250, 850] self.ctoa_testing = [100, 250, 850] random.shuffle(self.ctoa_practice) random.shuffle(self.ctoa_testing) self.insert_practice_block(block_nums=[1, 2, 3], trial_counts=12, factor_mask=factor_mask)
def setup(self): box_size = deg_to_px(1.8) box_thick = deg_to_px(0.05) stim_size = deg_to_px(0.95) stim_thick = deg_to_px(0.1) fix_size = deg_to_px(1) fix_offset = deg_to_px(2.8) box_stroke = [box_thick, WHITE, STROKE_CENTER] self.txtm.add_style(label='greentext', color=GREEN) self.target = kld.Annulus(diameter=stim_size, thickness=stim_thick, fill=WHITE) self.distractor = kld.FixationCross(size=stim_size, thickness=stim_thick, fill=WHITE) # Set the rotation of placeholder boxes & fixation to match that of the display (diamond, square) rotate = 45 if P.condition == 'diamond' else 0 self.placeholder = kld.Rectangle(width=box_size, stroke=box_stroke, rotation=rotate) self.fixation = kld.Asterisk(size=fix_size, thickness=stim_thick, fill=WHITE, rotation=rotate, spokes=8) # Which locations are labelled far or near is dependent on arrangement of display # 'near' locations refer to those that lie at the intersection of 'far' locations if P.condition == 'square': self.far_locs = { 1: [ point_pos(P.screen_c, amplitude=fix_offset, angle=315, clockwise=True), 'NorthEast' ], 2: [ point_pos(P.screen_c, amplitude=fix_offset, angle=45, clockwise=True), 'SouthEast' ], 3: [ point_pos(P.screen_c, amplitude=fix_offset, angle=135, clockwise=True), 'SouthWest' ], 4: [ point_pos(P.screen_c, amplitude=fix_offset, angle=225, clockwise=True), 'NorthWest' ] } self.near_locs = { 5: [midpoint(self.far_locs[4][0], self.far_locs[1][0]), 'North'], 6: [midpoint(self.far_locs[1][0], self.far_locs[2][0]), 'East'], 7: [midpoint(self.far_locs[3][0], self.far_locs[2][0]), 'South'], 8: [midpoint(self.far_locs[3][0], self.far_locs[4][0]), 'West'] } else: # if P.condition == 'diamond' self.far_locs = { 1: [ point_pos(P.screen_c, amplitude=fix_offset, angle=270, clockwise=True), 'North' ], 2: [ point_pos(P.screen_c, amplitude=fix_offset, angle=0, clockwise=True), 'East' ], 3: [ point_pos(P.screen_c, amplitude=fix_offset, angle=90, clockwise=True), 'South' ], 4: [ point_pos(P.screen_c, amplitude=fix_offset, angle=180, clockwise=True), 'West' ] } self.near_locs = { 5: [ midpoint(self.far_locs[4][0], self.far_locs[1][0]), 'NorthWest' ], 6: [ midpoint(self.far_locs[1][0], self.far_locs[2][0]), 'NorthEast' ], 7: [ midpoint(self.far_locs[3][0], self.far_locs[2][0]), 'SouthEast' ], 8: [ midpoint(self.far_locs[3][0], self.far_locs[4][0]), 'SouthWest' ] } if not P.development_mode: self.keymap = KeyMap('directional_response', [ 'North', 'NorthEast', 'East', 'SouthEast', 'South', 'SouthWest', 'West', 'NorthWest' ], [ 'North', 'NorthEast', 'East', 'SouthEast', 'South', 'SouthWest', 'West', 'NorthWest' ], [ sdl2.SDLK_KP_8, sdl2.SDLK_KP_9, sdl2.SDLK_KP_6, sdl2.SDLK_KP_3, sdl2.SDLK_KP_2, sdl2.SDLK_KP_1, sdl2.SDLK_KP_4, sdl2.SDLK_KP_7 ]) else: # Don't have a numpad myself, so I need an alternative when developing self.keymap = KeyMap('directional_response', [ 'North', 'NorthEast', 'East', 'SouthEast', 'South', 'SouthWest', 'West', 'NorthWest' ], [ 'North', 'NorthEast', 'East', 'SouthEast', 'South', 'SouthWest', 'West', 'NorthWest' ], [ sdl2.SDLK_i, sdl2.SDLK_o, sdl2.SDLK_l, sdl2.SDLK_PERIOD, sdl2.SDLK_COMMA, sdl2.SDLK_m, sdl2.SDLK_j, sdl2.SDLK_u ]) # Prime items always presented in far locations self.prime_locs = self.far_locs.copy() # Probe items can be far or near, determined conditionally self.probe_locs = dict(self.near_locs.items() + self.far_locs.items()) # So, to get a practice block of 25, we first need to generate the full set of 2048 # possible permutations, trim that down to the 288 legitimate permutations, # then trim that down to 25... self.insert_practice_block(1, 2048) # Because KLibs auto-generates trials for each product of ind_vars.py, # a lot of 'vestigial' trials are generated that we don't want, this sorts # through the trial list and removes those trials. for ind_b, block in enumerate(self.trial_factory.blocks): for trial in block: # targets & distractors cannot overlap within a given display if trial[1] == trial[2] or trial[3] == trial[4]: self.trial_factory.blocks[ind_b].remove(trial) block.i -= 1 block.length = len(block.trials) continue # For 'near' trials, Ts & Ds cannot appear at 'far' locations if trial[0] == 'near': if trial[3] < 5 or trial[4] < 5: self.trial_factory.blocks[ind_b].remove(trial) block.i -= 1 block.length = len(block.trials) # Conversely, cannot appear at 'near' locations on 'far' trials else: if trial[3] > 4 or trial[4] > 4: self.trial_factory.blocks[ind_b].remove(trial) block.i -= 1 block.length = len(block.trials) # We only want 25 trials for practice, this trims the block # to the appropriate length if ind_b == 0: for trial in block: self.trial_factory.blocks[ind_b].remove(trial) block.i -= 1 block.length = len(block.trials) if block.length == 25: break # Set to True once instructions are provided self.instructed = False
def setup(self): # Stimulus sizes fixation_size = deg_to_px(0.5) fixation_thickness = deg_to_px(0.05) cue_size = deg_to_px(0.5) cue_thickness = deg_to_px(0.05) arrow_tail_len = deg_to_px(0.35) arrow_tail_width = deg_to_px(0.1) arrow_head_len = deg_to_px(0.2) arrow_head_width = deg_to_px(0.3, even=True) # Stimuli self.fixation = kld.FixationCross(fixation_size, fixation_thickness, fill=BLACK) self.cue = kld.Asterisk(cue_size, thickness=cue_thickness, fill=BLACK, spokes=8) self.arrow_l = kld.Arrow(arrow_tail_len, arrow_tail_width, arrow_head_len, arrow_head_width, fill=BLACK, rotation=180) self.arrow_r = kld.Arrow(arrow_tail_len, arrow_tail_width, arrow_head_len, arrow_head_width, fill=BLACK) self.line = kld.Rectangle(arrow_tail_len + arrow_head_len, arrow_tail_width, stroke=[0, BLACK, 1], fill=BLACK) self.arrow_l.render() self.arrow_r.render() # Layout height_offset = deg_to_px(1.06) flanker_offset = arrow_tail_len + arrow_head_len + deg_to_px(0.06) self.above_loc = (P.screen_c[0], P.screen_c[1] - height_offset) self.below_loc = (P.screen_c[0], P.screen_c[1] + height_offset) self.above_flanker_locs = [] self.below_flanker_locs = [] for offset in [-2, -1, 1, 2]: x_pos = P.screen_c[0] + (offset * flanker_offset) self.above_flanker_locs.append((x_pos, self.above_loc[1])) self.below_flanker_locs.append((x_pos, self.below_loc[1])) # Initialize feedback messages for practice block timeout_msg = message('Too slow! Please try to respond more quickly.', blit_txt=False) incorrect_str = ( "Incorrect response!\n" "Please respond to left arrows with the 'z' key and right arrows with the '/' key." ) incorrect_msg = message(incorrect_str, align='center', blit_txt=False) self.feedback_msgs = { 'incorrect': incorrect_msg, 'timeout': timeout_msg } # Set up Response Collector to get keypress responses self.rc.uses(KeyPressResponse) self.rc.terminate_after = [1700, TK_MS ] # response period times out after 1700ms self.rc.keypress_listener.interrupts = True self.rc.keypress_listener.key_map = {'z': 'left', '/': 'right'} # Add practice block of 24 trials to start of experiment if P.run_practice_blocks: self.insert_practice_block(1, trial_counts=24)
def setup(self): # ---------------------------------- # # Setup Stimuli # # ---------------------------------- # # Set stimulus sizes line_length = deg_to_px(2) line_thickness = deg_to_px(0.5) thick_rect_border = deg_to_px(0.7) thin_rect_border = deg_to_px(0.3) fix_size = deg_to_px(0.6) fix_thickness = deg_to_px(0.1) square_size = deg_to_px(3) large_text_size = deg_to_px(0.65) # Stimulus 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]) # Generate target colouring # Select target colours from randomly rotated colourwheel # ensuring those selected are unique and equidistant self.color_selecter = kld.ColorWheel(diameter=1, rotation=random.randrange(0, 360)) self.target_colours = [] for i in (0, 120, 240): self.target_colours.append(self.color_selecter.color_from_angle(i)) # Assign colours to payout valences random.shuffle(self.target_colours) self.high_value_colour = self.target_colours[0] self.low_value_colour = self.target_colours[1] self.neutral_value_colour = self.target_colours[2] # Initialize drawbjects self.thick_rect = kld.Rectangle( square_size, stroke=[thick_rect_border, WHITE, STROKE_CENTER]) self.thin_rect = kld.Rectangle( square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER]) self.high_val_rect = kld.Rectangle( square_size, stroke=[thin_rect_border, self.high_value_colour, STROKE_CENTER]) self.low_val_rect = kld.Rectangle( square_size, stroke=[thin_rect_border, self.low_value_colour, STROKE_CENTER]) self.fixation = kld.Asterisk(fix_size, fix_thickness, fill=WHITE) self.fix_cueback = kld.Asterisk(fix_size * 2, fix_thickness * 2, fill=WHITE) self.go = kld.FixationCross(fix_size, fix_thickness, fill=BLACK) self.nogo = kld.FixationCross(fix_size, fix_thickness, fill=BLACK, rotation=45) self.flat_line = kld.Rectangle(line_length, line_thickness, fill=BLACK) self.tilt_line = kld.Rectangle(line_length, line_thickness, fill=BLACK, rotation=45) self.probe = kld.Ellipse(int(0.75 * square_size)) # ---------------------------------- # # Setup other experiment factors # # ---------------------------------- # # COTOA = Cue-Offset|Target-Onset Asynchrony self.cotoa = 800 # ms self.feedback_exposure_period = 1.25 # sec # Training block payout variables self.high_payout_baseline = 12 self.low_payout_baseline = 8 self.total_score = None self.penalty = -5 # ---------------------------------- # # Setup Response Collectors # # ---------------------------------- # # Initialize response collectors self.probe_rc = ResponseCollector(uses=RC_KEYPRESS) self.training_rc = ResponseCollector(uses=RC_KEYPRESS) # Initialize ResponseCollector keymaps self.training_keymap = KeyMap( 'training_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]) # --------------------------------- # # Setup Experiment Messages # # --------------------------------- # # Make default font size larger self.txtm.add_style('myText', 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." ) training_timeout_txt = err_txt.format("Line response 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, 'myText', align='center', blit_txt=False), 'probe_timeout': message(probe_timeout_txt, 'myText', align='center', blit_txt=False), 'training_timeout': message(training_timeout_txt, 'myText', align='center', blit_txt=False), 'response_on_nogo': message(response_on_nogo_txt, 'myText', 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!" # -------------------------------- # # Setup Eyelink boundaries # # -------------------------------- # fix_bounds = [P.screen_c, square_size / 2] self.el.add_boundary('fixation', fix_bounds, CIRCLE_BOUNDARY) # --------------------------------- # # Insert training block (line task) # # --------------------------------- # if P.run_practice_blocks: self.insert_practice_block(1, trial_counts=P.trials_training_block)
def setup(self): # Stimulus Sizes target_size = deg_to_px(3.0) diamond_size = sqrt(target_size**2/2.0) probe_diameter = deg_to_px(1.0) wheel_diameter = deg_to_px(16.0) # Stimulus Drawbjects self.line_a = kld.Rectangle(width=P.screen_x/2, height=2, fill=WHITE) self.line_b = kld.Rectangle(width=P.screen_x/2, height=2, fill=BLACK) self.diamond_a = kld.Rectangle(diamond_size, fill=WHITE, rotation=45) self.diamond_b = kld.Rectangle(diamond_size, fill=BLACK, rotation=45) self.probe = kld.Ellipse(probe_diameter, fill=None) self.wheel = kld.ColorWheel(wheel_diameter) self.line_a.render() self.line_b.render() self.diamond_a.render() self.diamond_b.render() # Layout self.left_x = P.screen_x/4 self.right_x = 3*P.screen_x/4 self.probe_positions = { "left": (self.left_x, P.screen_c[1]), "right": (self.right_x, P.screen_c[1]) } self.start_baseline = P.screen_y/4 self.end_offset = deg_to_px(5.0) self.left_start = [self.left_x, P.screen_y/4] self.right_start = [self.right_x, 3*P.screen_y/4] self.left_end = [self.left_x, P.screen_c[1]+self.end_offset] self.right_end = [self.right_x, P.screen_c[1]-self.end_offset] # Timing self.motion_duration = 1.5 # seconds # Experiment Messages if not P.condition: P.condition = P.default_condition toj_string = "Which shape {0} {1}?\n(White = 8 Black = 2)" stationary_string = toj_string.format("appeared", P.condition) motion_string = toj_string.format("touched the line", P.condition) self.toj_prompts = { 'stationary': message(stationary_string, align="center", blit_txt=False), 'motion': message(motion_string, align="center", blit_txt=False) } # Initialize ResponseCollector keymaps if P.use_numpad: keysyms = [sdl2.SDLK_KP_8, sdl2.SDLK_KP_2] else: keysyms = [sdl2.SDLK_8, sdl2.SDLK_2] self.toj_keymap = KeyMap( "toj_responses", # Name ['8', '2'], # UI labels ['white', 'black'], # Data labels keysyms # SDL2 Keysyms ) # Initialize second ResponseCollector object for colour wheel responses self.wheel_rc = ResponseCollector() # Generate practice blocks default_soas = self.trial_factory.exp_factors['t1_t2_soa'] toj_soas = [soa for soa in default_soas if soa!=0.0] toj_only = {"t1_t2_soa": toj_soas} probe_only = {"t1_t2_soa": [0.0]} if P.run_practice_blocks: num = P.trials_per_practice_block self.insert_practice_block(1, trial_counts=num, factor_mask=toj_only) self.insert_practice_block((2,4), trial_counts=num, factor_mask=probe_only) self.trial_factory.dump()
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)
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): # Initialize stimulus sizes mask_size_x = deg_to_px(3.0) mask_size_ring = deg_to_px(4.0) mask_thick = deg_to_px(0.3) # Initialize text styles self.txtm.add_style('normal', '0.7deg') self.txtm.add_style('title', '1.0deg') self.mask_x = kld.Asterisk(mask_size_x, mask_thick, fill=P.default_color, spokes=8) self.mask_ring = kld.Annulus(mask_size_ring, mask_thick, fill=P.default_color) self.digits = {} digits = [1, 2, 3, 4, 5, 6, 7, 8, 9] for digit in digits: self.digits[digit] = {} self.sizes = ['1.5deg', '2.0deg', '2.5deg', '3.0deg', '3.5deg'] for size in self.sizes: self.txtm.add_style(size, size) for digit in digits: for size in self.sizes: self.digits[digit][size] = message(str(digit), style=size, blit_txt=False) # Initialize thought probes p_title = message(P.probe_question, "title", blit_txt=False) p_responses = P.probe_responses p_order = P.probe_order p_origin = (P.screen_c[0], P.screen_x // 10) self.probe = ThoughtProbe(p_responses, p_title, int(P.screen_x * 0.8), p_origin, p_order) # Initialize effort probes qeffort_text = "How much effort were you putting into staying on-task?" self.effort_q = message(qeffort_text, "title", blit_txt=False) self.mineffort_msg = message("0%", "normal", blit_txt=False) self.maxeffort_msg = message("100%", "normal", blit_txt=False) self.submit_msg = message("Continue", "normal", blit_txt=False) pad = int(self.submit_msg.height * 0.75) self.submit = Button(self.submit_msg, int(self.submit_msg.width + pad * 1.5), self.submit_msg.height + pad, registration=8, location=(P.screen_c[0], int(P.screen_y * 0.8))) # Randomly distribute probes across session, avoiding placing them less than 20 sec. apart self.probe_trials = [] noprobe_span = [False] * P.noprobe_span probe_span = [True] + [False] * (P.probe_span - 1) while len(self.probe_trials) < (P.trials_per_block * P.blocks_per_experiment): random.shuffle(probe_span) self.probe_trials += noprobe_span + probe_span # Show task instructions and example thought probe self.instructions() # Add practice blocks to start of task self.first_nonpractice = 1 if P.run_practice_blocks: num_nonpractice = P.blocks_per_experiment self.insert_practice_block(1, trial_counts=9, factor_mask={'number': range(1, 10)}) self.insert_practice_block(2, trial_counts=9, factor_mask={'number': range(1, 10)}) self.first_nonpractice = P.blocks_per_experiment - num_nonpractice + 1