def block(self): if P.block_number == int(math.ceil(P.blocks_per_experiment/2.0))+1: util.flush() msg_text = "Whew! You're halfway done.\nTake a break, then press any key to continue." msg = message(msg_text, align="center", blit_txt=False) fill() blit(msg, registration=5, location=P.screen_c) flip() any_key()
def trial(self): self.trial_time = time.time() print(self.trial_articulations, self.response_articulations, self.duration, self.target_brightness) while self.evm.before('lines_on', True): fill() blit(self.fixation_light, 5, P.screen_c) flip() while self.evm.before('response_circle_on', True): self.start_time = time.time() target_on = self.evm.after('target_on') and self.evm.before('target_off') self.display_refresh(target_on) # Debug code for target timing, disabled by default self.elapsed = time.time() - self.start_time if 0.9 < (self.start_time - self.trial_time) <= 1.100 and self.debug_mode: print("init: %.3f" % (self.init_time - self.trial_time)) if lines_on: print("line: %.3f" % (self.line_time - self.trial_time)) print("total: %.3f" % (self.elapsed)) # Collect localization response and save response RT and response angle self.rc.collect() self.response_rt = self.rc.color_listener.response(False, True) self.deg_err = self.rc.color_listener.response(True, False) try: self.response_loc = (self.angle + self.deg_err) % 360 print("Response accuracy: %.1f" % self.deg_err) except TypeError: self.response_loc = NA # Require participant to click center of screen to continue to next trial self.click_to_continue() return { "block_num": P.block_number, "trial_num": P.trial_number, "trial_articulations": self.trial_articulations, "response_articulations": self.response_articulations, "target_brightness": self.target_brightness, "duration": str(self.duration), "rt": self.response_rt, "target_refreshes": self.target_refreshes, "target_loc": self.angle, "response_loc": self.response_loc, "deg_err": self.deg_err }
def click_to_continue(self): util.show_mouse_cursor() wait_for_click = True while wait_for_click: fill() blit(self.next_trial_circle, 5, P.screen_c) flip() events = util.pump(True) ui_request(queue=events) for e in events: if e.type == sdl2.SDL_MOUSEBUTTONUP: pos = (e.button.x, e.button.y) if self.within_boundary("center", pos): fill() flip() wait_for_click = False util.hide_mouse_cursor()
def articulation_callback(self): fill() blit(self.fixation, 5, P.screen_c) if self.response_articulations == True: blit(self.articulations, 5, P.screen_c) blit(self.response_ring, 5, P.screen_c) flip()
def trial_prep(self): # Define timecourse of events for the trial target_on = random.choice(range(500, 2050, 50)) events = [[400-P.refresh_time, 'lines_on']] events.append([events[-1][0] + target_on, 'target_on']) events.append([events[-1][0] + self.duration, 'target_off']) events.append([events[-1][0] + 1000, 'response_circle_on']) for e in events: self.evm.register_ticket(ET(e[1], e[0])) # Randomly select angle of target for the trial, and configure response collector accordingly self.angle = random.choice(range(0, 360, 1)) self.response_ring.rotation = self.angle self.target_pos = util.point_pos(P.screen_c, self.circle_radius, -90, self.angle) self.rc.color_listener.set_target(self.target_pos) # Initialize target asterisk with random opacity within range self.target_brightness = random.choice(range(BRIGHTNESS_MIN, BRIGHTNESS_MAX+1, 1)) target_colour = [self.target_brightness]*3 + [255] #self.target = kld.Asterisk(self.target_width, fill=target_colour, thickness=self.default_stroke/2).render() self.target = kld.Ellipse(self.target_width, fill=target_colour).render() # Reset debug flags before trial starts self.circle_already_on = False self.target_already_on = False self.target_refreshes = 0 # Enter trial with screen already at desired state fill() blit(self.fixation_light, 5, P.screen_c) flip()
def display_refresh(self, target=False): fill() blit(self.fixation, 5, P.screen_c) self.init_time = time.time() # for debug if self.trial_articulations: blit(self.articulations, 5, P.screen_c) self.line_time = time.time() # for debug if target: blit(self.target, 5, self.target_pos) self.target_refreshes += 1 flip() if target and not self.target_already_on: self.target_ontime = time.time() print("target on! %.3f" % (self.target_ontime - self.trial_time)) self.target_already_on = True if not target and self.target_already_on: print("total target on-time: %.3f " % (time.time() - self.target_ontime)) print("refreshes: {0} \n".format(self.target_refreshes)) self.target_already_on = False
def toj_callback(self): fill() blit(self.toj_prompts[self.toj_type], 5, P.screen_c) flip()
def block(self): halfway_block = (P.blocks_per_experiment / 2) + 1 if P.run_practice_blocks and P.session_number == 1: halfway_block += 3 if P.run_practice_blocks and P.session_number == 1 and P.block_number <= 3: txt = "This is a practice block ({0} of 3)\n\n".format( P.block_number) if P.block_number == 1: txt += ( "During this task, arrows will appear either above or below the '+' symbol.\n" "Your job will be to indicate the direction of the middle arrow as quickly\n" "and accurately as possible using the keyboard.\n\n" "( c = left, m = right )") elif P.block_number == 2: txt += ( "On some trials, the central arrow will be displaced upwards or downwards " "by a large amount.\n" "When this occurs, please press the space bar instead of the 'c' or 'm' keys." ) self.trial_type = 'EV' self.ev_offset = 'below' demo_arrows = self.generate_arrows() else: txt += ( "On some trials, a large red countdown timer will appear instead of the arrows." "\nWhen this occurs, please press the space bar as quickly as you can." ) instructions = message(txt, align='center', blit_txt=False) continue_msg = message('Press any key to begin.', align='center', blit_txt=False) instruction_time = CountDown(3) while True: keydown = key_pressed() fill() blit(instructions, 8, (P.screen_c[0], int(P.screen_y * 0.1))) if P.block_number == 2: blit(self.fixation, 5, P.screen_c) for shape, loc in demo_arrows: blit(shape, 5, loc) elif P.block_number == 3: elapsed = min( [int(instruction_time.elapsed() * 1000), 367]) demo_counter = message(str(elapsed).zfill(4), "PVT", blit_txt=False) blit(demo_counter, 5, P.screen_c) if P.development_mode or instruction_time.counting() == False: blit(continue_msg, 2, (P.screen_c[0], int(P.screen_y * 0.9))) if keydown == True: break flip() elif P.run_practice_blocks and P.session_number == 1 and P.block_number == 4: self.block_msg( "Practice complete! Press any key to begin the task.") elif P.block_number == 1: self.block_msg("Press any key to begin the task.") elif P.block_number == halfway_block: self.block_msg( "Phew, you're halfway done! Press any key to continue.")
def blit_it(self, it): fill() blit(it, registration=5, location=P.screen_c) flip()
def blit_img(self, img, reg, loc): fill() blit(img, registration=reg, location=loc) flip()
def capture_figures(self): txt = ( "Welcome to the TraceLab figure generator!\n\n" "Generated figures will be saved to 'ExpAssets/Resources/figures/'. {0}\n\n" "Press any key to begin.") auto_txt = "TraceLab will automatically exit when figure generation is complete." txt = txt.format(auto_txt if P.auto_generate else "") fill() message(txt, "default", registration=5, location=P.screen_c, align='center', blit_txt=True) flip() any_key() if P.development_mode: print("Random seed: {0}".format(P.random_seed)) if P.auto_generate: num_figs = P.auto_generate_count for i in range(0, num_figs): txt = "Generating figure {0} of {1}...".format(i + 1, num_figs) msg = message(txt, "default", blit_txt=False) fill() blit(msg, 5, P.screen_c) flip() figure = self._generate_figure(duration=5000.0) figure.write_out("figure{0}_{1}.tlf".format( i + 1, P.random_seed)) else: done = False while not done: msg = message("Generating...", "default", blit_txt=False) fill() blit(msg, 5, P.screen_c) flip() figure = self._generate_figure(duration=5000.0) while True: # Animate figure on screen with dot, then show full rendered shape figure.animate() animation_dur = round(figure.trial_a_frames[-1][2] * 1000, 2) msg = message("Press any key to continue.", blit_txt=False) msg_time = message( "Duration: {0} ms".format(animation_dur), blit_txt=False) fill() figure.draw() blit(msg, 3, (P.screen_x - 30, P.screen_y - 25)) blit(msg_time, 7, (30, 25)) flip() any_key() # Prompt participants whether they want to (q)uit, (r)eplay animation, # (d)iscard figure and move to next, or (s)ave figure to file resp = query(user_queries.experimental[6]) if resp == "r": # replay continue elif resp == "d": # discard break elif resp == "q": # quit done = True break elif resp == "s": # save f_name = query(user_queries.experimental[7]) + ".tlf" msg = message("Saving... ", blit_txt=False) fill() blit(msg, 5, P.screen_c) flip() figure.write_out(f_name) break
def present_empty_array(self): fill() for value in self.probe_locs.values(): blit(self.placeholder, registration=5, location=value[0]) blit(self.fixation, location=P.screen_c, registration=5) flip()
def display_refresh(self): # Clear the display and draw fixation fill() blit(self.fixation, 5, P.screen_c)
def init_session(self): try: cols = [ 'id', 'session_structure', 'session_count', 'sessions_completed', 'figure_set', 'handedness', 'created' ] user_data = self.db_select('participants', cols, where={'user_id': self.user_id})[0] self.restore_session(user_data) except IndexError as e: if query(uq.experimental[0]) == "y": self.user_id = query(uq.experimental[1]) if self.user_id is None: self.__generate_user_id() return self.init_session() else: fill() msg = message("Thanks for participating!", "default", blit_txt=False) blit(msg, 5, P.screen_c) flip() any_key() self.exp.quit() # Initialize figure paths for current participant/session p_dir = "p{0}_{1}".format(P.participant_id, self.exp.created) session_dir = "session_" + str(self.exp.session_number) self.exp.p_dir = os.path.join(P.data_dir, p_dir) self.exp.fig_dir = os.path.join(self.exp.p_dir, session_dir) # If any existing trial data for this session+participant, prompt experimenter whether to # delete existing data and redo session, continue from start of last completed block, # or continue to next session start_block, start_trial = self.__check_incomplete_session() # Load session structure. If participant already completed all sessions, show message # and quit. session_structure = P.session_structures[self.exp.session_structure] num_sessions = len(session_structure) if self.exp.session_number > num_sessions: txt1 = "Participant {0} has already completed all {1} sessions of the task." msg1 = message(txt1.format(self.user_id, num_sessions), blit_txt=False) msg2 = message("Press any key to exit TraceLab.", blit_txt=False) fill() blit(msg1, 2, (P.screen_c[0], P.screen_c[1] - 30)) blit(msg2, 8, P.screen_c) flip() any_key() self.exp.quit() # Parse block strings for current session current_session = session_structure[self.exp.session_number - 1] P.blocks_per_experiment = len(current_session) for block in current_session: cond = block if isinstance(block, str) else block[0] resp, fb = self.parse_exp_condition(cond) self.exp.block_factors.append({ 'response_type': resp, 'feedback_type': fb }) # Generate trials and import the figure set specified earlier self.init_figure_set() self.exp.blocks = self.__generate_blocks(current_session) self.exp.trial_factory.blocks = self.exp.blocks self.exp.trial_factory.dump() # If resuming incomplete session, skip ahead to last completed trial trimmed_start_block = self.exp.blocks.blocks[start_block - 1][(start_trial - 1):] self.exp.blocks.blocks[start_block - 1] = trimmed_start_block self.exp.blocks.i = start_block - 1 # skips ahead to start_block if specified # If needed, create figure folder for session if not os.path.exists(self.exp.fig_dir): os.makedirs(self.exp.fig_dir) # If session number > 1, log runtime info for session in runtime_info table if 'session_info' in self.db.table_schemas.keys( ) and self.exp.session_number > 1: runtime_info = EntryTemplate('session_info') for col, value in runtime_info_init().items(): if col == 'session_number': value = self.exp.session_number runtime_info.log(col, value) self.db.insert(runtime_info) self.db.update('participants', {'initialized': 1}) self.log_session_init()
def bandit_callback(self, before_go=False): fill() blit(self.star if before_go else self.star_muted, 5, P.screen_c) blit(self.left_bandit, 5, self.left_box_loc) blit(self.right_bandit, 5, self.right_box_loc)
def present_neutral_boxes(self): fill() blit(self.star, 5, P.screen_c) blit(self.neutral_box, 5, self.left_box_loc) blit(self.neutral_box, 5, self.right_box_loc)
def show_error_message(self, msg_key): fill() blit(self.err_msgs[msg_key], location=P.screen_c, registration=5) flip() any_key()
def trial(self): if P.development_mode: trial_info = ( "\ntrial_type: '{0}', high_val_loc: '{1}', probe_loc: '{2}', " "cue_loc: '{3}', winning_bandit: '{4}'") print( trial_info.format(self.trial_type, self.high_value_location, self.probe_location, self.cue_location, self.winning_bandit)) while self.evm.before('target_on', True) and not self.err: self.confirm_fixation() self.present_neutral_boxes() if self.evm.between('cue_on', 'cue_off'): if self.cue_location in [LEFT, DOUBLE]: blit(self.thick_rect, 5, self.left_box_loc) if self.cue_location in [RIGHT, DOUBLE]: blit(self.thick_rect, 5, self.right_box_loc) elif self.evm.between('cue_off', 'cueback_off'): blit(self.star_cueback, 5, P.screen_c) flip() self.targets_shown = True # after bandits or probe shown, don't recycle trial on user error if self.trial_type in [BANDIT, BOTH] and not self.err: while self.evm.before('nogo_end') and not self.err: if key_pressed(): self.show_error_message('too_soon') self.err = "early_response" break self.confirm_fixation() self.bandit_callback(before_go=True) flip() # PROBE RESPONSE PERIOD if self.trial_type in [PROBE, BOTH] and not self.err: self.probe_rc.collect() if not self.err: if len(self.probe_rc.keypress_listener.responses): self.show_error_message('wrong_response') self.err = 'keypress_on_probe' elif len(self.probe_rc.audio_listener.responses) == 0: self.show_error_message('probe_timeout') if self.probe_rc.audio_listener.stream_error: self.err = 'microphone_error' else: self.err = 'probe_timeout' # BANDIT RESPONSE PERIOD if self.trial_type in [BANDIT, BOTH] and not self.err: self.bandit_rc.collect() if self.trial_type == BANDIT and P.ignore_vocal_for_bandits == False: if len(self.bandit_rc.audio_listener.responses): self.show_error_message('wrong_response') self.err = 'vocal_on_bandit' # Retrieve collected response data before logging to database if self.err: bandit_choice, bandit_rt, reward = ['NA', 'NA', 'NA'] probe_rt = 'NA' else: self.err = 'NA' # Retreive responses from RepsponseCollector(s) and record data if self.trial_type in [BANDIT, BOTH]: bandit_choice = self.bandit_rc.keypress_listener.response( value=True, rt=False) bandit_rt = self.bandit_rc.keypress_listener.response( value=False, rt=True) if bandit_rt == TIMEOUT: self.show_error_message('bandit_timeout') reward = 'NA' else: # determine bandit payout (reward) and display feedback to participant reward = self.feedback(bandit_choice) else: bandit_choice, bandit_rt, reward = ['NA', 'NA', 'NA'] if self.trial_type in [PROBE, BOTH]: probe_rt = self.probe_rc.audio_listener.response(value=False, rt=True) else: probe_rt = 'NA' # Clear any remaining stimuli from screen before trial end clear() return { "block_num": P.block_number, "trial_num": P.trial_number, "trial_type": self.trial_type, "cue_loc": self.cue_location, "cotoa": self.cotoa, "high_value_col": self.high_value_color[:3] if self.trial_type != PROBE else "NA", "low_value_col": self.low_value_color[:3] if self.trial_type != PROBE else "NA", "high_value_loc": self.high_value_location if self.trial_type != PROBE else "NA", "winning_bandit": self.winning_bandit if self.trial_type != PROBE else "NA", "bandit_choice": bandit_choice, "bandit_rt": bandit_rt, "reward": reward, "probe_loc": self.probe_location if self.trial_type != BANDIT else "NA", "probe_rt": probe_rt, "err": self.err }
def trial(self): start_delay = CountDown(P.origin_wait_time) while start_delay.counting(): ui_request() fill() blit(self.tracker_dot, 5, self.origin_pos) flip() animate_start = self.evm.trial_time self.figure.animate() animate_time = self.evm.trial_time - animate_start avg_velocity = self.figure.path_length / animate_time if self.response_type == PHYS: self.physical_trial() elif self.response_type == MOTR: self.imagery_trial() else: self.control_trial() fill() flip() if self.__practicing__: return return { "session_num": self.session_number, "block_num": P.block_number, "trial_num": P.trial_number, "response_type": self.response_type, "feedback_type": self.feedback_type, "figure_type": self.figure_name, "figure_file": self.file_name + ".tlf", "stimulus_gt": self.animate_time, # intended animation time "stimulus_mt": animate_time, # actual animation time "avg_velocity": avg_velocity, # in pixels per second "path_length": self.figure.path_length, "trace_file": (self.file_name + ".tlt") if self.response_type == PHYS else 'NA', "rt": self.rt, "it": self.it, "control_question": self.control_question if self.response_type == CTRL else 'NA', "control_response": self.control_response, "mt": self.mt }
def present_filled_array(self, display): fill() for value in self.probe_locs.values(): blit(self.placeholder, registration=5, location=value[0]) if display == 'prime': blit(self.target, registration=5, location=self.T_prime_loc[0]) blit(self.distractor, registration=5, location=self.D_prime_loc[0]) else: blit(self.target, registration=5, location=self.T_probe_loc[0]) blit(self.distractor, registration=5, location=self.D_probe_loc[0]) blit(self.fixation, location=P.screen_c, registration=5) flip()
def blit_msg(self, msg, align): msg = message(msg, align=align, blit_txt=False) fill() blit(msg, registration=5, location=P.screen_c) flip()
def give_instructions(self): button_map = { 'North': message("8", align='center', blit_txt=False, style='greentext'), 'East': message("6", align='center', blit_txt=False, style='greentext'), 'South': message("2", align='center', blit_txt=False, style='greentext'), 'West': message("4", align='center', blit_txt=False, style='greentext'), 'NorthEast': message("9", align='center', blit_txt=False, style='greentext'), 'NorthWest': message("7", align='center', blit_txt=False, style='greentext'), 'SouthWest': message("1", align='center', blit_txt=False, style='greentext'), 'SouthEast': message("3", align='center', blit_txt=False, style='greentext') } hide_mouse_cursor() txt = ( "In this experiment, your task is to indicate the location of the target 'o'\n" "while ignoring the distractor '+'." "\n\n(press the '5' on the numpad to continue past each message)") instruction_msg = message(txt, align='center', blit_txt=False) fill() blit(instruction_msg, location=P.screen_c, registration=5) flip() self.continue_on() txt = ( "Each trial will begin with a fixation cross, when you see this\n" "you may begin the trial by pressing the '5' key on the numpad.\n" "Shortly after which an array will appear") instruction_msg = message(txt, align='center', blit_txt=False) fill() blit(instruction_msg, location=(P.screen_c[0], int(P.screen_c[1] * 0.3)), registration=5) blit(self.fixation, location=P.screen_c, registration=5) flip() self.continue_on() fill() blit(instruction_msg, location=(P.screen_c[0], int(P.screen_c[1] * 0.3)), registration=5) for value in self.probe_locs.values(): blit(self.placeholder, registration=5, location=value[0]) blit(self.fixation, location=P.screen_c, registration=5) flip() self.continue_on() txt = ( "Shortly after the array appears, both the target 'o' and distractor '+'\n" "will appear in random locations within the array...") instruction_msg = message(txt, align='center', blit_txt=False) t_loc = self.prime_locs[1][0] d_loc = self.prime_locs[3][0] fill() blit(instruction_msg, location=(P.screen_c[0], int(P.screen_c[1] * 0.3)), registration=5) for value in self.probe_locs.values(): blit(self.placeholder, registration=5, location=value[0]) blit(self.target, location=t_loc, registration=5) blit(self.distractor, location=d_loc, registration=5) blit(self.fixation, location=P.screen_c, registration=5) flip() self.continue_on() txt = ( "Once they appear, please indicate the location of the 'o' as quickly and\n" "accurately as possible, using the numpad ('8' for North, '9' for Northeast, etc.,)\n" "Each trial will actually consist of two displays, each requiring their own response,\n" "one after the other") instruction_msg = message(txt, align='center', blit_txt=False) fill() blit(instruction_msg, location=(P.screen_c[0], int(P.screen_c[1] * 0.3)), registration=5) for value in self.probe_locs.values(): blit(self.placeholder, registration=5, location=value[0]) blit(button_map[value[1]], registration=5, location=value[0]) blit(self.target, location=t_loc, registration=5) blit(self.distractor, location=d_loc, registration=5) blit(self.fixation, location=P.screen_c, registration=5) flip() self.continue_on() txt = ( "Once you have made both responses, you will be provided with feedback,\n" "the upper and lower line referring to your performance\n" "in the first and second display, respectively.\n" "Please press spacebar to skip past the feedback display") instruction_msg = message(txt, align='center', blit_txt=False) fill() blit(instruction_msg, location=P.screen_c, registration=5) flip() self.continue_on() txt = "For correct responses, your reaction time will be provided to you." fb_txt = "360\n412" instruction_msg = message(txt, align='center', blit_txt=False) fb_msg = message(fb_txt, align='center', blit_txt=False) fill() blit(instruction_msg, location=(P.screen_c[0], int(P.screen_c[1] * 0.3)), registration=5) blit(fb_msg, location=P.screen_c, registration=5) flip() self.continue_on() txt = "For incorrect responses, your reaction time will be replaced by the word WRONG." fb_txt = "323\nWRONG" instruction_msg = message(txt, align='center', blit_txt=False) fb_msg = message(fb_txt, align='center', blit_txt=False) fill() blit(instruction_msg, location=(P.screen_c[0], int(P.screen_c[1] * 0.3)), registration=5) blit(fb_msg, location=P.screen_c, registration=5) flip() self.continue_on() continue_txt = ( "Throughout the task, please keep your fingers rested on the numpad,\n" "with your middle finger resting on the '5' key\n\n" "The experiment will begin with a short practice round to familiarize you with the task\n\n" "When you're ready, press the '5' key to begin...") continue_msg = message(continue_txt, align='center', blit_txt=False) fill() blit(continue_msg, location=P.screen_c, registration=5) flip() self.continue_on()
def drift_correct(self, location=None, target=None, fill_color=None, draw_target=True): """Checks the accuracy of the EyeLink's calibration by presenting a fixation stimulus and requiring the participant to press the space bar while looking directly at it. If there is a large difference between the gaze location at the time the key was pressed and the true location of the fixation, it indicates that there has been drift in the calibration. On older EyeLink models (EyeLink I & II), the recorded drift is used to adjust the calibration for improved accuracy on future trials. On recent models (EyeLink 1000 and up), drift corrections will *check* for drift and prompt the participant to try again if the drift is large, but they do not affect the tracker's calibration. Args: location (Tuple(int, int), optional): The (x,y) pixel coordinates where the drift correct target should be located. Defaults to the center of the screen. target: A :obj:`Drawbject` or other :func:`KLGraphics.blit`-able shape to use as the drift correct target. Defaults to a circular :func:`drift_correct_target`. fill_color: A :obj:`List` or :obj:`Tuple` containing an RGBA colour to use for the background for the drift correct screen. Defaults to the value of ``P.default_fill_color``. draw_target (bool, optional): A flag indicating whether the function should draw the drift correct target itself (True), or whether it should leave it to the programmer to draw the target before :meth:`drift_correct` is called (False). Defaults to True. Raises: TrialException: If repeated EyeLink errors are encountered while attempting to perform the drift correct. """ hide_mouse_cursor() target = drift_correct_target() if target is None else target draw_target = EL_TRUE if draw_target in [EL_TRUE, True] else EL_FALSE location = P.screen_c if location is None else location if not valid_coords(location): raise ValueError("'location' must be a pair of (x,y) pixel coordinates.") try: while True: if draw_target == EL_TRUE: fill(P.default_fill_color if not fill_color else fill_color) blit(target, 5, location) flip() ret = self.doDriftCorrect(location[0], location[1], draw_target, EL_TRUE) if ret != 27: # 27 means we hit Esc to enter calibration, so redo drift correct break if draw_target == EL_TRUE: fill(P.default_fill_color if not fill_color else fill_color) flip() return self.applyDriftCorrect() except RuntimeError: try: self.setOfflineMode() except RuntimeError: self._unresolved_exceptions += 1 if self._unresolved_exceptions > 5: cso("\n<red>*** Fatal Error: Unresolvable EyeLink Error ***</red>") print(full_trace()) self._unresolved_exceptions = 0 raise TrialException("EyeLink not ready.") return self.drift_correct(location, target, fill_color, draw_target)
def present_spatial_array(self, spatial_array): fill() blit(self.fixation, registration=5, location=P.screen_c) for item in spatial_array: blit(item[0], registration=5, location=item[1]) flip()
def trial(self): tone_played = False while self.evm.before('target_on', pump_events=True): fill() blit(self.fixation, 5, P.screen_c) if self.evm.between('cue_on', 'cue_off') and self.cue_location != None: loc = self.cue_locations[self.cue_location] blit(self.cue, 5, loc) if self.tone_trial and self.evm.after('tone_on'): if not tone_played: self.warning_tone.play() tone_played = True flip() if self.trial_type in ['ANTI', 'EV']: fill() blit(self.fixation, 5, P.screen_c) for shape, loc in self.arrows: blit(shape, 5, loc) flip() self.rc.collect() # Get response data and preprocess it before logging to database response, rt = self.rc.keypress_listener.response() if rt == klibs.TIMEOUT: response = 'NA' # if ANT trial, determine absolute diff. between y of central arrow and nearest flanker if self.trial_type != 'AV': ylocs = [arrow[1][1] for arrow in self.arrows] l_diff = abs(ylocs[2] - ylocs[1]) r_diff = abs(ylocs[2] - ylocs[3]) abs_diff = px_to_deg(min([l_diff, r_diff])) else: abs_diff = 'NA' if self.trial_type == 'ANTI': self.ev_offset = 'NA' accuracy = int(response == self.target_direction) elif self.trial_type == 'EV': accuracy = int(response == 'detection') elif self.trial_type == 'AV': self.ev_offset = 'NA' self.target_direction = 'NA' self.target_location = 'NA' self.congruent = 'NA' accuracy = int(response == 'detection') if response == 'NA': accuracy = 'NA' if P.practicing and accuracy is not 1: # If on a practice block, show feedback if an incorrect response is made. if accuracy is 0: feedback = "Incorrect response!\n" if 'ANTI' in self.trial_type: feedback += "Please press 'c' for left arrows and 'm' for right arrows." elif 'EV' in self.trial_type: feedback += "Please press the space bar for displaced arrows." else: feedback += "Please press the space bar for countdown timers." else: feedback = ( "No valid response made!\n" "Please press 'c' for left arrows, 'm' for right arrows, " "and space for displaced arrows or countdowns.") feedback_msg = message(feedback, 'block_msg', blit_txt=False, align='center') fill() blit(feedback_msg, 5, P.screen_c) flip() flush() any_key() # After feedback acknowledged, return to fixation screen fill() blit(self.fixation, 5, P.screen_c) flip() else: while self.evm.before('trial_end', pump_events=True): fill() blit(self.fixation, 5, P.screen_c) flip() return { "session_num": P.session_number, "block_num": P.block_number, "trial_num": P.trial_number, "trial_type": self.trial_type, "warning_tone": self.tone_trial, "cue_type": self.cue_type, "target_location": self.target_location, "target_direction": self.target_direction, "congruent": self.congruent, "displacement": self.ev_offset, "abs_displacement": abs_diff, # diff. in dva between middle arrow and nearest flanker "response": response, "accuracy": accuracy, "rt": rt }
def present_fixation(self): fill() blit(self.fixation, location=P.screen_c, registration=5) flip()
def trial(self): # Display the stimuli in sequence (which stimuli and in which sequence is # determined above in trial_prep). while self.evm.before('t2_off'): ui_request() fill() blit(self.t1_line, 5, self.t1_pos) blit(self.t2_line, 5, self.t2_pos) if self.toj_type == "motion": blit(self.t1, self.t1_reg, self.t1_path.position) blit(self.t2, self.t2_reg, self.t2_path.position) else: if self.evm.after('t1_on'): blit(self.t1, 5, self.t1_pos) if self.evm.after('t2_on'): blit(self.t2, 5, self.t2_pos) if self.probe_trial and self.evm.between('t1_on', 'probe_off'): blit(self.probe, 5, self.probe_pos) flip() # After 2nd target is off, collect either TOJ response or colour wheel response # depending on trial type. if self.probe_trial: self.wheel_rc.collect() else: self.toj_callback() self.rc.collect() # Parse collected response data before writing to the database if not self.probe_trial: toj_response = self.rc.keypress_listener.response(rt=False) toj_rt = self.rc.keypress_listener.response(value=False) if toj_response == 'NO_RESPONSE': toj_response, toj_rt = ['NA', 'timeout'] response_col, angle_err, wheel_rt = ['NA', 'NA', 'NA'] else: try: angle_err, response_col = self.wheel_rc.color_listener.response(rt=False) wheel_rt = self.wheel_rc.color_listener.response(value=False) response_col = list(response_col) # to be consistent with probe_col except ValueError: # if no response made (timeout), only one value will be returned angle_err, response_col, wheel_rt = ['NA', 'NA', 'timeout'] toj_response, toj_rt = ['NA', 'NA'] return { "block_num": P.block_number, "trial_num": P.trial_number, "toj_condition": P.condition, "trial_type": 'probe' if self.probe_trial else 'toj', "target_type": self.toj_type, "t1_location": self.t1_location, "t1_type": "white" if self.t1_shape == "a" else "black", "upper_target": self.upper_target if self.toj_type == "motion" else 'NA', "t1_t2_soa": self.t1_t2_soa, "toj_response": toj_response, "toj_rt": toj_rt, "probe_loc": self.probe_location if self.probe_trial else 'NA', "probe_col": str(self.probe.fill_color[:3]) if self.probe_trial else 'NA', "response_col": str(response_col[:3]), "angle_err": angle_err, "wheel_rt": wheel_rt }
def display_refresh(self, cue=None, tone=False, target=False): fill() blit(self.fixation, location=P.screen_c, registration=5) if cue is not None: if cue == TEMP_CUE: self.box_left.stroke = self.cued_stroke self.box_right.stroke = self.cued_stroke elif cue == VIS_LEFT: self.box_left.stroke = self.cued_stroke elif cue == VIS_RIGHT: self.box_right.stroke = self.cued_stroke else: self.box_right.stroke = self.uncued_stroke self.box_left.stroke = self.uncued_stroke blit(self.box_left, registration=5, location=self.locs['top_left']) blit(self.box_left, registration=5, location=self.locs['bottom_left']) blit(self.box_right, registration=5, location=self.locs['top_right']) blit(self.box_right, registration=5, location=self.locs['bottom_right']) if target: blit(self.target, registration=5, location=self.locs[self.target_loc]) flip() if tone: self.audio_tone.play()
def wheel_callback(self): fill() blit(self.wheel, location=P.screen_c, registration=5) flip()
def present_instructions(self): msg = "During this experiment, each trial will begin with a cross centre-screen." self.anykey_msg(msg) fill() blit(self.fixation, location=P.screen_c, registration=5) flip() smart_sleep(1000) msg = ("When you see this, press spacebar to start the rest of the trial.\n" + "Once you do, four boxes will appear around it.") self.anykey_msg(msg) fill() blit(self.fixation, location=P.screen_c, registration=5) flip() smart_sleep(500) self.display_refresh() smart_sleep(1000) msg = ("Since you start each trial yourself, if your eyes get tired\n" + "you can take a moment before starting the next trial.") self.anykey_msg(msg) msg = "Shortly after the boxes appear, \nsome number of boxes may 'flash' and a tone might be played." self.anykey_msg(msg) self.cue_type = TEMP_CUE self.display_refresh() smart_sleep(250) self.display_refresh(cue=True, tone=True) smart_sleep(50) self.display_refresh() smart_sleep(1000) msg = "Once the boxes return to normal, a white circle will appear shortly after in one of the boxes." self.anykey_msg(msg) msg = ("If only the left or right boxes flash, the circle will appear on that side.\n" + "If all, or none, of the boxes flash, then the circle could appear on either side." ) self.anykey_msg(msg) self.cue_type = VIS_LEFT self.target_loc = TOP_LEFT self.display_refresh() smart_sleep(250) self.display_refresh(cue=True, tone=True) smart_sleep(50) self.display_refresh() smart_sleep(250) self.display_refresh(target=True) smart_sleep(600) fill() blit(self.fixation, location=P.screen_c, registration=5) flip() smart_sleep(500) self.cue_type = VIS_RIGHT self.target_loc = BOTTOM_RIGHT self.display_refresh() smart_sleep(250) self.display_refresh(cue=True) smart_sleep(50) self.display_refresh() smart_sleep(250) self.display_refresh(target=True) smart_sleep(600) fill() blit(self.fixation, location=P.screen_c, registration=5) flip() smart_sleep(500) self.cue_type = TEMP_CUE self.target_loc = TOP_RIGHT self.display_refresh() smart_sleep(250) self.display_refresh(cue=True, tone=True) smart_sleep(50) self.display_refresh() smart_sleep(250) self.display_refresh(target=True) smart_sleep(600) fill() blit(self.fixation, location=P.screen_c, registration=5) flip() smart_sleep(500) self.cue_type = TEMP_CUE self.target_loc = BOTTOM_LEFT self.display_refresh() smart_sleep(250) self.display_refresh(cue=True, tone=True) smart_sleep(50) self.display_refresh() smart_sleep(250) self.display_refresh(target=True) smart_sleep(600) msg = ("Once the target appears, you task is to indicate on which side it appeared.\n" + "{0} key = left boxes, {1} key = right boxes" ).format(self.left_key.upper(), self.right_key.upper()) self.anykey_msg(msg) msg = ("When you make a response, your reaction time will be provided to you.\n" + "Otherwise, you'll be asked to respond faster.\nTry to keep this number as" + " low as possible, while remaining accurate.") self.anykey_msg(msg) msg = ("Feedback for correct responses will be provided in white.\n" + "For incorrect responses, it will be in red.") self.anykey_msg(msg) msg = "The experiment will now begin with a few practice rounds to familiarize you with the task." self.anykey_msg(msg)
def blit(self): if P.development_mode: self.exp.log_f.write("\n\tD{0}: blit()".format(self.index)) if self.allow_blit: blit(self.disc, 5, self.x_y_pos) if self.using_secondary_pos: blit(self.disc, 5, self.secondary_x_y_pos) if P.development_mode: if P.dm_show_disc_indices: blit(self.index_str, 5, self.x_y_pos) if self.using_secondary_pos: blit(self.secondary_index_str, 5, self.secondary_x_y_pos) if P.dm_draw_boundaries: blit(self.debug_boundary, 5, self.x_y_pos) if self.using_secondary_pos: blit(self.debug_boundary, 5, self.secondary_x_y_pos) self.initial_blit = True if self.first_disc and self.removal_behavior == P.REMOVE_ON_PRESENTATION: self.exp.show_dc_target = False # after the first disc is shown, dc_target should be off
def bandit_callback(self, before_go=False): self.confirm_fixation() self.present_neutral_boxes() blit(self.left_bandit, 5, self.left_box_loc) blit(self.right_bandit, 5, self.right_box_loc)
def display_refresh(self, disc_location=None): # handle the removal of background image on absent condition trials ui_request() fill(GREY) if self.bg_state != BG_ABSENT: if (disc_location is not None and disc_location.final_disc ) and self.bg_state == BG_INTERMITTENT: fill(self.bg[2]) else: if P.development_mode: blit(self.bg[4], 7, (50, 50)) blit(self.bg[1]) # show the drift correct target if need be if self.show_dc_target: blit(drift_correct_target(), position=P.screen_c, registration=5) # blit passed discs if they're allow_blit attribute is set if P.development_mode: if disc_location: disc_args = [disc_location.index, self.evm.trial_time_ms, "F" if disc_location.final_disc else "x", \ "P" if disc_location.penultimate_disc else "x", "N" if disc_location.n_back else "x"] self.log_f.write( "\n\n*** Current Disc: {0} ({2}{3}{4}) at {1} ***".format( *disc_args)) for d in [ disc_location, disc_location.previous_disc, disc_location.next_disc ]: try: try: until_timeout = self.evm.until( d.event_timeout_label ) if d.timing_out else None except EventError: until_timeout = "ELAPSED" try: until_decay = self.evm.until( d.offset_decay_label) if d.decaying else None except EventError: until_decay = "ELAPSED" disc_attrs = [ d.initial_blit, d.allow_blit, d.index, d.on_timestamp, d.off_timestamp, d.fixation_timestamp, d.exit_time, until_timeout, until_decay, d.timed_out ] self.log_f.write( "\nD{2}, initial_blit: {0}, allow_blit: {1}, on: {3}, off: {4}, fixation: {5}, exit: {6}, decay_in: {8}, timed_out: {9}, timeout_in: {7}" .format(*disc_attrs)) self.log_f.write(" +") except AttributeError: pass self.log_f.write("\n") if disc_location: # display discs for d in [ disc_location, disc_location.previous_disc, disc_location.next_disc ]: if isinstance(d, DiscLocation) and d.allow_blit: d.blit() flip() if disc_location: # log timestamps for discs turning on or off for d in [ disc_location, disc_location.previous_disc, disc_location.next_disc ]: if isinstance(d, DiscLocation): timestamp = [self.evm.trial_time, self.el.now()] if d.allow_blit: if not d.on_timestamp: d.record_presentation(timestamp) if d.decaying: d.check_decay() elif d.initial_blit and not d.off_timestamp: d.record_removal(timestamp)
def draw(self): blit(self.msg, 5, self.midpoint) mp = mouse_pos() if self.bounds.within(mp): blit(self.hover, 5, self.midpoint)
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 trial(self): # BANDIT TRIAL if P.practicing: cotoa, probe_rt = ['NA', 'NA'] # Don't occur in bandit blocks # Present placeholders while self.evm.before('target_on', True) and not self.err: self.confirm_fixation() self.present_neutral_boxes() flip() # BANDIT RESPONSE PERIOD self.targets_shown = True # After bandits shown, don't recycle trial # Present bandits and listen for response self.bandit_rc.collect() # If wrong response made if self.err: bandit_choice, bandit_rt, reward = ['NA', 'NA', 'NA'] else: self.err = 'NA' # Retrieve responses from ResponseCollector(s) & record data bandit_choice = self.bandit_rc.keypress_listener.response( value=True, rt=False) bandit_rt = self.bandit_rc.keypress_listener.response( value=False, rt=True) if bandit_rt == TIMEOUT: self.show_error_message('bandit_timeout') reward = 'NA' else: # Determine bandit payout & display reward = self.feedback(bandit_choice) # PROBE TRIAL else: bandit_choice, bandit_rt, reward = [ 'NA', 'NA', 'NA' ] # Don't occur in probe trials # Present placeholders & confirm fixation while self.evm.before('target_on', True): self.confirm_fixation() self.present_neutral_boxes() # Present cue if self.evm.between('cue_on', 'cue_off'): if self.cue_location == LEFT: blit(self.thick_rect, 5, self.left_box_loc) else: blit(self.thick_rect, 5, self.right_box_loc) # Present cueback elif self.evm.between('cue_off', 'cueback_off'): blit(self.star_cueback, 5, P.screen_c) flip() # PROBE RESPONSE PERIOD self.targets_shown = True # After probe shown, don't recycle trial # Present probes & listen for response self.probe_rc.collect() # If 'go' trial, check for response if self.go_no_go == GO: # If wrong response made if self.err: probe_rt = 'NA' # If correct response OR timeout else: self.err = 'NA' probe_rt = self.probe_rc.keypress_listener.response( value=False, rt=True) if probe_rt == TIMEOUT: self.show_error_message('probe_timeout') probe_rt = 'NA' # Similarly, for 'nogo' trials else: probe_rt = 'NA' # If response made, penalize if len(self.probe_rc.keypress_listener.responses): self.show_error_message('response_on_nogo') self.err = 'response_on_nogo' # If no response, continue as normal else: self.err = 'NA' # Return trial data return { "block_num": P.block_number, "trial_num": P.trial_number, "block_type": "BANDIT" if P.practicing else "PROBE", "high_value_col": self.high_value_colour[:3] if P.practicing else 'NA', "high_value_loc": self.high_value_location if P.practicing else 'NA', "low_value_col": self.low_value_colour[:3] if P.practicing else 'NA', "low_value_loc": self.low_value_location if P.practicing else 'NA', "winning_trial": self.winning_trial if P.practicing else 'NA', "bandit_selected": self.bandit_selected if P.practicing else 'NA', "bandit_rt": bandit_rt, "reward": reward, "cue_loc": self.cue_location if not P.practicing else 'NA', "cotoa": self.cotoa if not P.practicing else 'NA', "probe_loc": self.probe_location if not P.practicing else 'NA', "probe_col": self.probe_colour if not P.practicing else 'NA', "go_no_go": self.go_no_go if not P.practicing else 'NA', "probe_rt": probe_rt, "err": self.err } # Clear remaining stimuli from screen clear()