def trial(self): while self.evm.before("present_fixation", True): ui_request() self.blit_img(self.fixation, 5, P.screen_c) while self.evm.before("search_onset", True): ui_request() if self.search_type == SPACE: self.rc.collect() else: try: self.target_onset = 'NA' self.target_sw = Stopwatch() self.rc.collect() except IndexError: pass if len(self.rc.keypress_listener.responses): response, rt = self.rc.keypress_listener.response() if response != self.present_absent: self.present_feedback() else: response, rt = 'None', 'NA' clear() return { "practicing": str(P.practicing), "block_num": P.block_number, "trial_num": P.trial_number, "search_type": self.search_type, "stimulus_type": "LINE" if P.condition == 'line' else "COLOUR", "present_absent": self.present_absent, "set_size": self.set_size if self.search_type == SPACE else 'NA', "target_distractor": self.target_distractor, "distractor_distractor": self.distractor_distractor, "target_onset": self.target_onset if self.search_type == TIME else 'NA', "response": response, "rt": rt, "error": str(response != self.present_absent) }
def present_stream(self): # Each stream item presented for a pre-specified duration cd = CountDown(self.item_duration) sw = Stopwatch() for item in self.rsvp_stream: cd.reset() sw.reset() fill() blit(item, registration=5, location=P.screen_c) flip() #print(cd.elapsed) while cd.counting(): ui_request() print(sw.elapsed()) sw.reset()
def trial_prep(self): if self.search_type == SPACE: self.search_stimuli = self.prepare_spatial_array() self.rc.display_callback = self.present_spatial_array self.rc.display_kwargs = {'spatial_array': self.search_stimuli} else: self.search_stimuli = self.prepare_temporal_stream() self.rc.display_callback = self.present_temporal_stream self.rc.display_kwargs = {'temporal_stream': self.search_stimuli} events = [[1000, 'present_example_target']] events.append([events[-1][0] + 1000, 'present_fixation']) events.append([events[-1][0] + 1000, 'search_onset']) for e in events: self.evm.register_ticket(ET(e[1], e[0])) self.trial_sw = Stopwatch() self.present_target() hide_mouse_cursor()
class SSAT_DISP2020(klibs.Experiment): 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 block(self): self.search_type = SPACE if self.search_type == TIME else TIME if not P.practicing: if self.search_type == SPACE: self.condition = self.spatial_conditions.pop() else: self.condition = self.temporal_conditions.pop() else: self.condition = self.practice_conditions.pop() self.target_distractor, self.distractor_distractor = self.condition self.create_stimuli(self.target_distractor, self.distractor_distractor) self.present_instructions() def setup_response_collector(self): self.rc.uses(RC_KEYPRESS) self.rc.keypress_listener.interrupts = True self.rc.keypress_listener.key_map = self.keymap timeout = 10 if self.search_type == SPACE else 100 self.rc.terminate_after = [timeout, TK_S] def trial_prep(self): if self.search_type == SPACE: self.search_stimuli = self.prepare_spatial_array() self.rc.display_callback = self.present_spatial_array self.rc.display_kwargs = {'spatial_array': self.search_stimuli} else: self.search_stimuli = self.prepare_temporal_stream() self.rc.display_callback = self.present_temporal_stream self.rc.display_kwargs = {'temporal_stream': self.search_stimuli} events = [[1000, 'present_example_target']] events.append([events[-1][0] + 1000, 'present_fixation']) events.append([events[-1][0] + 1000, 'search_onset']) for e in events: self.evm.register_ticket(ET(e[1], e[0])) self.trial_sw = Stopwatch() self.present_target() hide_mouse_cursor() def trial(self): while self.evm.before("present_fixation", True): ui_request() self.blit_img(self.fixation, 5, P.screen_c) while self.evm.before("search_onset", True): ui_request() if self.search_type == SPACE: self.rc.collect() else: try: self.target_onset = 'NA' self.target_sw = Stopwatch() self.rc.collect() except IndexError: pass if len(self.rc.keypress_listener.responses): response, rt = self.rc.keypress_listener.response() if response != self.present_absent: self.present_feedback() else: response, rt = 'None', 'NA' clear() return { "practicing": str(P.practicing), "block_num": P.block_number, "trial_num": P.trial_number, "search_type": self.search_type, "stimulus_type": "LINE" if P.condition == 'line' else "COLOUR", "present_absent": self.present_absent, "set_size": self.set_size if self.search_type == SPACE else 'NA', "target_distractor": self.target_distractor, "distractor_distractor": self.distractor_distractor, "target_onset": self.target_onset if self.search_type == TIME else 'NA', "response": response, "rt": rt, "error": str(response != self.present_absent) } def trial_clean_up(self): self.rc.keypress_listener.reset() self.search_stimuli = [] def clean_up(self): pass def present_target(self): msg = "This is your target!" msg_loc = [P.screen_c[0], P.screen_c[1] - deg_to_px(2.0)] fill() message(msg, location=msg_loc, registration=5) blit(self.target_item, location=P.screen_c, registration=5) flip() def prepare_spatial_array(self): spatial_array_locs = list(self.spatial_array_locs) random.shuffle(spatial_array_locs) self.item_locs = [] jitter = [x * 0.1 for x in range(-3, 4)] for i in range(0, self.set_size): x_jitter = deg_to_px(random.choice(jitter)) y_jitter = deg_to_px(random.choice(jitter)) loc = spatial_array_locs.pop() loc[0], loc[1] = loc[0] + x_jitter, loc[1] + y_jitter self.item_locs.append(loc) spatial_array = [] if self.present_absent == PRESENT: self.target_loc = self.item_locs.pop() spatial_array.append([self.target_item, self.target_loc]) for loc in self.item_locs: distractor = random.choice(self.distractors) spatial_array.append([distractor, loc]) return spatial_array def present_spatial_array(self, spatial_array): fill() blit(self.fixation, registration=5, location=P.screen_c) if P.development_mode: c = kld.Annulus(deg_to_px(1.0), deg_to_px(0.1), fill=(255, 0, 0, 255)) blit(c, registration=5, location=self.target_loc) for item in spatial_array: blit(item[0], registration=5, location=item[1]) flip() def prepare_temporal_stream(self): stream_length = 16 temporal_stream = [] if self.present_absent == PRESENT: self.target_time = random.randint(5, 12) else: self.target_time = -1 for i in range(stream_length): if i == self.target_time: temporal_stream.append([self.target_item, True]) else: distractor = random.choice(self.distractors) temporal_stream.append([distractor, False]) return temporal_stream 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 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 present_feedback(self): msg = "Incorrect!" self.blit_msg(msg, 'left') feedback_cd = CountDown(0.5) while feedback_cd.counting(): ui_request() def blit_msg(self, msg, align): msg = message(msg, align=align, blit_txt=False) fill() blit(msg, registration=5, location=P.screen_c) flip() def blit_img(self, img, reg, loc): fill() blit(img, registration=reg, location=loc) flip() def create_stimuli(self, tdSimilarity, ddSimilarity): # Setup target properties if P.condition == 'line': self.target_angle = random.randint(0, 179) self.target_height = deg_to_px(0.1) self.target_fill = WHITE self.target_item = kld.Rectangle(width=self.item_size, height=self.target_height, fill=WHITE, rotation=self.target_angle) if tdSimilarity == HIGH: self.ref_angle = self.target_angle else: self.ref_angle = self.target_angle + 90 self.mask = kld.Asterisk(size=self.item_size, thickness=self.target_height, fill=WHITE, spokes=12) else: self.target_height = self.item_size self.palette = kld.ColorWheel(diameter=self.item_size) self.target_angle = random.randint(0, 359) self.target_colour = self.palette.color_from_angle( self.target_angle) self.target_item = kld.Rectangle(width=self.item_size, height=self.target_height, fill=self.target_colour) if tdSimilarity == HIGH: self.ref_angle = self.target_angle else: self.ref_angle = self.target_angle + 180 self.mask = kld.Rectangle(width=self.item_size, fill=WHITE) # Setup distractor(s) properties if ddSimilarity == HIGH: padding = [random.choice([-20, 20])] else: padding = [-40, -20, 20, 40] self.distractors = [] if P.condition == 'line': for p in padding: rotation = self.ref_angle + p self.distractors.append( kld.Rectangle(width=self.item_size, height=self.target_height, fill=WHITE, rotation=rotation)) else: for p in padding: colour = self.palette.color_from_angle(self.ref_angle + p) self.distractors.append( kld.Rectangle(width=self.item_size, height=self.target_height, fill=colour))
class SSAT_Color(klibs.Experiment): def setup(self): self.group = random.choice(['A', 'B']) # Stimulus sizes fix_size = deg_to_px(0.6) fix_thickness = deg_to_px(0.1) self.item_size = deg_to_px(0.8) self.item_thickness = deg_to_px(.1) # Initilize drawbjects self.fixation = FixationCross(fix_size, fix_thickness, fill=WHITE) # Initialize ResponseCollectors self.spatial_rc = ResponseCollector(uses=RC_KEYPRESS) self.temporal_pre_rc = ResponseCollector(uses=RC_KEYPRESS, flip_screen=True) self.temporal_post_rc = ResponseCollector(uses=RC_KEYPRESS, flip_screen=True) self.group_A_keymap = KeyMap("search_response", ['z', '/'], ['absent', 'present'], [sdl2.SDLK_z, sdl2.SDLK_SLASH]) self.group_B_keymap = KeyMap("search_response", ['z', '/'], ['present', 'absent'], [sdl2.SDLK_z, sdl2.SDLK_SLASH]) self.item_duration = .1 # seconds self.isi = .05 # seconds self.anykey_text = "{0}\nPress any key to continue." self.group_A_instuctions = "If you see the target item, please press the '/' key.\nIf you don't see the target item, please press the 'z' key." self.group_B_instuctions = "If you see the target item, please press the 'z' key.\nIf you don't see the target item, please press the '/' key." self.general_instructions_1 = "In this experiment, you will see a series of items; amongst these items a target item may, or may not, be presented.\n{0}" self.general_instructions_2 = self.general_instructions_1.format( self.group_A_instuctions if self.group == "A" else self.group_B_instuctions) self.general_instructions_3 = ( "{0}\nThe experiment will begin with a practice round to familiarlize yourself with the task." "\n\nBefore every trial, a preview of the target item will be presented." ) self.spatial_instructions_1 = "Searching in Space!\n\nFor these trials, you will see a collection of items arranged in a circle.\n{0}" self.temporal_instructions_1 = "Searching in Time!\n\nFor these trials, you will see a series of items presented one at a time, center screen.\n{0}" self.group_A_spatial = ( "If one of the items is the target, press the '/' key as fast as possible.\n" "If none of the items are the target, press the 'z' key as fast as possible." ) self.group_B_spatial = ( "If one of the items is the target, press the 'z' key as fast as possible.\n" "If none of the items are the target, press the '/' key as fast as possible." ) self.group_A_temporal = ( "At any time, if you see the target item, press the '/' key as fast as possible.\n" "Once the images stop appearing, if you haven't seen the target item, press the 'z' key as fast as possible." ) self.group_B_temporal = ( "At any time, if you see the target item, press the 'z' key as fast as possible.\n" "Once the images stop appearing, if you haven't seen the target item, press the '/' key as fast as possible." ) self.general_instructions = self.general_instructions_3.format( self.general_instructions_2) self.spatial_instructions = self.spatial_instructions_1.format( self.group_A_spatial if self.group == 'A' else self.group_B_spatial) self.temporal_instructions = self.temporal_instructions_1.format( self.group_A_temporal if self.group == 'A' else self.group_B_temporal) self.general_instruct_shown = False self.spatial_conditions_exp = [[H**O, HETERO], [H**O, H**O], [HETERO, H**O], [HETERO, HETERO]] self.temporal_conditions_exp = [[H**O, HETERO], [H**O, H**O], [HETERO, H**O], [HETERO, HETERO]] self.practice_conditions = [[H**O, HETERO], [H**O, H**O], [HETERO, H**O], [HETERO, HETERO]] random.shuffle(self.spatial_conditions_exp) random.shuffle(self.temporal_conditions_exp) random.shuffle(self.practice_conditions) self.search_type = random.choice([SPACE, TIME]) if P.run_practice_blocks: self.insert_practice_block( block_nums=range(1, 5), trial_counts=P.trials_per_practice_block) 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 setup_response_collector(self): self.spatial_rc.terminate_after = [10, TK_S] self.spatial_rc.keypress_listener.interrupts = True self.spatial_rc.keypress_listener.key_map = self.group_A_keymap if self.group == 'A' else self.group_B_keymap self.temporal_pre_rc.terminate_after = [10, TK_S] self.temporal_pre_rc.keypress_listener.key_map = self.group_A_keymap if self.group == 'A' else self.group_B_keymap self.temporal_pre_rc.keypress_listener.interrupts = True self.temporal_pre_rc.display_callback = self.present_stream self.temporal_post_rc.terminate_after = [5, TK_S] self.temporal_post_rc.keypress_listener.key_map = self.group_A_keymap if self.group == 'A' else self.group_B_keymap self.temporal_post_rc.keypress_listener.interrupts = True self.temporal_post_rc.display_callback = self.post_stream def trial_prep(self): self.target_onset = NA if self.search_type == SPACE: array_radius = deg_to_px(8) theta = 360.0 / self.set_size self.item_locs = [] for i in range(0, self.set_size): self.item_locs.append( point_pos(origin=P.screen_c, amplitude=array_radius, angle=0, rotation=theta * (i + 1))) random.shuffle(self.item_locs) if self.present_absent == PRESENT: self.target_loc = self.item_locs.pop() else: self.rsvp_stream = self.prepare_stream() self.rsvp_stream.reverse( ) # items are extracted via pop() in present_stream() events = [[1000, 'present_target']] events.append([events[-1][0] + 1000, 'present_fixation']) events.append([events[-1][0] + 1000, 'search_onset']) for e in events: self.evm.register_ticket(ET(e[1], e[0])) hide_mouse_cursor() self.present_target() def trial(self): # Wait 1s before presenting array while self.evm.before("present_fixation", True): ui_request() self.present_fixation() while self.evm.before("search_onset", True): ui_request() if self.search_type == SPACE: self.present_array() self.spatial_rc.collect() if len(self.spatial_rc.keypress_listener.responses): spatial_response, spatial_rt = self.spatial_rc.keypress_listener.response( ) if spatial_response != self.present_absent: self.present_feedback() else: spatial_response = "None" spatial_rt = 'NA' else: try: self.response_sw = Stopwatch() self.stream_sw = Stopwatch() self.target_sw = Stopwatch() # the display callback "present_stream()" pops an element each pass; when all targets have been shown this bad boy throws an error self.temporal_pre_rc.collect() except IndexError: pass self.stream_sw.pause() if len(self.temporal_pre_rc.keypress_listener.responses): temporal_response, temporal_rt = self.temporal_pre_rc.keypress_listener.response( ) else: self.temporal_post_rc.collect() if len(self.temporal_post_rc.keypress_listener.responses): temporal_response = self.temporal_post_rc.keypress_listener.response( rt=False) temporal_rt = self.response_sw.elapsed() * 1000 else: temporal_response = "None" temporal_rt = "NA" if temporal_response != self.present_absent: self.present_feedback() clear() return { "practicing": str(P.practicing), "block_num": P.block_number, "trial_num": P.trial_number, "search_type": self.search_type, "stimulus_type": 'COLOR', "present_absent": self.present_absent, "set_size": self.set_size if self.search_type == SPACE else "NA", "target_distractor": self.target_distractor, "distractor_distractor": self.distractor_distractor, "target_time": self.target_time if self.search_type == TIME else "NA", "stream_duration": self.stream_sw.elapsed() if self.search_type == TIME else "NA", "target_onset": self.target_onset if self.search_type == TIME else "NA", "spatial_response": spatial_response if self.search_type == SPACE else "NA", "spatial_rt": spatial_rt if self.search_type == SPACE else "NA", "temporal_response": temporal_response if self.search_type == TIME else "NA", "temporal_rt": temporal_rt if self.search_type == TIME else "NA" } def trial_clean_up(self): self.spatial_rc.keypress_listener.reset() self.temporal_pre_rc.keypress_listener.reset() self.temporal_post_rc.keypress_listener.reset() if not P.practicing: if P.trial_number == P.trials_per_block: if self.search_type == SPACE: self.search_type = TIME else: self.search_type = SPACE else: if P.trial_number == P.trials_per_practice_block: if self.search_type == SPACE: self.search_type = TIME else: self.search_type = SPACE def clean_up(self): pass 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 present_fixation(self): fill() blit(self.fixation, location=P.screen_c, registration=5) flip() def present_feedback(self): fill() message("Incorrect!", location=P.screen_c, registration=5, blit_txt=True) flip() feedback_period_cd = CountDown(0.5) # seconds while feedback_period_cd.counting(): ui_request() def create_stimuli(self): # Select distractor colourings ref_angle = 0 if self.target_distractor == H**O else 180 self.distractor_fills = [] if self.distractor_distractor == H**O: pad = random.choice([-20, 20]) self.distractor_fills.append( self.color_selector.color_from_angle(ref_angle + pad)) else: for i in range(1, 3): self.distractor_fills.append( self.color_selector.color_from_angle(ref_angle + (20 * i))) self.distractor_fills.append( self.color_selector.color_from_angle(ref_angle - (20 * i))) # Now that we have our colouring, create stimuli self.distractors = [] for f in self.distractor_fills: self.distractors.append(Rectangle(width=self.item_size, fill=f)) def present_array(self): fill() blit(self.fixation, registration=5, location=P.screen_c) if self.present_absent == PRESENT: blit(self.target_item, registration=5, location=self.target_loc) for loc in self.item_locs: blit(random.choice(self.distractors), registration=5, location=loc) flip() def prepare_stream(self): self.stream_length = 16 stream_items = [] if self.present_absent == PRESENT: self.target_time = random.randint(5, 12) else: self.target_time = -1 for i in range(self.stream_length): if i == self.target_time: stream_items.append([self.target_item, True]) else: stream_items.append([random.choice(self.distractors), False]) return stream_items def present_stream(self): duration_cd = CountDown(self.item_duration, start=False) isi_cd = CountDown(self.isi, start=False) item = self.rsvp_stream.pop() fill() blit(item[0], registration=5, location=P.screen_c) flip() duration_cd.start() while duration_cd.counting(): pass fill() if item[1]: self.target_onset = self.target_sw.elapsed() isi_cd.start() while isi_cd.counting(): pass def post_stream(self): fill() flip()
def trial(self): # Wait 1s before presenting array while self.evm.before("present_fixation", True): ui_request() self.present_fixation() while self.evm.before("search_onset", True): ui_request() if self.search_type == SPACE: self.present_array() self.spatial_rc.collect() if len(self.spatial_rc.keypress_listener.responses): spatial_response, spatial_rt = self.spatial_rc.keypress_listener.response( ) if spatial_response != self.present_absent: self.present_feedback() else: spatial_response = "None" spatial_rt = 'NA' else: try: self.response_sw = Stopwatch() self.stream_sw = Stopwatch() self.target_sw = Stopwatch() # the display callback "present_stream()" pops an element each pass; when all targets have been shown this bad boy throws an error self.temporal_pre_rc.collect() except IndexError: pass self.stream_sw.pause() if len(self.temporal_pre_rc.keypress_listener.responses): temporal_response, temporal_rt = self.temporal_pre_rc.keypress_listener.response( ) else: self.temporal_post_rc.collect() if len(self.temporal_post_rc.keypress_listener.responses): temporal_response = self.temporal_post_rc.keypress_listener.response( rt=False) temporal_rt = self.response_sw.elapsed() * 1000 else: temporal_response = "None" temporal_rt = "NA" if temporal_response != self.present_absent: self.present_feedback() clear() return { "practicing": str(P.practicing), "block_num": P.block_number, "trial_num": P.trial_number, "search_type": self.search_type, "stimulus_type": 'COLOR', "present_absent": self.present_absent, "set_size": self.set_size if self.search_type == SPACE else "NA", "target_distractor": self.target_distractor, "distractor_distractor": self.distractor_distractor, "target_time": self.target_time if self.search_type == TIME else "NA", "stream_duration": self.stream_sw.elapsed() if self.search_type == TIME else "NA", "target_onset": self.target_onset if self.search_type == TIME else "NA", "spatial_response": spatial_response if self.search_type == SPACE else "NA", "spatial_rt": spatial_rt if self.search_type == SPACE else "NA", "temporal_response": temporal_response if self.search_type == TIME else "NA", "temporal_rt": temporal_rt if self.search_type == TIME else "NA" }
def trial(self): # Wait 1s before presenting array while self.evm.before("present_fixation", True): ui_request() self.present_fixation() while self.evm.before("search_onset", True): ui_request() if self.search_type == SPACE: self.present_array() self.spatial_rc.collect() if len(self.spatial_rc.keypress_listener.responses): spatial_response, spatial_rt = self.spatial_rc.keypress_listener.response( ) if spatial_response != self.present_absent: self.present_feedback() else: spatial_response = "None" spatial_rt = 'NA' else: try: self.response_sw = Stopwatch( ) # Used as RT stopwatch in cases where responses are made after stream has ended self.stream_sw = Stopwatch( ) # Records duration of stream, just in case it becomes handy to have self.target_sw = Stopwatch( ) # Records time that target is presented, again just in case. # the display callback "present_stream()" pops an element each pass; when all targets have been shown this bad boy throws an error self.temporal_pre_rc.collect() except IndexError: pass # Pause once stream has ended self.stream_sw.pause() if len(self.temporal_pre_rc.keypress_listener.responses): temporal_response, temporal_rt = self.temporal_pre_rc.keypress_listener.response( ) else: self.temporal_post_rc.collect() if len(self.temporal_post_rc.keypress_listener.responses): temporal_rt = self.response_sw.elapsed( ) * 1000 # Rescale RT to ms and log temporal_response = self.temporal_post_rc.keypress_listener.response( rt=False) else: temporal_response = "None" temporal_rt = "NA" if temporal_response != self.present_absent: self.present_feedback() clear() return { "practicing": str(P.practicing), "block_num": P.block_number, "trial_num": P.trial_number, "search_type": self.search_type, "stimulus_type": 'LINE', "present_absent": self.present_absent, "set_size": self.set_size if self.search_type == SPACE else 'NA', "target_distractor": self.target_distractor, "distractor_distractor": self.distractor_distractor, "target_time": self.target_time if self.search_type == TIME else "NA", "stream_duration": self.stream_sw.elapsed() if self.search_type == TIME else "NA", "target_onset": self.target_onset if self.search_type == TIME else "NA", "spatial_response": spatial_response if self.search_type == SPACE else "NA", "spatial_rt": spatial_rt if self.search_type == SPACE else "NA", "temporal_response": temporal_response if self.search_type == TIME else "NA", "temporal_rt": temporal_rt if self.search_type == TIME else "NA" }
class ABColour_TMTM(klibs.Experiment): def setup(self): # Stimulus sizes fix_thickness = deg_to_px(0.1) fix_size = deg_to_px(0.6) wheel_size = int(P.screen_y * 0.75) cursor_size = deg_to_px(1) cursor_thickness = deg_to_px(0.3) target_size = deg_to_px(0.8) # Initilize drawbjects self.fixation = FixationCross(size=fix_size, thickness=fix_thickness, fill=WHITE) self.t1_wheel = ColorWheel(diameter=wheel_size) self.t2_wheel = ColorWheel(diameter=wheel_size) self.cursor = Annulus(diameter=cursor_size, thickness=cursor_thickness, fill=BLACK) # Create text styles to store target colouring self.txtm.add_style(label="T1", font_size=target_size) self.txtm.add_style(label="T2", font_size=target_size) # Stimulus presentation durations (intervals of 16.7ms refresh rate) self.id_target_duration = P.refresh_time * 5 # 83.3ms self.id_mask_duration = P.refresh_time self.col_target_duration = P.refresh_time * 5 # 167ms self.col_mask_duration = P.refresh_time self.isi = P.refresh_time # ISI = inter-stimulus interval (target offset -> mask onset) # Colour ResponseCollector needs to be passed an object whose fill (colour) # is that of the target colour. W/n trial_prep(), these dummies will be filled # w/ the target colour and then passed to their ResponseCollectors, respectively. self.t1_dummy = Ellipse(width=1) self.t2_dummy = Ellipse(width=1) # Experiment messages self.anykey_txt = "{0}\nPress any key to continue." self.t1_id_request = "What was the first number?" self.t2_id_request = "What was the second number?" self.t1_col_request = "What was the first colour?" self.t2_col_request = "What was the second colour?" self.prac_identity_instruct = "\nIn this block, you will be asked to report what number was presented.\nIf you're unsure, make your best guess." self.prac_colour_instruct = "\nIn this block, you will be asked to report what colour was presented.\nIf you're unsure, make your best guess." self.test_identity_instruct = "\nIn this block, you will be asked to report which two numbers were presented.\nIf you're unsure, make your best guess." self.test_colour_instruct = "\nIn this block, you will be asked to report which two colours were presented.\nIf you're unsure, make your best guess." # Initialize ResponseCollectors self.t1_identity_rc = ResponseCollector(uses=RC_KEYPRESS) self.t2_identity_rc = ResponseCollector(uses=RC_KEYPRESS) self.t1_colouring_rc = ResponseCollector(uses=RC_COLORSELECT) self.t2_colouring_rc = ResponseCollector(uses=RC_COLORSELECT) # Initialize ResponseCollector Keymaps self.keymap = KeyMap( 'identity_response', ['1', '2', '3', '4', '5', '6', '7', '8', '9'], ['1', '2', '3', '4', '5', '6', '7', '8', '9'], [ sdl2.SDLK_1, sdl2.SDLK_2, sdl2.SDLK_3, sdl2.SDLK_4, sdl2.SDLK_5, sdl2.SDLK_6, sdl2.SDLK_7, sdl2.SDLK_8, sdl2.SDLK_9 ]) # Inserting practice blocks requires a pre-defined trial count; but in our case they are of an undefined length, # lasting for as long as it takes participants to reach a performance threshold. So, initially they are of length 1 # but trials are inserted later on depending on participant performance. if P.run_practice_blocks: self.insert_practice_block([1, 3], trial_counts=1) # Randomly select starting condition self.block_type = random.choice([IDENTITY, COLOUR]) 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 setup_response_collector(self): # Configure identity collector self.t1_identity_rc.terminate_after = [10, TK_S] # Waits 10s for response self.t1_identity_rc.display_callback = self.identity_callback # Continuously draw images to screen self.t1_identity_rc.display_kwargs = { 'target': "T1" } # Passed as arg when identity_callback() is called self.t1_identity_rc.keypress_listener.key_map = self.keymap # Assign key mappings self.t1_identity_rc.keypress_listener.interrupts = True # Terminates listener after valid response self.t2_identity_rc.terminate_after = [10, TK_S] self.t2_identity_rc.display_callback = self.identity_callback self.t2_identity_rc.display_kwargs = {'target': "T2"} self.t2_identity_rc.keypress_listener.key_map = self.keymap self.t2_identity_rc.keypress_listener.interrupts = True # Configure colour collector # Because colours are randomly selected on a trial by trial basis # most properties of colouring_rc need to be assigned within trial_prep() self.t1_colouring_rc.terminate_after = [10, TK_S] self.t2_colouring_rc.terminate_after = [10, TK_S] def trial_prep(self): # Prepare colour wheels self.t1_wheel.rotation = random.randrange( 0, 360) # Randomly rotate wheel to prevent location biases self.t2_wheel.rotation = random.randrange(0, 360) while self.t1_wheel.rotation == self.t2_wheel.rotation: # Ensure unique rotation values self.t2_wheel.rotation = random.randrange(0, 360) self.t1_wheel.render() self.t2_wheel.render() # Select target identities self.t1_identity = random.sample(numbers, 1)[0] # Select & assign identity self.t2_identity = random.sample(numbers, 1)[0] while self.t1_identity == self.t2_identity: # Ensure that T1 & T2 identities are unique self.t2_identity = random.sample(numbers, 1)[0] # Select target angles (for selecting colour from wheel) self.t1_angle = random.randrange(0, 360) self.t2_angle = random.randrange(0, 360) while self.t1_angle == self.t2_angle: self.t2_angle = random.randrange(0, 360) self.t1_colour = self.t1_wheel.color_from_angle( self.t1_angle) # Assign colouring self.t2_colour = self.t2_wheel.color_from_angle(self.t2_angle) # Dummy objects to serve as reference point when calculating response error self.t1_dummy.fill = self.t1_colour self.t2_dummy.fill = self.t2_colour self.t1_colouring_rc.display_callback = self.wheel_callback self.t1_colouring_rc.display_kwargs = {'wheel': self.t1_wheel} self.t1_colouring_rc.color_listener.set_wheel( self.t1_wheel) # Set generated wheel as wheel to use self.t1_colouring_rc.color_listener.set_target( self.t1_dummy) # Set dummy as target reference point self.t2_colouring_rc.display_callback = self.wheel_callback self.t2_colouring_rc.display_kwargs = { 'wheel': self.t2_wheel } # Passed as arg w/ calling wheel_callback() self.t2_colouring_rc.color_listener.set_wheel(self.t2_wheel) self.t2_colouring_rc.color_listener.set_target(self.t2_dummy) if self.block_type == IDENTITY: self.target_duration = self.id_target_duration self.mask_duration = self.id_mask_duration else: self.target_duration = self.col_target_duration self.mask_duration = self.col_mask_duration # Initialize EventManager if P.practicing: # T2 not present during practice blocks events = [[self.itoa, "T1_on"]] events.append([events[-1][0] + self.target_duration, 'T1_off']) events.append([events[-1][0] + self.isi, 'T1_mask_on']) events.append([events[-1][0] + self.mask_duration, 'T1_mask_off']) events.append([events[-1][0] + 300, 'response_foreperiod']) else: events = [[self.itoa, 'T1_on']] events.append([events[-1][0] + self.target_duration, 'T1_off']) events.append([events[-1][0] + self.isi, 'T1_mask_on']) events.append([events[-1][0] + self.mask_duration, 'T1_mask_off']) events.append([events[-4][0] + self.ttoa, 'T2_on']) # SOA = Time between onset of T1 & T2 events.append([events[-1][0] + self.target_duration, 'T2_off']) events.append([events[-1][0] + self.isi, 'T2_mask_on']) events.append([events[-1][0] + self.mask_duration, 'T2_mask_off']) events.append([events[-1][0] + 300, 'response_foreperiod']) # Stream begins 1000ms after fixation for e in events: self.evm.register_ticket(ET(e[1], e[0])) # Prepare stream self.tmtm_stream = self.prep_stream() # Present fixation & wait for initiation self.present_fixation() def trial(self): # Hide cursor during trial hide_mouse_cursor() # Wait some foreperiod before presenting T1 while self.evm.before('T1_on', True): ui_request() # Present T1 fill() blit(self.tmtm_stream['t1_target'], registration=5, location=P.screen_c) flip() self.t1_sw = Stopwatch() # Don't do anything during T1 presentation while self.evm.before('T1_off', True): ui_request() self.t1_isi_sw = Stopwatch() # Remove T1 fill() flip() self.t1_sw.pause() print("T1 duration actual: " + str(self.t1_sw.elapsed())) fill() flip() while self.evm.before('T1_mask_on', True): ui_request() self.t1_isi_sw.pause() print("ISI actual: " + str(self.t1_isi_sw.elapsed())) # After one refresh rate (how long it takes to remove T1) present mask fill() blit(self.tmtm_stream['t1_mask'], registration=5, location=P.screen_c) flip() self.t1_mask_sw = Stopwatch() # Don't do anything during presentation while self.evm.before('T1_mask_off', True): ui_request() # Remove mask fill() flip() self.t1_mask_sw.pause() print("T1 mask duration actual: " + str(self.t1_mask_sw.elapsed())) # If not practicing, present T2 if not P.practicing: # After TTOA is up, present T2 while self.evm.before('T2_on', True): ui_request() fill() blit(self.tmtm_stream['t2_target'], registration=5, location=P.screen_c) flip() self.t2_sw = Stopwatch() # Don't do anything during presentation while self.evm.before('T2_off', True): ui_request() self.t2_isi_sw = Stopwatch() # Remove T2 fill() flip() self.t2_sw.pause() fill() flip() while self.evm.before('T2_mask_on', True): ui_request() self.t2_isi_sw.pause() # After one refresh rate, present mask fill() blit(self.tmtm_stream['t2_mask'], registration=5, location=P.screen_c) flip() self.t2_mask_sw = Stopwatch() # Don't do anything during presentation while self.evm.before('T2_mask_off', True): ui_request() # Remove mask fill() flip() self.t2_mask_sw.pause() # Wait 1/3 second before asking for responses while self.evm.before('response_foreperiod', True): ui_request() # Request & record responses if self.block_type == IDENTITY: # Not relevant to identity trials t1_response_err, t1_response_err_rt, t2_response_err, t2_response_err_rt = [ 'NA', 'NA', 'NA', 'NA' ] # Collect identity responses self.t1_identity_rc.collect() if not P.practicing: self.t2_identity_rc.collect() # Assign to variables returned t1_id_response, t1_id_rt = self.t1_identity_rc.keypress_listener.response( ) # No T2 present during practice if not P.practicing: t2_id_response, t2_id_rt = self.t2_identity_rc.keypress_listener.response( ) else: t2_id_response, t2_id_rt = ['NA', 'NA'] # During practice, keep a tally of T1 performance if P.practicing: if t1_id_response == self.t1_identity: self.t1_performance += 1 else: # Colour block # Not relevant to colour trials t1_id_response, t1_id_rt, t2_id_response, t2_id_rt = [ 'NA', 'NA', 'NA', 'NA' ] # Collect colour responses self.t1_colouring_rc.collect() if not P.practicing: self.t2_colouring_rc.collect() # Assign to variables returned t1_response_err, t1_response_err_rt = self.t1_colouring_rc.color_listener.response( ) # T2 only presented during test blocks if not P.practicing: t2_response_err, t2_response_err_rt = self.t2_colouring_rc.color_listener.response( ) else: t2_response_err, t2_response_err_rt = ['NA', 'NA'] if P.practicing: # As numeric identities have 9 possible values, similarly the colour wheel can # be thought of as having 9 'bins' (each 40º wide). Colour responses are labelled # as 'correct' if their angular error does not exceed 20º in either direction. if (abs(t1_response_err) <= 20): self.t1_performance += 1 clear() print(self.target_duration) print(self.mask_duration) return { "practicing": str(P.practicing), "block_num": P.block_number, "trial_num": P.trial_number, "block_type": self.block_type, "itoa": self.itoa, "ttoa": self.ttoa, "target_duration": self.target_duration, "mask_duration": self.mask_duration, "t1_identity": self.t1_identity, "t2_identity": self.t2_identity if not P.practicing else 'NA', "t1_identity_response": t1_id_response, "t1_identity_rt": t1_id_rt, "t2_identity_response": t2_id_response, "t2_identity_rt": t2_id_rt, "t1_colour": self.t1_colour, "t1_angle": self.t1_angle, "t1_wheel_rotation": self.t1_wheel.rotation, "t2_colour": self.t2_colour if not P.practicing else 'NA', "t2_angle": self.t2_angle if not P.practicing else 'NA', "t2_wheel_rotation": self.t2_wheel.rotation if not P.practicing else 'NA', "t1_ang_err": t1_response_err, "t1_colour_rt": t1_response_err_rt, "t2_ang_err": t2_response_err, "t2_colour_rt": t2_response_err_rt, "t1_performance_practice": self.t1_performance if P.practicing else 'NA', "t1_duration_actual": self.t1_sw.elapsed(), "t1_isi_actual": self.t1_isi_sw.elapsed(), "t1_mask_duration_actual": self.t1_mask_sw.elapsed(), "t2_duration_actual": self.t2_sw.elapsed() if not P.practicing else 'NA', "t2_isi_actual": self.t2_isi_sw.elapsed() if not P.practicing else 'NA', "t2_mask_duration_actual": self.t2_mask_sw.elapsed() if not P.practicing else 'NA' } def trial_clean_up(self): # Reset response listeners self.t1_identity_rc.keypress_listener.reset() self.t2_identity_rc.keypress_listener.reset() self.t1_colouring_rc.color_listener.reset() self.t2_colouring_rc.color_listener.reset() # Performance checks during practice if P.practicing: if not self.practice_complete: # First 10 trials considered an 'introductory' run, where no performance check is performed if not self.pre_run_complete: if self.practice_trial_num == 10: self.t1_performance = 0 self.pre_run_complete = True else: #else: # Every 10 trials, check performance & adjust difficulty as necessary if self.practice_trial_num % 10 == 0: # If subj accuracy above 80%, if self.t1_performance > 8: # Make task harder by adjusting target & mask durations if self.block_type == IDENTITY: if self.id_target_duration > P.refresh_time: self.id_target_duration -= P.refresh_time self.id_mask_duration += P.refresh_time else: if self.col_target_duration > P.refresh_time: self.col_target_duration -= P.refresh_time self.col_mask_duration += P.refresh_time self.t1_performance = 0 # Conversely, if performance is below/at chance, make easier elif self.t1_performance <= 2: if self.block_type == IDENTITY: self.id_target_duration += P.refresh_time self.id_mask_duration -= P.refresh_time else: self.col_target_duration += P.refresh_time self.col_mask_duration -= P.refresh_time self.t1_performance = 0 else: self.t1_identity_performance = self.t1_performance self.practice_complete = True self.practice_trial_num += 1 else: if P.trial_number == P.trials_per_block and P.block_number == 2: self.block_type = COLOUR if self.block_type == IDENTITY else IDENTITY def clean_up(self): pass # --------------------------------- # # Project specific helper functions # --------------------------------- # def present_fixation(self): fill() blit(self.fixation, location=P.screen_c, registration=5) flip() any_key() def prep_stream(self): # Dynamically assign target colouring self.txtm.styles['T1'].color = self.t1_colour self.txtm.styles['T2'].color = self.t2_colour # Generate unique masks for each target self.t1_mask = self.generate_mask() self.t2_mask = self.generate_mask() stream_items = { 't1_target': message(self.t1_identity, align='center', style='T1', blit_txt=False), 't1_mask': self.t1_mask, 't2_target': message(self.t2_identity, align='center', style='T2', blit_txt=False), 't2_mask': self.t2_mask } return stream_items 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 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 generate_mask(self): # Set mask size canvas_size = deg_to_px(1) # Set cell size cell_size = canvas_size / 5 # Mask comprised of 16 smaller cells arranged 4x4 # Each cell has a black outline cell_outline_width = deg_to_px(.05) # Initialize canvas to be painted w/ mask cells canvas = Image.new('RGBA', [canvas_size, canvas_size], (0, 0, 0, 0)) surface = aggdraw.Draw(canvas) # Initialize pen to draw cell outlines transparent_pen = aggdraw.Pen((0, 0, 0), cell_outline_width) # Generate cells, arranged in 4x4 array for row in [0, 1, 2, 3, 4]: for col in [0, 1, 2, 3, 4]: # Randomly select colour for each cell cell_colour = const_lum[random.randrange(0, 360)] # Brush to apply colour colour_brush = aggdraw.Brush(tuple(cell_colour[:3])) # Determine cell boundary coords top_left = (row * cell_size, col * cell_size) bottom_right = ((row + 1) * cell_size, (col + 1) * cell_size) # Create cell surface.rectangle((top_left[0], top_left[1], bottom_right[0], bottom_right[1]), transparent_pen, colour_brush) # Apply cells to mask surface.flush() return np.asarray(canvas)
def trial(self): # Hide cursor during trial hide_mouse_cursor() # Wait some foreperiod before presenting T1 while self.evm.before('T1_on', True): ui_request() # Present T1 fill() blit(self.tmtm_stream['t1_target'], registration=5, location=P.screen_c) flip() self.t1_sw = Stopwatch() # Don't do anything during T1 presentation while self.evm.before('T1_off', True): ui_request() self.t1_isi_sw = Stopwatch() # Remove T1 fill() flip() self.t1_sw.pause() print("T1 duration actual: " + str(self.t1_sw.elapsed())) fill() flip() while self.evm.before('T1_mask_on', True): ui_request() self.t1_isi_sw.pause() print("ISI actual: " + str(self.t1_isi_sw.elapsed())) # After one refresh rate (how long it takes to remove T1) present mask fill() blit(self.tmtm_stream['t1_mask'], registration=5, location=P.screen_c) flip() self.t1_mask_sw = Stopwatch() # Don't do anything during presentation while self.evm.before('T1_mask_off', True): ui_request() # Remove mask fill() flip() self.t1_mask_sw.pause() print("T1 mask duration actual: " + str(self.t1_mask_sw.elapsed())) # If not practicing, present T2 if not P.practicing: # After TTOA is up, present T2 while self.evm.before('T2_on', True): ui_request() fill() blit(self.tmtm_stream['t2_target'], registration=5, location=P.screen_c) flip() self.t2_sw = Stopwatch() # Don't do anything during presentation while self.evm.before('T2_off', True): ui_request() self.t2_isi_sw = Stopwatch() # Remove T2 fill() flip() self.t2_sw.pause() fill() flip() while self.evm.before('T2_mask_on', True): ui_request() self.t2_isi_sw.pause() # After one refresh rate, present mask fill() blit(self.tmtm_stream['t2_mask'], registration=5, location=P.screen_c) flip() self.t2_mask_sw = Stopwatch() # Don't do anything during presentation while self.evm.before('T2_mask_off', True): ui_request() # Remove mask fill() flip() self.t2_mask_sw.pause() # Wait 1/3 second before asking for responses while self.evm.before('response_foreperiod', True): ui_request() # Request & record responses if self.block_type == IDENTITY: # Not relevant to identity trials t1_response_err, t1_response_err_rt, t2_response_err, t2_response_err_rt = [ 'NA', 'NA', 'NA', 'NA' ] # Collect identity responses self.t1_identity_rc.collect() if not P.practicing: self.t2_identity_rc.collect() # Assign to variables returned t1_id_response, t1_id_rt = self.t1_identity_rc.keypress_listener.response( ) # No T2 present during practice if not P.practicing: t2_id_response, t2_id_rt = self.t2_identity_rc.keypress_listener.response( ) else: t2_id_response, t2_id_rt = ['NA', 'NA'] # During practice, keep a tally of T1 performance if P.practicing: if t1_id_response == self.t1_identity: self.t1_performance += 1 else: # Colour block # Not relevant to colour trials t1_id_response, t1_id_rt, t2_id_response, t2_id_rt = [ 'NA', 'NA', 'NA', 'NA' ] # Collect colour responses self.t1_colouring_rc.collect() if not P.practicing: self.t2_colouring_rc.collect() # Assign to variables returned t1_response_err, t1_response_err_rt = self.t1_colouring_rc.color_listener.response( ) # T2 only presented during test blocks if not P.practicing: t2_response_err, t2_response_err_rt = self.t2_colouring_rc.color_listener.response( ) else: t2_response_err, t2_response_err_rt = ['NA', 'NA'] if P.practicing: # As numeric identities have 9 possible values, similarly the colour wheel can # be thought of as having 9 'bins' (each 40º wide). Colour responses are labelled # as 'correct' if their angular error does not exceed 20º in either direction. if (abs(t1_response_err) <= 20): self.t1_performance += 1 clear() print(self.target_duration) print(self.mask_duration) return { "practicing": str(P.practicing), "block_num": P.block_number, "trial_num": P.trial_number, "block_type": self.block_type, "itoa": self.itoa, "ttoa": self.ttoa, "target_duration": self.target_duration, "mask_duration": self.mask_duration, "t1_identity": self.t1_identity, "t2_identity": self.t2_identity if not P.practicing else 'NA', "t1_identity_response": t1_id_response, "t1_identity_rt": t1_id_rt, "t2_identity_response": t2_id_response, "t2_identity_rt": t2_id_rt, "t1_colour": self.t1_colour, "t1_angle": self.t1_angle, "t1_wheel_rotation": self.t1_wheel.rotation, "t2_colour": self.t2_colour if not P.practicing else 'NA', "t2_angle": self.t2_angle if not P.practicing else 'NA', "t2_wheel_rotation": self.t2_wheel.rotation if not P.practicing else 'NA', "t1_ang_err": t1_response_err, "t1_colour_rt": t1_response_err_rt, "t2_ang_err": t2_response_err, "t2_colour_rt": t2_response_err_rt, "t1_performance_practice": self.t1_performance if P.practicing else 'NA', "t1_duration_actual": self.t1_sw.elapsed(), "t1_isi_actual": self.t1_isi_sw.elapsed(), "t1_mask_duration_actual": self.t1_mask_sw.elapsed(), "t2_duration_actual": self.t2_sw.elapsed() if not P.practicing else 'NA', "t2_isi_actual": self.t2_isi_sw.elapsed() if not P.practicing else 'NA', "t2_mask_duration_actual": self.t2_mask_sw.elapsed() if not P.practicing else 'NA' }