def pre_trial(self): fill() blit(self.fixation, location=P.screen_c, registration=5) flip() any_key()
def get_effort(self): slider_loc = (P.screen_c[0], int(P.screen_y * 0.55)) slider_cols = {'line': WHITE, 'slider': TRANSLUCENT_WHITE} scale = Slider(int(P.screen_x * 0.75), ticks=5, location=slider_loc, fills=slider_cols) label_pad = scale.tick.surface_height show_mouse_cursor() onset = time.time() while True: sq = pump(True) ui_request(queue=sq) fill() blit(self.effort_q, 5, (P.screen_c[0], int(P.screen_y * 0.3))) blit(self.mineffort_msg, 8, (scale.xmin, slider_loc[1] + label_pad)) blit(self.maxeffort_msg, 8, (scale.xmax, slider_loc[1] + label_pad)) scale.draw() if scale.pos != None: self.submit.draw() flip() scale.listen(sq) if scale.pos != None: if self.submit.listen(sq) or key_pressed('Return', queue=sq): rt = time.time() - onset hide_mouse_cursor() return (scale.pos, rt)
def feedback(self, response): correct_response = True if response == self.tilt_line_location else False # Every 5 trials of a particular payoff, ask anticipated earnings if self.potential_payoff == HIGH: self.high_value_trial_count += 1 if self.high_value_trial_count in [5, 10, 15]: self.query_learning(HIGH) else: self.low_value_trial_count += 1 if self.low_value_trial_count in [5, 10, 15]: self.query_learning(LOW) # Determine payout for trial if correct_response & (self.winning_trial == YES): points = self.payout() msg = message("You won {0} points!".format(points), 'myText', blit_txt=False) else: points = self.penalty msg = message("You lost 5 points!", 'myText', blit_txt=False) # Keep tally of score self.total_score += points feedback = [points, msg] # Present score feedback_exposure = CountDown(self.feedback_exposure_period) fill() blit(feedback[1], location=P.screen_c, registration=5) flip() while feedback_exposure.counting(): ui_request() return feedback[0]
def block(self): # Block type defaults to probe trials, overidden in practice block(s) self.block_type = PROBE # Show total score following completion of bandit task if self.total_score: fill() score_txt = "Total block score: {0} points!".format( self.total_score) msg = message(score_txt, 'timeout', blit_txt=False) blit(msg, 5, P.screen_c) flip() any_key() self.total_score = 0 # Reset score once presented # Bandit task if P.practicing: self.block_type == BANDIT # Initialize selection counters self.times_selected_high = 0 self.time_selected_low = 0 # End of block messaging if not P.practicing: self.block_type == PROBE fill() msg = message(self.end_of_block_txt, blit_txt=False) blit(msg, 5, P.screen_c) flip() any_key()
def block(self): # Show total score following completion of training task if self.total_score: fill() score_txt = "Total block score: {0} points!".format( self.total_score) msg = message(score_txt, 'myText', blit_txt=False) blit(msg, 5, P.screen_c) flip() any_key() self.total_score = 0 # Reset score once presented # Training task if P.practicing: self.block_type = TRAINING # Initialize selection counters self.high_value_trial_count = 0 self.low_value_trial_count = 0 # End of block messaging if not P.practicing: self.block_type = PROBE fill() msg = message(self.end_of_block_txt, 'myText', blit_txt=False) blit(msg, 5, P.screen_c) flip() any_key()
def block(self): # 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: block_type_txt = self.anykey_txt.format(self.colour_instruct) else: block_type_txt = self.anykey_txt.format(self.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()
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 wheel_callback(self, wheel): # Hide cursor during selection phase hide_mouse_cursor() # Response request msg colour_request_msg = self.t1_col_request if wheel == self.t1_wheel else self.t2_col_request message_offset = deg_to_px(1.5) message_loc = (P.screen_c[0], (P.screen_c[1] - message_offset)) fill() # Present appropriate wheel if wheel == self.t1_wheel: blit(self.t1_wheel, registration=5, location=P.screen_c) else: blit(self.t2_wheel, registration=5, location=P.screen_c) # Present response request message(colour_request_msg, location=message_loc, registration=5, blit_txt=True) # Present annulus drawbject as cursor blit(self.cursor, registration=5, location=mouse_pos()) flip()
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 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 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 trial(self): # Clear screen and wait for target onset fill() flip() while self.evm.before('target_on'): ui_request() # Start timer and collect response self.rc.collect() response = self.rc.keypress_listener.response() # Stop timer and show for 1sec after response is made elapsed_msg = message(str(int(response.rt)).zfill(4), style='PVT', blit_txt=False) feedback_timer = CountDown(1) while feedback_timer.counting(): fill() blit(elapsed_msg, 5, P.screen_c) flip() # Log trial data to database return { "block_num": P.block_number, "trial_num": P.trial_number, "isi": self.interstim_interval, "rt": response.rt }
def display_refresh(self, cue=False, target=False): # In keypress condition, after target presented, check that gaze # is still within fixation bounds and print message at end if not if P.keypress_response_cond and not self.before_target: gaze = self.el.gaze() if not self.bi.within_boundary(label='drift_correct', p=gaze): self.moved_eyes_during_rc = True fill() blit(self.fix, registration=5, location=P.screen_c) blit(self.placeholder, registration=5, location=self.box1_loc) blit(self.placeholder, registration=5, location=self.box2_loc) if cue: blit(self.cue, registration=5, location=self.cue_loc) if target: if self.target_location != 'catch': blit(self.target, registration=5, location=self.target_loc) if self.before_target: self.before_target = False flip()
def init_figure_set(self): if not self.exp.figure_set_name or self.exp.figure_set_name == "NA": return if not self.exp.figure_set_name in self.exp.figure_sets: e_msg = "No figure set named '{0}' is registered.".format( self.exp.figure_set_name) raise ValueError(e_msg) # Verify that all figures listed in figure set exist, raising error if it doesn't figure_set = self.exp.figure_sets[self.exp.figure_set_name] for f in figure_set.names: f_path = os.path.join(P.resources_dir, "figures", f) if not os.path.exists(f_path + ".zip") and f != "random": fill() e_msg = ( "The figure '{0}' listed in the figure set '{1}' wasn't found.\n" "Please check that the file is named correctly and try again. " "TraceLab will now exit.".format(f, self.exp.figure_set_name)) blit(message(e_msg, blit_txt=False), 5, P.screen_c) flip() any_key() self.exp.quit() # Overwrite 'figure_name' trial factor with values defined in chosen figure set self.exp.trial_factory.exp_factors['figure_name'] = figure_set.to_list( )
def feedback(self, response): if self.winning_bandit == HIGH: winning_bandit_loc = self.high_value_location else: winning_bandit_loc = self.low_value_location if response == winning_bandit_loc: points = self.bandit_payout(value=self.winning_bandit) msg = message("You won {0} points!".format(points), "score up", blit_txt=False) else: points = self.penalty # -5 msg = message("You lost 5 points!", "score down", blit_txt=False) self.total_score += points feedback = [points, msg] feedback_exposure = CountDown(self.feedback_exposure_period) while feedback_exposure.counting(): ui_request() fill() blit(feedback[1], location=P.screen_c, registration=5) flip() return feedback[0]
def trial_clean_up(self): # Reset ResponseCollectors self.t1_colouring_rc.color_listener.reset() self.t2_colouring_rc.color_listener.reset() self.t1_identity_rc.keypress_listener.reset() self.t2_identity_rc.keypress_listener.reset() # Switch block type if not P.practicing: if P.trial_number == P.trials_per_block: if P.block_number < P.blocks_per_experiment: if self.block_type == IDENTITY: self.block_type = COLOUR else: self.block_type = IDENTITY else: if P.trial_number == P.trials_per_practice_block: if self.block_type == IDENTITY: self.block_type = COLOUR else: self.block_type = IDENTITY if P.trial_number == P.trials_per_block: break_txt = self.anykey_txt.format("Good work! Take a break") break_msg = message(break_txt, align='center', blit_txt=False) fill() blit(break_msg, registration=5, location=P.screen_c) flip() any_key()
def trial(self): # Set to true once played (to avoid repeats) tone_played = False # Prior to target onset, present fixation, tone & cue (when applicable) while self.evm.before('target_on'): fill() blit(self.fixation, registration=5, location=P.screen_c) # Tone if self.tone_trial and self.evm.between('tone_on', 'tone_off'): if not tone_played: self.warning_tone.play() tone_played = True # Cue if self.cue_location is not None and self.evm.between( 'cue_on', 'cue_off'): loc = self.cue_locations[self.cue_location] blit(self.cue, registration=5, location=loc) flip() # For some reason the cursor reappears here... hide_mouse_cursor() # Present target fill() blit(self.fixation, registration=5, location=P.screen_c) for shape, loc in self.arrows: blit(shape, registration=5, location=loc) flip() # Listen for response self.rc.collect() # Record response value & rt response, rt = self.rc.keypress_listener.response() # If no (valid) response made before timeout if rt == klibs.TIMEOUT: response = 'na' # Label response as correct/incorrect accuracy = int(response == self.target_direction) # Record trial data to database return { "block_num": P.block_number, "trial_num": P.trial_number, "practicing": P.practicing, "tone_trial": self.tone_trial, "tone_onset": self.tone_onset, "cue_type": self.cue_type, "congruent": self.congruent, "target_location": self.target_location, "target_direction": self.target_direction, "accuracy": accuracy, "response": response, "rt": rt }
def draw_image_line(self, width, line, totlines, buff): ''' Reads in the buffer from the EyeLink camera image line by line and writes it into a buffer of size (width * totlines). Once the last line of the image has been read into the buffer, the image buffer is placed in a PIL.Image with the palette set by set_image_palette, converted to RGBA, resized, and then rendered to the middle of the screen. After rendering, the image buffer is cleared. ''' if len(self.imagebuffer) > (width * totlines): self.imagebuffer = [] self.imagebuffer += buff if int(line) == int(totlines): # Render complete camera image and resize to self.size img = Image.new("P", (width, totlines), 0) img.putpalette(self.palette) img.putdata(self.imagebuffer) self.img = img.convert('RGBA').resize(self.size, Image.BILINEAR) # Set up aggdraw to draw crosshair/bounds/etc. on image surface self.drawer = Draw(self.img) self.drawer.setantialias(True) self.draw_cross_hair() self.drawer.flush() # Draw complete image to screen fill() blit(asarray(self.img), 5, P.screen_c) if self.title: loc_x = (P.screen_c[0]) loc_y = (P.screen_c[1] + self.size[1] / 2 + 20) blit(self.title, 8, (loc_x, loc_y)) flip() # Clear image buffer self.imagebuffer = []
def ANT_callback(self): if self.evm.after('target_off') and not self.targets_removed: fill() blit(self.fixation, 5, P.screen_c) flip() self.targets_removed == True
def present_temporal_stream(self, temporal_stream): duration_cd = CountDown(ITEM_DURATION, start=False) mask_cd = CountDown(MASK_DURATION, start=False) response_window = CountDown(2, start=False) last_item = True if len(temporal_stream) == 1 else False item = temporal_stream.pop() if item[1]: self.target_onset = self.target_sw.elapsed() duration_cd.start() while duration_cd.counting(): self.blit_img(item[0], reg=5, loc=P.screen_c) mask_cd.start() while mask_cd.counting(): self.blit_img(self.mask, reg=5, loc=P.screen_c) if last_item: clear() response_window.start() while response_window.counting(): fill() flip()
def identity_callback(self, target): # Request appropriate identity identity_request_msg = self.t1_id_request if target == "T1" else self.t2_id_request fill() message(identity_request_msg, location=P.screen_c, registration=5, blit_txt=True) flip()
def imagery_trial(self): fill() blit(self.origin_inactive, 5, self.origin_pos, flip_x=P.flip_x) flip() start = self.evm.trial_time if P.demo_mode or P.dm_always_show_cursor: show_mouse_cursor() at_origin = False while not at_origin: x, y, button = mouse_pos(return_button_state=True) left_button_down = button == 1 if self.within_boundary('origin', (x, y)) and left_button_down: at_origin = True self.rt = self.evm.trial_time - start ui_request() fill() blit(self.origin_active, 5, self.origin_pos, flip_x=P.flip_x) flip() while at_origin: x, y, button = mouse_pos(return_button_state=True) left_button_down = button == 1 if not (self.within_boundary('origin', (x, y)) and left_button_down): at_origin = False self.mt = self.evm.trial_time - (self.rt + start) if P.demo_mode: hide_mouse_cursor()
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 block(self): if self.total_score: fill() score_txt = "Total block score: {0} points!".format( self.total_score) msg = message(score_txt, 'timeout', blit_txt=False) blit(msg, 5, P.screen_c) flip() any_key() self.total_score = 0 # reset total bandit score each block # Change bandit colours between blocks if P.practicing: greys = [(0, 0, 0, 255), (96, 96, 96, 255)] random.shuffle(greys) self.high_value_color = greys[0] self.low_value_color = greys[1] else: bandit_colours = self.bandit_colour_combos.pop() random.shuffle(bandit_colours) self.high_value_color = bandit_colours[0] self.low_value_color = bandit_colours[1] # Calibrate microphone for audio responses (people get quieter over time) threshold = self.audio.calibrate() self.probe_rc.audio_listener.threshold = threshold self.bandit_rc.audio_listener.threshold = threshold
def calibrate(self): """Determines the loudness threshold for vocal responses based on sample input from the participant. During calibration, input levels are monitored during three 3-second intervals in which participants are asked to make a single vocal response. After all three samples are collected, the threshold is set to the smallest peak value of the three samples, and the participant is prompted to make one more response to see if it passes the threshold. If it does, calibration is complete and will end after any key is pressed. If it doesn't, the participant will be notified that calibration wasn't sucessful and will be prompted to press 'c' to calibrate again, or 'v' to try validation again. As a convenience for programmers writing and testing experiments using audio input, if KLibs is in development mode and the Params option 'dm_auto_threshold' is set to True, this calibration process will be skipped for a quicker one requiring no user input. In this mode, the ambient room noise is recorded for one second after a countdown, and the threshold is then set to be five times the average peak volume from that interval. This will not work if your microphone does not pick up any ambient room noise. Returns: int: an integer from 1 to 32767 representing the threshold value to use for vocal responses. Raises: RuntimeError: If using auto thresholding and the recorded ambient noise level is 0. """ if not self.stream: self.stream = self.exp.audio.stream if P.development_mode and P.dm_auto_threshold: ambient = self.get_ambient_level() if ambient == 0: e = ( "Ambient level appears to be zero, increase the gain on your microphone or " "disable auto-thresholding.") raise RuntimeError(e) elif ambient * 5 > 32767: e = ( "Ambient noise level too high to use auto-thresholding. Reduce the gain on " "your microphone or try and reduce the noise level in the room." ) raise RuntimeError(e) self.threshold = ambient * 5 else: peaks = [] for i in range(0, 3): msg = "Provide a normal sample of your intended response." peaks.append(self.get_peak_during(3, msg)) if i < 2: s = "" if i == 1 else "s" # to avoid "1 more samples" next_message = ("Got it! {0} more sample{1} to collect. " "Press any key to continue".format( 2 - i, s)) fill() message(next_message, location=P.screen_c, registration=5) flip() any_key() self.threshold = min(peaks) self.__validate() return self.threshold
def present_target(self): msg = "This is your target!" msg_loc = [P.screen_c[0], (P.screen_c[1] - deg_to_px(2))] fill() message(msg, location=msg_loc, registration=5) blit(self.target_item, location=P.screen_c, registration=5) flip()
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 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 rc_callback(self): fill() blit(self.fixation, registration=5, location=P.screen_c) for shape, loc in self.arrows: blit(shape, registration=5, location=loc) flip()
def draw_cal_target(self, x, y=None, pump_events=True): fill() if pump_events: pump() if y is None: y = x[1] x = x[0] blit(self.dc_target, 5, (int(x), int(y))) flip()
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 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 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()