def anykey_msg(self, msg): msg += "\n\n{press any key to continue}" fill() message(msg, location=P.screen_c, registration=5, align='center', blit_txt=True) flip() any_key()
def clean_up(self): # Let Ss know when experiment is over self.all_done_text = "You're all done! Now I get to take a break.\nPlease buzz the researcher to let them know you're done!" fill() msg = message(self.all_done_text, 'timeout', blit_txt=False) blit(msg, 5, P.screen_c) flip() any_key()
def block(self): # Show block start message and wait for input before starting block start_msg = message("Press any key to start.", blit_txt=False) fill() blit(start_msg, 5, P.screen_c) flip() any_key() # wait for keypress before continuing
def clean_up(self): # Inform Ss that they have completed the experiment all_done_txt = "Whew! You're all done!\nPlease buzz the researcher to let them know." all_done_msg = message(all_done_txt, align="center", blit_txt=False) fill() blit(all_done_msg, 5, P.screen_c) flip() any_key()
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_prep(self): # Reset error flag self.targets_shown = False self.err = None # TRAINING PROPERTIES if P.practicing: self.cotoa = 'NA' # No cue, so no COTOA # Establish location of target line if self.tilt_line_location == LEFT: self.tilt_line_loc = self.left_box_loc self.flat_line_loc = self.right_box_loc else: self.tilt_line_loc = self.right_box_loc self.flat_line_loc = self.left_box_loc # PROBE PROPERTIES else: # Rest breaks if P.trial_number % (P.trials_per_block / P.breaks_per_block) == 0: if P.trial_number < P.trials_per_block: fill() msg = message(self.rest_break_txt, 'myText', blit_txt=False) blit(msg, 5, P.screen_c) flip() any_key() # Establish & assign probe location self.probe_loc = self.right_box_loc if self.probe_location == RIGHT else self.left_box_loc # go/nogo signal always presented w/probe self.go_nogo_loc = self.probe_loc # Establish & assign probe colour if self.probe_colour == HIGH: self.probe.fill = self.high_value_colour elif self.probe_colour == LOW: self.probe.fill = self.low_value_colour else: self.probe.fill = self.neutral_value_colour # Add timecourse of events to EventManager if P.practicing: # training trials events = [[1000, 'target_on']] else: # Probe trials events = [[1000, 'cue_on']] events.append([events[-1][0] + 200, 'cue_off']) events.append([events[-1][0] + 200, 'cueback_off']) events.append([events[-2][0] + 800, 'target_on']) for e in events: self.evm.register_ticket(ET(e[1], e[0])) # Perform drift correct on Eyelink before trial start self.el.drift_correct()
def trial(self): # Before target onset, show fixation and draw cues during cue period while self.evm.before('target_on', pump_events=True): self.display_refresh() if self.evm.between('cue_on', 'cue_off'): self.draw_cues() flip() # Draw target stimuli/flankers and enter response collection loop fill() blit(self.fixation, 5, P.screen_c) blit(self.target, 5, self.target_loc) for loc in self.flanker_locs: blit(self.flanker, 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 == TIMEOUT: response = 'NA' # If practice trial, show participant feedback for bad responses if P.practicing and response != self.target_direction: fill() if response == 'NA': blit(self.feedback_msgs['timeout'], 5, P.screen_c) else: blit(self.feedback_msgs['incorrect'], 5, P.screen_c) flip() any_key() # Otherwise, clear screen immediately after response and wait for trial end else: self.display_refresh() flip() while self.evm.before('trial_end', pump_events=True): self.display_refresh() flip() # Log recorded trial data to database return { "block_num": P.block_number, "trial_num": P.trial_number, "cue_type": self.cue_type, "cue_onset": self.cue_onset if self.cue_type != 'none' else 'NA', "target_direction": self.target_direction, "target_loc": self.target_location, "flanker_type": self.flanker_type, "response": response, "rt": rt }
def show_logo(self): from klibs.KLUtilities import flush from klibs.KLUserInterface import any_key from klibs.KLGraphics import fill, blit, flip from klibs.KLGraphics import NumpySurface as NpS logo = NpS(P.logo_file_path) flush() for i in (1, 2): fill() blit(logo, 5, P.screen_c) flip() any_key()
def block(self): # Get response type and feedback type for block self.response_type = self.block_factors[P.block_number - 1]['response_type'] self.feedback_type = self.block_factors[P.block_number - 1]['feedback_type'] # If on first block of session, or response type is different from response type of # previous block, do tutorial animation and practice if self.response_type != self.prev_response_type: # Load instructions for new response type new_instructions = self.instruction_files[self.response_type] instructions_file = os.path.join(P.resources_dir, "Text", new_instructions['text']) inst_txt = io.open(instructions_file, encoding='utf-8').read() self.instructions = message(inst_txt, "instructions", align="center", blit_txt=False) if P.enable_practice: # Load tutorial animation for current condition, play it, and enter practice self.practice_kf = FrameSet(new_instructions['frames'], "assets") if P.block_number == 1: fill() blit(self.practice_instructions, 5, P.screen_c) flip() any_key() else: self.start_trial_button() self.practice() self.prev_response_type = self.response_type for i in range(1, 4): # we do this a few times to avoid block messages being skipped due to duplicate input # from the touch screen we use ui_request() fill() blit(self.instructions, registration=5, location=P.screen_c, flip_x=P.flip_x) flip() any_key() # Indicates if on first trial of block, needed for reloading incomplete sessions self.first_trial = True
def __verify_session_structures(self): error_strings = { "bad_format": "Response type and feedback type must be separated by a single hyphen.", "bad_condition": ("Response type must be either 'PP' (physical), 'MI' (imagery), " "or 'CC' (control)."), "bad_feedback": ("Feedback type must be one or two characters long, and be " "a combination of the letters 'V', 'R' and / or 'X'."), "bad_trialcount": ("Custom trial counts must be specified in (blocktype, trials) " "format, where 'trials' is a positive integer."), } # Validate specified session structure to use, return informative error if formatted wrong session_num, block_num = (0, 0) e = "Error encountered parsing Block {0} of Session {1} in session structure '{2}' ({3}):" for structure_key, session_structure in P.session_structures.items(): for session in session_structure: session_num += 1 for block in session: block_num += 1 if type(block) in [tuple, list]: if isinstance(block[1], int) and block[1] > 0: err = self.validate_block_condition(block[0]) else: err = "bad_trialcount" else: err = self.validate_block_condition(block) if err: if isinstance(block, tuple): block = list(block) err_txt1 = e.format(block_num, session_num, structure_key, str(block)) err_txt1 += "\n" + error_strings[err] msg1 = message(err_txt1, "error", align="center", 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()
def present_instructions(self): block_txt = "Block {0} of {1}\n\n".format(P.block_number, P.blocks_per_experiment) if self.search_type == SPACE: block_txt += self.spatial_instructions else: block_txt += self.temporal_instructions if P.practicing: block_txt += "\n\n(This is a practice block)" block_txt = self.anykey_txt.format(block_txt) self.blit_msg(block_txt, "left") any_key()
def block(self): # Determine probe bias for block and generate list of probe locs accordingly if P.block_number > 3: self.probe_bias = "left" nonbiased_loc = "right" else: self.probe_bias = "right" nonbiased_loc = "left" loc_list = [self.probe_bias]*4 + [nonbiased_loc] self.probe_locs = loc_list * int(P.trials_per_block/float(len(loc_list))+1) random.shuffle(self.probe_locs) # At the start of each block, display a start message (block progress if experimental block, # practice message if practice block). After 3000ms, keypress will start first trial. probe_msg = ( "During this block, the colour target will appear more often on the " "{0} and less often on the {1}.".format(self.probe_bias, nonbiased_loc) ) header = "Block {0} of {1}".format(P.block_number, P.blocks_per_experiment) if P.practicing: header = "This is a practice block. ({0})".format(header) if P.block_number > 1: msg = message(header+"\n"+probe_msg, align="center", blit_txt=False) else: msg = message(header, blit_txt=False) message_interval = CountDown(1) while message_interval.counting(): ui_request() # Allow quitting during loop fill() blit(msg, 8, (P.screen_c[0], P.screen_y*0.4)) flip() flush() fill() blit(msg, 8, (P.screen_c[0], P.screen_y*0.4)) message("Press any key to start.", registration=5, location=[P.screen_c[0], P.screen_y*0.6]) flip() any_key() # When running participants, send halfway point and last-block notifications to researcher via Slack if not P.development_mode: if P.block_number == 3: # If participant is halfway done slack_message("Halfway done ({0}/{1})".format(P.block_number, P.blocks_per_experiment))
def clean_up(self): if self.session_number == self.session_count and P.enable_learned_figures_querying: self.fig_dir = os.path.join(self.p_dir, "learned") if not os.path.exists(self.fig_dir): os.makedirs(self.fig_dir) learned_fig_num = 1 if query(user_queries.experimental[3]) == "y": self.origin_pos = (P.screen_c[0], int(P.screen_y * 0.8)) self.origin_boundary = [self.origin_pos, P.origin_size // 2] while True: self.setup_response_collector() self.rc.draw_listener.add_boundaries([ ('start', self.origin_boundary, CIRCLE_BOUNDARY), ('stop', self.origin_boundary, CIRCLE_BOUNDARY) ]) self.start_trial_button() self.capture_learned_figure(learned_fig_num) if query(user_queries.experimental[4]) == "y": learned_fig_num += 1 else: break # if the entire experiment is successfully completed, update the sessions_completed column q_str = "UPDATE `participants` SET `sessions_completed` = ? WHERE `id` = ?" self.db.query(q_str, QUERY_UPD, q_vars=[self.session_number, P.participant_id]) # log session data to database session_data = { 'participant_id': P.participant_id, 'user_id': self.user_id, 'session_number': self.session_number, 'completed': now(True) } self.db.insert(session_data, "sessions") # show 'experiment complete' message before exiting experiment msg = message(P.experiment_complete_message, "instructions", blit_txt=False) flush() fill() blit(msg, registration=5, location=P.screen_c) flip() any_key()
def practice(self, play_key_frames=True, callback=None): self.__practicing__ = True if callback == self.__practice__: play_key_frames = False self.__practice__() elif callback == self.practice: play_key_frames = True elif callback == any_key: self.__practicing__ = False return any_key() if play_key_frames: self.practice_kf.play() self.practice_button_bar.reset() self.practice_button_bar.render() self.evm.start_clock() cb = self.practice_button_bar.collect_response() self.evm.stop_clock() self.__practicing__ = False return self.practice(callback=cb)
def block(self): if not P.practicing: if self.search_type == SPACE: self.condition = self.spatial_conditions_exp.pop() else: self.condition = self.temporal_conditions_exp.pop() else: self.condition = self.practice_conditions.pop() self.target_distractor, self.distractor_distractor = self.condition # Generate wheel to select colors from self.color_selector = ColorWheel(deg_to_px(1), rotation=random.randrange(0, 360)) # Select target colouring self.target_color = self.color_selector.color_from_angle(0) self.target_item = Rectangle(width=self.item_size, fill=self.target_color) self.create_stimuli() if not self.general_instruct_shown: self.general_instruct_shown = True general_text = self.anykey_text.format(self.general_instructions) general_msg = message(general_text, align='left', blit_txt=False) fill() blit(general_msg, 5, P.screen_c) flip() any_key() block_txt = "Block {0} of {1}".format(P.block_number, P.blocks_per_experiment) progress_txt = self.anykey_text.format(block_txt) if P.practicing: progress_txt += "\n(This is a practice block)" progress_msg = message(progress_txt, align='center', blit_txt=False) fill() blit(progress_msg, 5, P.screen_c) flip() any_key() if self.search_type == SPACE: block_type_txt = self.anykey_text.format(self.spatial_instructions) else: block_type_txt = self.anykey_text.format( self.temporal_instructions) block_type_msg = message(block_type_txt, align='left', blit_txt=False) fill() blit(block_type_msg, 5, P.screen_c) flip() any_key()
def trial_prep(self): clear() # choose randomly varying parts of trial self.orientation = random.choice(self.orientations) self.fixation = tuple(random.choice(self.exp_meta_factors['fixation'])) # infer which mask & stim to use and retrieve them self.figure = self.target_shape if self.target_level == LOCAL else False if self.figure: stim_label = "{0}_D_{1}".format(self.target_shape, self.orientation) else: stim_label = "D_{0}_{1}".format(self.orientation, self.target_shape) self.figure = self.stimuli[stim_label] self.mask_label = "{0}_{1}".format(self.mask_type, self.mask_size) try: self.mask = self.masks[self.mask_label] except KeyError: self.mask = None # for the no mask condition, easier than creating empty keys in self.masks blit(self.trial_start_msg, 5, P.screen_c) flip() any_key() self.el.drift_correct(self.fixation)
def block(self): # Grab 1/2 mark halfway_block = P.blocks_per_experiment / 2 + 1 # Present task instructions prior to 1st block if not self.instructions_presented: self.instructions_presented = True halfway_block += 1 txt = ( "During this task arrows will appear either above or below fixation.\n" "Your job is to indicate in which direction the middle arrow is pointing,\n" "both as quickly and accurately as possible.\n" "( 'c' = Left, 'm' = Right )\n\n" "Press any key to continue...") msg = message(txt, align='center', blit_txt=False) fill() blit(msg, registration=5, location=P.screen_c) flip() # Instructions will hang until keypress any_key() # Provide break at 1/2 mark if P.block_number == halfway_block: txt = ("You're half way through! Take a break and\n" "press any key when you're ready to continue.") msg = message(txt, align='center', blit_txt=False) fill() blit(msg, registration=5, location=P.screen_c) flip() any_key()
def block(self): if not P.practicing: if self.search_type == SPACE: self.condition = self.spatial_conditions_exp.pop() else: self.condition = self.temporal_conditions_exp.pop() else: self.condition = self.practice_conditions.pop() self.target_distractor, self.distractor_distractor = self.condition self.target_tilt = random.randint(0, 179) self.target_item = Rectangle(self.item_size, self.item_thickness, fill=WHITE, rotation=self.target_tilt) self.create_stimuli() if not self.general_instruct_shown: self.general_instruct_shown = True general_text = self.anykey_text.format(self.general_instructions) general_msg = message(general_text, align='left', blit_txt=False) fill() blit(general_msg, 5, P.screen_c) flip() any_key() block_txt = "Block {0} of {1}".format(P.block_number, P.blocks_per_experiment) progress_txt = self.anykey_text.format(block_txt) if P.practicing: progress_txt += "\n(This is a practice block)" progress_msg = message(progress_txt, align='center', blit_txt=False) fill() blit(progress_msg, 5, P.screen_c) flip() any_key() if self.search_type == SPACE: block_type_txt = self.anykey_text.format(self.spatial_instructions) else: block_type_txt = self.anykey_text.format( self.temporal_instructions) block_type_msg = message(block_type_txt, align='left', blit_txt=False) fill() blit(block_type_msg, 5, P.screen_c) flip() any_key()
def instructions(self): p1 = ( "During this task, you will presented with a sequence of numbers in the middle of " "the screen.\n\nPress any key to see an example.") p2 = ( "Your task will be to press the [space] key as quickly as possible whenever a " "number other than {0} appears, and to withhold your response whenever the number " "is {0}.\n\nPress any key to continue.".format(P.target)) p3 = ( "Occasionally, the task will be interrupted by screens asking you about your " "focus just prior.\nWhen this happens, please select the most accurate " "response using the mouse cursor.\n\nPress any key to see an example." ) p4 = ( "After responding, you will be asked how much effort you " "were putting into staying focused on the task before the interruption.\n\n" "When this happens, please answer using the scale from 0% to 100% that will appear " "on screen. Your responses will be anonymous so please answer honestly.\n\n" "Press any key to see an example.") msg1 = message(p1, 'normal', blit_txt=False, align='center') msg2 = message(p2, 'normal', blit_txt=False, align='center', wrap_width=int(P.screen_x * 0.8)) msg3 = message(p3, 'normal', blit_txt=False, align='center', wrap_width=int(P.screen_x * 0.8)) msg4 = message(p4, 'normal', blit_txt=False, align='center', wrap_width=int(P.screen_x * 0.8)) # First page of instructions flush() fill() blit(msg1, 5, P.screen_c) flip() any_key(allow_mouse_click=False) # Example stimuli numlist = [1, 2, 3, 4, 5, 6, 7, 8, 9] random.shuffle(numlist) for n in numlist[1:5]: trialtime = CountDown(1.125) numsize = random.choice(self.sizes) while trialtime.counting(): ui_request() fill() mask_on = trialtime.elapsed() > 0.25 if mask_on: blit(self.mask_x, 5, P.screen_c) blit(self.mask_ring, 5, P.screen_c) else: blit(self.digits[n][numsize], 5, P.screen_c) flip() # Task explanation/illustration flush() fill() blit(msg2, 5, P.screen_c) flip() any_key(allow_mouse_click=False) # Probe explanation + example probe fill() blit(msg3, 5, P.screen_c) flip() any_key(allow_mouse_click=False) self.probe.collect() # Slider explanation + example slider fill() blit(msg4, 5, P.screen_c) flip() any_key(allow_mouse_click=False) self.get_effort()
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): self.blit_it(self.fixation) any_key()
def present_txt(self, txt): msg = message(txt, align='center', blit_txt=False) self.blit_it(msg) any_key()
def show_error_message(self, msg_key): fill() blit(self.err_msgs[msg_key], location=P.screen_c, registration=5) flip() any_key()
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 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 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 block(self): if not P.practicing: if P.trial_number % 60 == 0: rest_txt = "Whew, go ahead a take a break!\nPress any key when you're ready to continue." rest_msg = message(rest_txt, align='center', blit_txt=False) fill() blit(rest_msg, 5, P.screen_c) flip() any_key() self.t1_performance = 0 # Present block progress block_txt = "Block {0} of {1}".format(P.block_number, P.blocks_per_experiment) progress_txt = self.anykey_txt.format(block_txt) if P.practicing: progress_txt += "\n(This is a practice block)" progress_msg = message(progress_txt, align='center', blit_txt=False) fill() blit(progress_msg, 5, P.screen_c) flip() any_key() # Inform as to block type if self.block_type == COLOUR: if P.practicing: block_type_txt = self.anykey_txt.format( self.prac_colour_instruct) else: block_type_txt = self.anykey_txt.format( self.test_colour_instruct) else: if P.practicing: block_type_txt = self.anykey_txt.format( self.prac_identity_instruct) else: block_type_txt = self.anykey_txt.format( self.test_identity_instruct) block_type_msg = message(block_type_txt, align='center', blit_txt=False) fill() blit(block_type_msg, 5, P.screen_c) flip() any_key() # Pre-run: First 10 practice trials, no performance adjustments self.pre_run_complete = False # Practice: Subsequent practice trials wherein performance is adjusted self.practice_complete = False self.practice_trial_num = 1 # Reset T1 performance each practice block self.t1_performance = 0 # The following block manually inserts trials one at a time # during which performance is checked and adjusted for. if P.practicing: while P.practicing: self.itoa = random.choice([100, 200, 300]) self.ttoa = random.choice([120, 240, 360, 480, 600]) self.setup_response_collector() self.trial_prep() self.evm.start_clock() try: self.trial() except TrialException: pass self.evm.stop_clock() self.trial_clean_up() # Once practice is complete, the loop is exited if self.practice_complete: P.practicing = False
def collect_demographics(anonymous=False): '''Collects participant demographics and writes them to the 'participants' table in the experiment's database, based on the queries in the "demographic" section of the project's user_queries.json file. If P.manual_demographics_collection = True, this function should be called at some point during the setup() section of your experiment class. Otherwise, this function will be run automatically when the experiment is launched. Args: anonymous (bool, optional): If True, this function will log all of the anonymous values for the experiment's demographic queries to the database immediately without prompting the user for input. ''' from klibs.KLEnvironment import exp, db # ie. demographic questions aren't being asked for this experiment if not P.collect_demographics and not anonymous: return # first insert required, automatically-populated fields demographics = EntryTemplate('participants') demographics.log('created', now(True)) try: # columns moved to session_info in newer templates demographics.log("random_seed", P.random_seed) demographics.log("klibs_commit", P.klibs_commit) except ValueError: pass # collect a response and handle errors for each question for q in user_queries.demographic: if q.active: # if querying unique identifier, make sure it doesn't already exist in db if q.database_field == P.unique_identifier: # TODO: fix this to work with multi-user mode existing = db.query("SELECT `{0}` FROM `participants`".format( q.database_field)) while True: value = query(q, anonymous=anonymous) if utf8(value) in [utf8(val[0]) for val in existing]: err = ("A participant with that ID already exists!\n" "Please try a different identifier.") fill() blit( message(err, "alert", align='center', blit_txt=False), 5, P.screen_c) flip() any_key() else: break else: value = query(q, anonymous=anonymous) demographics.log(q.database_field, value) # typical use; P.collect_demographics is True and called automatically by klibs if not P.demographics_collected: P.participant_id = db.insert(demographics) P.p_id = P.participant_id P.demographics_collected = True # Log info about current runtime environment to database if 'session_info' in db.table_schemas.keys(): runtime_info = EntryTemplate('session_info') for col, value in runtime_info_init().items(): runtime_info.log(col, value) db.insert(runtime_info) # Save copy of experiment.py and config files as they were for participant if not P.development_mode: pid = P.random_seed if P.multi_user else P.participant_id # pid set at end for multiuser P.version_dir = join(P.versions_dir, "p{0}_{1}".format(pid, now(True))) os.mkdir(P.version_dir) copyfile("experiment.py", join(P.version_dir, "experiment.py")) copytree(P.config_dir, join(P.version_dir, "Config")) else: # The context for this is: collect_demographics is set to false but then explicitly called later db.update(demographics.table, demographics.defined) if P.multi_session_project and not P.manual_demographics_collection: try: exp.init_session() except: pass
def present_fixation(self): fill() blit(self.fixation, location=P.screen_c, registration=5) flip() any_key()
def show_instruction(self, instruction, registration, location): fill() blit(instruction, registration, location) flip() smart_sleep(500) any_key()
def instructions(self): p1 = ( "During this task, you will presented with a sequence of letters in the middle of " "the screen.\n\nPress any key to see an example.") p2a = ( "For some blocks, your task will be to indicate whether each letter matches " "the letter just before it:") p2b = ( "For others, your task will be to indicate whether each letter matches " "the letter two before it:") p2c = "(matches for each block type are highlighted)" p3 = ( "Occasionally, the task will be interrupted by screens asking you about your " "focus just prior.\nWhen this happens, please select the most accurate " "response using the mouse cursor.\n\nPress any key to see an example." ) msg1 = message(p1, 'normal', blit_txt=False, align='center') msg2a = message(p2a, 'normal', blit_txt=False, wrap_width=int(P.screen_x * 0.8)) msg2b = message(p2b, 'normal', blit_txt=False, wrap_width=int(P.screen_x * 0.8)) msg2c = message(p2c, 'normal', blit_txt=False) msg3 = message(p3, 'normal', blit_txt=False, align='center', wrap_width=int(P.screen_x * 0.8)) # First page of instructions flush() fill() blit(msg1, 5, P.screen_c) flip() any_key(allow_mouse_click=False) # Example stimuli for letter in (1, 2, 1): trialtime = CountDown(2.5) while trialtime.counting(): mask_on = trialtime.elapsed() > 0.5 stim = self.mask if mask_on else self.letters[ self.letter_set[letter]] ui_request() fill() blit(self.accuracy_rect, 5, P.screen_c) blit(self.accuracy_mask, 5, P.screen_c) blit(stim, 5, P.screen_c) flip() # Task explanation/illustration fill() blit(msg2a, 8, (P.screen_c[0], int(P.screen_y * 0.15))) blit(msg2b, 8, (P.screen_c[0], int(P.screen_y * 0.45))) blit(msg2c, 2, (P.screen_c[0], int(P.screen_y * 0.8))) self.draw_nback_illustration(int(P.screen_y * 0.3), target=5) self.draw_nback_illustration(int(P.screen_y * 0.6), target=4) flip() any_key(allow_mouse_click=False) # Probe explanation + example probe fill() blit(msg3, 5, P.screen_c) flip() any_key() self.probe.collect()