def start_trial_button(self): fill() blit(self.next_trial_box, 5, self.next_trial_button_loc, flip_x=P.flip_x) blit(self.next_trial_msg, 5, self.next_trial_button_loc, flip_x=P.flip_x) flip() if P.demo_mode or P.dm_always_show_cursor: show_mouse_cursor() flush() clicked = False while not clicked: event_queue = pump(True) for e in event_queue: if e.type == SDL_MOUSEBUTTONDOWN: clicked = self.within_boundary("next trial button", [e.button.x, e.button.y]) elif e.type == SDL_KEYDOWN: ui_request(e.key.keysym) if not (P.demo_mode or P.dm_always_show_cursor): hide_mouse_cursor()
def log_and_recycle_trial(self, err_type): """ Renders an error message to the screen and wait for a response. When a response is made, the incomplete trial data is logged to the trial_err table and the trial is recycled. """ flush() fill() message(self.err_msgs[err_type], registration=5, location=P.screen_c) flip() any_key() err_data = { "participant_id": P.participant_id, "block_num": P.block_number, "trial_num": P.trial_number, "session_type": 'saccade' if P.saccade_response_cond else 'keypress', "cue_location": self.cue_location, "target_location": self.target_location, "start_axis": self.start_axis, "box_rotation": self.rotation_dir if self.animation_trial else NA, "animation_trial": boolean_to_logical(self.animation_trial), "err_type": err_type } self.database.insert(data=err_data, table="trials_err") raise TrialException(self.err_msgs[err_type])
def trial_prep(self): # Infer the cue location based on starting axis (ie. left and top boxes are 'box 1', # bottom and right are 'box 2') if self.cue_location == BOX_1: self.cue_location = LEFT if self.start_axis is H_START_AXIS else TOP else: self.cue_location = RIGHT if self.start_axis is H_START_AXIS else BOTTOM # Reset trial flags self.before_target = True self.target_acquired = False self.moved_eyes_during_rc = False # Add timecourse of events to EventManager self.evm.register_tickets([ ("cross fix end", 300), ("circle fix end", 1100), #800ms after cross fix end ("cue end", 1400), #300ms after circle fix end ("circle box end", 1600), #200ms after cue end ("animation end", 1900), #300ms after circle box end ("asterisk end", 2060), #160ms after animation end ("task end", 4560) #2500ms after asterisk end ]) # Perform drift correct with red fixation cross, changing to white upon # completion self.display_refresh(self.start_axis, self.cross_r) self.el.drift_correct(fill_color=BLACK, el_draw_fixation=EL_FALSE) self.display_refresh(self.start_axis, self.cross_w) flush()
def block(self): # Show block message at start of every block 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) practice_msg = "During this block you will be given feedback for your responses." msg = message(header + "\n" + practice_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() start_msg = message("Press any key to start.", blit_txt=False) fill() blit(msg, 8, (P.screen_c[0], P.screen_y * 0.4)) blit(start_msg, 5, [P.screen_c[0], P.screen_y * 0.6]) flip() any_key()
def block_msg(self, text): msg = message(text, 'block_msg', blit_txt=False) fill() blit(msg, 5, P.screen_c) flip() flush() any_key()
def trial(self): while self.evm.before('cue_on'): self.wait_time() self.display_refresh(cue=True) while self.evm.before('cue_off'): self.wait_time() self.display_refresh() while self.evm.before('target_on'): self.wait_time() flush() self.display_refresh(target=True) if P.saccade_response_cond: self.record_saccades() keypress_rt = 'NA' if P.keypress_response_cond: self.rc.collect() keypress_rt = self.rc.keypress_listener.response(rt=True, value=False) clear() smart_sleep(1000) if P.keypress_response_cond: if self.target_location == "catch" and keypress_rt != TIMEOUT: fill() message(self.err_msgs['early'], registration=5, location=P.screen_c) flip() any_key() elif self.moved_eyes_during_rc: fill() message("Moved eyes during response interval!", registration=5, location=P.screen_c) flip() any_key() return { "block_num": P.block_number, "trial_num": P.trial_number, 'session_type': 'saccade' if P.saccade_response_cond else 'keypress', 'box_alignment': self.box_alignment, 'cue_location': self.cue_location, 'target_location': self.target_location, 'target_acquired': str(self.target_acquired).upper() if P.saccade_response_cond else NA, 'keypress_rt': keypress_rt if P.keypress_response_cond else NA, 'moved_eyes': str(self.moved_eyes_during_rc).upper() if P.keypress_response_cond else NA }
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 trial_prep(self): if P.development_mode: print "\ntrial factors" print "======================" fill() msg = "Box Alignment: {0}\nCue Location: {1}\nTarget Location: {2}".format( self.box_alignment, self.cue_location, self.target_location) print msg print "======================" message(msg, registration=5, location=P.screen_c, blit_txt=True) flip() any_key() clear() any_key() self.placeholder = self.construct_placeholder() self.cue = self.construct_cue() self.cue_loc = self.locations[self.cue_location] self.set_box_positions() self.target_trial = False if self.target_location != 'catch': self.target_trial = True self.target_loc = self.get_target_location() self.evm.register_tickets([ ('cue_on', 1000), # Cue appears 1000ms after drift check ('cue_off', 1100), # Cue removed after 100ms ('target_on', 1960), # Target appears 860ms after cue removal ('task_end', 4460) # 2500ms to respond to target before trial aborts ]) # Reset trial flags self.before_target = True self.target_acquired = False self.moved_eyes_during_rc = False self.display_refresh() self.el.drift_correct(fill_color=BLACK, draw_target=EL_FALSE) self.fix.fill = WHITE self.display_refresh() flush()
def block(self): block_num = P.block_number block_count = P.blocks_per_experiment # Display progress messages at start of blocks if block_num > 1: flush() fill() block_msg = "Completed block {0} of {1}. Press any key to continue." block_msg = block_msg.format(block_num - 1, block_count) message(block_msg, registration=5, location=P.screen_c) flip() any_key()
def get_peak_during(self, period, msg=None): """Determines the peak loudness value recorded over a given period. Displays a visual callback that shows the current input volume and the loudest peak encounteredduring the interval so far. Args: period (numeric): the number of seconds to record input for. msg (:obj:`~klibs.KLGraphics.KLNumpySurface.NumpySurface`, optional): a rendered message to display in the top-right corner of the screen during the sampling loop. Returns: int: the loudest peak of all samples recorded during the period. """ local_peak = 0 last_sample = 0 if msg: msg = message(msg, blit_txt=False) flush() self.stream.start() sample_period = CountDown(period + 0.05) while sample_period.counting(): ui_request() sample = self.stream.sample().peak if sample_period.elapsed() < 0.05: # Sometimes 1st or 2nd peaks are extremely high for no reason, so ignore first 50ms continue if sample > local_peak: local_peak = sample sample_avg = (sample + last_sample) / 2 peak_circle = peak(5, int( (local_peak / 32767.0) * P.screen_y * 0.8)) sample_circle = peak( 5, int((sample_avg / 32767.0) * P.screen_y * 0.8)) last_sample = sample fill() blit(Ellipse(peak_circle, fill=[255, 145, 0]), location=P.screen_c, registration=5) blit(Ellipse(sample_circle, fill=[84, 60, 182]), location=P.screen_c, registration=5) if msg: blit(msg, location=[25, 25], registration=7) flip() self.stream.stop() return local_peak
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 collect_response(self): self.start = time.time() finished = False selection = None last_selected = None flush() mt_start = None while not finished: show_mouse_cursor() events = pump(True) for e in events: if e.type == sdl2.SDL_KEYDOWN: ui_request(e.key.keysym) elif e.type == sdl2.SDL_MOUSEBUTTONDOWN: selection = None for b in self.buttons: if self.within_boundary(b.button_text, [e.button.x, e.button.y]): self.toggle(b) if not self.rt: self.rt = time.time() - self.start mt_start = time.time() if b.active: selection = b last_selected = b if callable(b.callback): if self.finish_b is None: return b.callback else: b.callback() try: if self.finish_b.active and self.within_boundary( "Done", [e.button.x, e.button.y]): self.response = int(last_selected.button_text) self.mt = time.time() - mt_start finished = True except AttributeError: pass try: self.finish_b.active = selection is not None except AttributeError: pass self.render() fill() flip() hide_mouse_cursor()
def physical_trial(self): self.rc.collect() self.rt = self.rc.draw_listener.start_time self.drawing = self.rc.draw_listener.responses[0][0] self.it = self.rc.draw_listener.first_sample_time - self.rt self.mt = self.rc.draw_listener.responses[0][1] if self.feedback_type in (FB_ALL, FB_RES) and not self.__practicing__: flush() fill() blit(self.figure.render(trace=self.drawing), 5, P.screen_c, flip_x=P.flip_x) flip() start = time.time() while time.time() - start < P.feedback_duration / 1000.0: ui_request()
def slide(self): show_mouse_cursor() self.blit() dragging = False while True: if not dragging: m_pos = mouse_pos() for event in pump(True): if event.type == sdl2.SDL_KEYDOWN: ui_request(event.key.keysym) elif event.type in (sdl2.SDL_MOUSEBUTTONDOWN, sdl2.SDL_MOUSEBUTTONUP): within_button = self.within_boundary("button", m_pos) if self.button_active and within_button: return self.response dragging = self.within_boundary("handle", m_pos) if dragging: button_up = False off_handle = False for event in pump(True): if event.type == sdl2.SDL_KEYDOWN: ui_request(event.key.keysym) elif event.type == sdl2.SDL_MOUSEBUTTONUP: button_up = True off_handle = not self.within_boundary("handle", mouse_pos()) if off_handle or button_up: dragging = False self.response = self.handle_value() self.button_active = True flush() return -1 self.handle_pos = mouse_pos()[0] self.blit() return False
def block(self): block_num = P.block_number block_count = P.blocks_per_experiment # Display progress messages at start of blocks if block_num > 1: flush() fill() block_msg = "Completed block {0} of {1}. Press any key to continue." block_msg = block_msg.format(block_num - 1, block_count) message(block_msg, registration=5, location=P.screen_c) flip() any_key() # When running participants, notify researcher at halfway point & last block via Slack if P.slack_messaging and not P.development_mode: if block_num == ( (block_count + 1) / 2) + 1: # If participant is halfway done slack_message("Halfway done ({0}/{1})".format( block_num, block_count)) elif block_num == block_count: # If participant is on last block slack_message("On last block ({0}/{1})".format( block_num, block_count))
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): while self.evm.before("cross fix end"): self.jc_wait_time() self.display_refresh(self.start_axis, self.cross_w) while self.evm.before("circle fix end"): self.jc_wait_time() self.display_refresh(self.start_axis, self.circle) while self.evm.before("cue end"): self.jc_wait_time() self.display_refresh(self.start_axis, self.circle, cue=self.cue_location) while self.evm.before("circle box end"): self.jc_wait_time() self.display_refresh(self.start_axis, self.circle) current_frame = 0 while self.evm.before("animation end"): self.jc_wait_time() if self.animation_trial: if current_frame < self.animation_frames: if self.evm.trial_time_ms > ( current_frame * self.frame_duration + 1600): box_locs = self.frames[self.start_axis][ self.rotation_dir][current_frame] self.display_refresh(box_locs, self.asterisk) current_frame += 1 else: self.display_refresh(self.start_axis, self.asterisk) while self.evm.before("asterisk end"): self.display_refresh(self.box_axis_during_target(), self.circle) self.jc_wait_time() flush() self.display_refresh(self.box_axis_during_target(), self.circle, target=self.target_location) if P.saccade_response_cond: self.jc_saccade_data() keypress_rt = NA if P.keypress_response_cond: self.rc.collect() keypress_rt = self.rc.keypress_listener.response(rt=True, value=False) clear() smart_sleep(1000) if P.keypress_response_cond: if self.target_location == "none" and keypress_rt != TIMEOUT: fill() message(self.err_msgs['early'], registration=5, location=P.screen_c) flip() any_key() elif self.moved_eyes_during_rc: fill() message("Moved eyes during response interval!", registration=5, location=P.screen_c) flip() any_key() return { "block_num": P.block_number, "trial_num": P.trial_number, "session_type": 'saccade' if P.saccade_response_cond else 'keypress', "cue_location": self.cue_location, "target_location": self.target_location, "start_axis": self.start_axis, "box_rotation": self.rotation_dir if self.animation_trial else NA, "animation_trial": str(self.animation_trial).upper(), "target_acquired": str(self.target_acquired).upper() if P.saccade_response_cond else NA, "keypress_rt": keypress_rt, "moved_eyes": str(self.moved_eyes_during_rc).upper() if P.keypress_response_cond else NA }
def query(query_ob, anonymous=False): '''Asks the user a question and collects the response via the keyboard. Intended for use with the queries contained within a project's user_queries.json file. This function is used internally for collecting demographics at the start of each run, but can also be used during experiment runtime to collect info from participants based on the queries contained in the "experimental" section of the user_queries.json file. Args: query_ob (:class:`~klibs.KLJSON_Object.AttributeDict`): The object containing the query to present. See :obj:`~klibs.KLCommunication.user_queries` for more information. anonymous (bool, optional): If True, will immediately return the query object's anonymous value without prompting the user (used interally for P.development_mode). Defaults to False. Returns: The response to the query, coerced to the type specified by query_ob.format.type (can be str, int, float, bool, or None). Raises: ValueError: If the query object's type is not one of "str", "int", "float", "bool", or None, or if a query_ob.format.range value is given and the type is not "int" or "float". TypeError: If query_ob.accepted is specified and it is not a list of values, or if a query_ob.format.range is specified and it is not a two-item list. ''' from klibs.KLEnvironment import txtm if anonymous: try: # Check if anon value is an EVAL statement, and if so evaluate it eval_statement = re.match(re.compile(u"^EVAL:[ ]*(.*)$"), query_ob.anonymous_value) if eval_statement: query_ob.anonymous_value = eval(eval_statement.group(1)) except TypeError: pass return query_ob.anonymous_value f = query_ob.format if f.type not in ("int", "float", "str", "bool", None): err = "Invalid data type for query '{0}': {1}".format( query_ob.title, f.type) raise ValueError(err) # Set defaults for styles and positioning if not specified if f.styles == 'default': f.styles = AttributeDict({ 'query': 'default', 'input': 'default', 'error': 'alert' }) locations = AttributeDict({ 'query': AUTO_POS, 'input': AUTO_POS, 'error': AUTO_POS }) registrations = AttributeDict({ 'query': AUTO_POS, 'input': AUTO_POS, 'error': AUTO_POS }) if f.positions == 'default': f.positions = AttributeDict({ 'locations': locations, 'registrations': registrations }) else: if f.positions.locations == 'default': f.positions.locations = locations if f.positions.registrations == 'default': f.positions.registrations = registrations q_text = message(query_ob.query, f.styles.query, align='center', blit_txt=False) # address automatic positioning p = f.positions if p.locations.query == AUTO_POS: p.locations.query = [P.screen_c[0], int(0.1 * P.screen_y)] p.registrations.query = BL_CENTER for k in ['input', 'error']: if p.locations[k] == AUTO_POS: v_pad = q_text.height + 2 * txtm.styles[f.styles.query].line_height p.locations[k] = [P.screen_c[0], p.locations.query[1] + v_pad] p.registrations[k] = BL_CENTER # Create an informative error message for invalid responses accepted_responses = query_ob.accepted # for code readability try: if accepted_responses: try: iter(accepted_responses) accepted_str = pretty_list(accepted_responses) invalid_answer_str = default_strings['invalid_answer'].format( accepted_str) except: raise TypeError( "The 'accepted' key of a question must be a list of values." ) elif f.range: if f.type not in ("int", "float"): raise ValueError( "Only queries with numeric types can use the range parameter." ) elif isinstance(f.range, list) == False or len(f.range) != 2: raise TypeError( "Query ranges must be two-item lists, containing an upper bound " "and a lower bound.") try: template = default_strings['out_of_range'] except KeyError: template = "Your answer must be a number between {0} and {1}, inclusive." invalid_answer_str = template.format(f.range[0], f.range[1]) except: cso("\n<red>Error encountered while parsing query '{0}':</red>".format( query_ob.title)) raise # user input loop; exited by breaking input_string = u'' # populated in loop below error_string = None user_finished = False # Clear event queue and draw query text to screen before entering input loop flush() SDL_StartTextInput() fill() blit(q_text, p.registrations.query, p.locations.query) flip() while not user_finished: for event in pump(True): if event.type == SDL_KEYDOWN: error_string = None # clear error string (if any) on new key event ui_request(event.key.keysym) sdl_keysym = event.key.keysym.sym if sdl_keysym == SDLK_ESCAPE: # Esc clears any existing input input_string = "" elif sdl_keysym == SDLK_BACKSPACE: # Backspace removes last character from input input_string = input_string[:-1] elif sdl_keysym in (SDLK_KP_ENTER, SDLK_RETURN): # Enter or Return check if a valid response has been made and end loop if it has if len(input_string) > 0: response = input_string # If type is 'int' or 'float', make sure input can be converted to that type if f.type == "int": try: response = int(input_string) except ValueError: error_string = "Please respond with an integer." elif f.type == "float": try: response = float(input_string) except ValueError: error_string = "Please respond with a number." # If no errors yet, check input against list of accepted values (if q has one) if not error_string: if accepted_responses: user_finished = response in accepted_responses if not user_finished: error_string = invalid_answer_str elif f.range: user_finished = (f.range[0] <= response <= f.range[1]) if not user_finished: error_string = invalid_answer_str else: user_finished = True elif query_ob.allow_null is True: user_finished = True else: # If no input and allow_null is false, display error error_string = default_strings['answer_not_supplied'] elif event.type == SDL_TEXTINPUT: input_string += event.text.text.decode('utf-8') if f.case_sensitive is False: input_string = input_string.lower() input_string = input_string.strip( ) # remove any trailing whitespace else: continue # If any text entered or error message encountered, render text for drawing if error_string: rendered_input = message(error_string, f.styles.error, blit_txt=False) input_string = "" elif len(input_string): if f.password: rendered_input = message(len(input_string) * '*', f.styles.input, blit_txt=False) else: rendered_input = message(input_string, f.styles.input, blit_txt=False) else: rendered_input = None # Draw question and any entered response to screen fill() blit(q_text, p.registrations.query, p.locations.query) if rendered_input: loc = p.locations.error if error_string else p.locations.input reg = p.registrations.error if error_string else p.registrations.input blit(rendered_input, reg, loc) flip() # Once a valid response has been made, clear the screen fill() flip() SDL_StopTextInput() if query_ob.allow_null and len(input_string) == 0: return None elif f.type == "int": return int(input_string) elif f.type == "str": if f.action == QUERY_ACTION_HASH: return make_hash(input_string) elif f.action == QUERY_ACTION_UPPERCASE: return utf8(input_string).upper() else: return utf8(input_string) elif f.type == "float": return float(input_string) elif f.type == "bool": return input_string in f.accept_as_true else: return input_string
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()
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 }