Exemplo n.º 1
0
 def __init__(self, bar, button_text, button_size, location, callback=None):
     super(Button, self).__init__()
     super(EnvAgent, self).__init__()
     self.bar = bar
     self.size = button_size
     self.button_text = button_text
     self.button_rtext_a = message(button_text,
                                   "button_active",
                                   blit_txt=False)
     self.button_rtext_i = message(button_text,
                                   "button_inactive",
                                   blit_txt=False)
     self.frame_i = Rectangle(button_size[0],
                              button_size[1],
                              fill=None,
                              stroke=(5, (255, 255, 255)))
     self.frame_a = Rectangle(button_size[0],
                              button_size[1],
                              fill=None,
                              stroke=(5, (150, 255, 150)))
     self.active = False
     self.location = location
     self.text_location = (self.location[0] + self.size[0] // 2,
                           self.location[1] + self.size[1] // 2)
     self.create_boundary()
     self.callback = callback
Exemplo n.º 2
0
    def __init__(self, y_pos, bar_length, bar_height, handle_radius, bar_fill,
                 handle_fill):
        BoundaryInspector.__init__(self)
        EnvAgent.__init__(self)
        self.boundaries = {}
        self.pos = (P.screen_c[0] - bar_length // 2, y_pos)  # upper-left
        self.message_pos = (P.screen_c[0], y_pos - 50)
        self.__handle_pos = (self.pos[0], self.pos[1] + bar_height // 2)
        self.handle_color = handle_fill
        self.handle_radius = handle_radius
        self.handle_stroke = None
        self.handle_boundary = None
        self.bar = None
        self.bar_size = (bar_length, bar_height)
        self.bar_color = bar_fill
        self.bar_stroke = None
        self.show_increment_ticks = True
        self.show_increment_text = False
        self.increment_count = None
        self.increments = []
        self.increment_by = 1
        self.increment_surfs = {}
        self.lower_bound = None
        self.upper_bound = None
        self.handle = Ellipse(self.handle_radius * 2,
                              fill=self.handle_color,
                              stroke=self.handle_stroke)
        self.bar = Rectangle(self.bar_size[0],
                             self.bar_size[1],
                             fill=self.bar_color,
                             stroke=self.bar_stroke)
        self.add_boundary("handle", [
            (self.pos[0] + self.handle_radius, self.pos[1]), self.handle_radius
        ], CIRCLE_BOUNDARY)
        self.msg = message("How many corners did the dot traverse?",
                           "default",
                           blit_txt=False)
        self.lb_msg = None
        self.ub_msg = None
        self.ok_text = message("OK", blit_txt=False)
        self.ok_inactive_button = Rectangle(100,
                                            50,
                                            stroke=(1, (255, 255, 255)),
                                            fill=(125, 125, 125)).render()
        self.ok_active_button = Rectangle(100,
                                          50,
                                          stroke=(1, (255, 255, 255)),
                                          fill=(5, 175, 45)).render()
        self.button_active = False
        self.button_pos = (P.screen_c[0], y_pos + bar_height + 50)
        button_upper_left = (self.button_pos[0] - 50, self.button_pos[1] - 25)
        button_botton_right = (self.button_pos[0] + 50,
                               self.button_pos[1] + 25)
        self.add_boundary("button", (button_upper_left, button_botton_right),
                          RECT_BOUNDARY)

        self.response = None
    def construct_placeholder(self):
        stroke = [self.rect_thickness, WHITE, STROKE_CENTER]
        # width = None
        # height = None

        # Horizontal/vertical indicates directionality of placeholders length
        if self.box_alignment == VERTICAL:
            width, height = self.rect_short_side, self.rect_long_side

        else:
            height, width = self.rect_short_side, self.rect_long_side

        return Rectangle(width=width, height=height, stroke=stroke)
Exemplo n.º 4
0
    def __init__(self, data):

        self.media_type = IMAGE_FILE
        self.height = None
        self.width = None
        self.duration = None

        if data.text:
            # todo: make style optional
            self.contents = message(data.text.string,
                                    data.text.style,
                                    align="center",
                                    blit_txt=False)
        elif data.drawbject:
            d = data.drawbject
            if d.shape == "rectangle":
                self.contents = Rectangle(d.width, d.height, d.stroke,
                                          d.fill).render()
            if d.shape == "ellipse":
                self.contents = Ellipse(d.width, d.height, d.stroke,
                                        d.fill).render()
            if d.shape == "annulus":
                self.contents = Annulus(d.diameter, d.ring_width, d.stroke,
                                        d.fill).render()
        else:
            self.media_type = data.file.media_type
            if self.is_audio:
                self.duration = data.file
                self.contents = AudioClip(
                    join(P.resources_dir, "audio", data.file.filename))
            else:
                # If asset is image file, import and scale for current screen size (animations
                # originally hard-coded at 1920x1080 so we scale relative to that)
                img = Image.open(join(P.image_dir, data.file.filename))
                target_size = (P.screen_x, (P.screen_x / 16.0) * 9
                               )  # corrected for aspect ratio
                scaled_size = scale(img.size, (1920, 1080),
                                    target_size,
                                    center=False)
                self.contents = np.asarray(
                    img.resize(scaled_size, Image.BILINEAR))

        try:
            self.height = self.contents.height
            self.width = self.contents.width
        except AttributeError:
            try:
                self.height = self.contents.shape[0]
                self.width = self.contents.shape[1]
            except AttributeError:
                pass  # ie. audio file
Exemplo n.º 5
0
    def setup(self):

        # Generate messages to be displayed during experiment
        self.err_msgs = {}
        if P.saccade_response_cond:
            self.err_msgs['eye'] = "Moved eyes too soon!"
            self.err_msgs['key'] = "Please respond with eye movements only."
            self.err_msgs['early'] = self.err_msgs[
                'key']  # for convenience in logic
        else:
            self.err_msgs['eye'] = "Moved eyes!"
            self.err_msgs['key'] = "Please respond with the spacebar only."
            self.err_msgs['early'] = "Responded too soon!"

        # Stimulus sizes
        self.target_width = deg_to_px(
            0.5, even=True)  # diameter of target circle (0.5 degrees)
        self.cue_size = deg_to_px(
            0.5, even=True)  # size of asterisk/fixations (0.5 degrees)
        self.box_size = deg_to_px(
            0.8, even=True)  # size of placeholder boxes (0.8 degrees)

        # Stimulus Drawbjects
        self.box = Rectangle(self.box_size, stroke=(2, WHITE)).render()
        self.cross_r = FixationCross(self.cue_size, 2, fill=RED).render()
        self.cross_w = FixationCross(self.cue_size, 2, fill=WHITE).render()
        self.circle = Circle(self.target_width, fill=WHITE).render()
        self.asterisk = SquareAsterisk(self.cue_size, 2, fill=WHITE).render()

        # Layout of stimuli

        # offset between centre of boxes and centre of screen, in degrees
        offset_size_deg = P.dm_offset_size if P.development_mode else 7.0
        self.offset_size = deg_to_px(offset_size_deg)
        self.target_locs = {
            TOP: (P.screen_c[0], P.screen_c[1] - self.offset_size),
            RIGHT: (P.screen_c[0] + self.offset_size, P.screen_c[1]),
            BOTTOM: (P.screen_c[0], P.screen_c[1] + self.offset_size),
            LEFT: (P.screen_c[0] - self.offset_size, P.screen_c[1])
        }

        # prepare all animation locations for both rotation directions and starting axes
        self.animation_frames = 15
        animation_duration = 300  # ms
        self.frame_duration = animation_duration / self.animation_frames
        rotation_increment = (pi / 2) / self.animation_frames
        cx, cy = P.screen_c
        self.frames = {
            V_START_AXIS: {
                ROT_CW: [],
                ROT_CCW: []
            },
            H_START_AXIS: {
                ROT_CW: [],
                ROT_CCW: []
            }
        }
        for i in range(0, self.animation_frames):
            l_x_cw = -self.offset_size * cos(i * rotation_increment)
            l_y_cw = self.offset_size * sin(i * rotation_increment)
            l_x_ccw = self.offset_size * cos(i * rotation_increment)
            l_y_ccw = -self.offset_size * sin(i * rotation_increment)
            cw_locs = [(cx + l_x_cw, cy + l_y_cw), (cx - l_x_cw, cy - l_y_cw)]
            ccw_locs = [(cx + l_x_ccw, cy - l_y_ccw),
                        (cx - l_x_ccw, cy + l_y_ccw)]
            self.frames[H_START_AXIS][ROT_CW].append(ccw_locs)
            self.frames[H_START_AXIS][ROT_CCW].append(cw_locs)
            self.frames[V_START_AXIS][ROT_CW].insert(0, cw_locs)
            self.frames[V_START_AXIS][ROT_CCW].insert(0, ccw_locs)

        # Define keymap for ResponseCollector
        self.keymap = KeyMap(
            "speeded response",  # Name
            ["spacebar"],  # UI Label
            ["spacebar"],  # Data Label
            [SDLK_SPACE]  # SDL2 Keycode
        )

        # Boundaries for stimuli
        self.fixation_boundary = deg_to_px(
            3.0)  # radius of 3 degrees of visual angle
        self.el.add_boundary("drift_correct",
                             [P.screen_c, self.fixation_boundary],
                             CIRCLE_BOUNDARY)
Exemplo n.º 6
0
    def setup(self):

        # Set up custom text styles for the experiment
        self.txtm.add_style('instructions', 18, [255, 255, 255, 255])
        self.txtm.add_style('error', 18, [255, 0, 0, 255])
        self.txtm.add_style('tiny', 12, [255, 255, 255, 255])
        self.txtm.add_style('small', 14, [255, 255, 255, 255])

        # Pre-render shape stimuli
        dot_stroke = [P.dot_stroke, P.dot_stroke_col, STROKE_OUTER
                      ] if P.dot_stroke > 0 else None
        self.tracker_dot = Ellipse(P.dot_size,
                                   stroke=dot_stroke,
                                   fill=P.dot_color).render()
        self.origin_active = Ellipse(P.origin_size,
                                     fill=self.origin_active_color).render()
        self.origin_inactive = Ellipse(
            P.origin_size, fill=self.origin_inactive_color).render()

        # If capture figures mode, generate, view, and optionally save some figures
        if P.capture_figures_mode:
            self.fig_dir = os.path.join(P.resources_dir, "figures")
            self.capture_figures()
            self.quit()

        # Initialize participant ID and session options, reloading ID if it already exists
        self.session = TraceLabSession()
        self.user_id = self.session.user_id

        # Once session initialized, show loading screen and finish setup
        self.loading_msg = message("Loading...", "default", blit_txt=False)
        fill()
        blit(self.loading_msg, 5, P.screen_c)
        flip()

        # Scale UI size variables to current screen resolution
        P.btn_s_pad = scale((P.btn_s_pad, 0), (1920, 1080))[0]
        P.y_pad = scale((0, P.y_pad), (1920, 1080))[1]

        # Initialize messages and response buttons for control trials
        control_fail_txt = "Please keep your finger on the start area for the complete duration."
        self.control_fail_msg = message(control_fail_txt,
                                        'error',
                                        blit_txt=False)
        ctrl_buttons = ["1", "2", "3", "4", "5"]
        self.control_bar = ButtonBar(buttons=[(i, P.btn_size, None)
                                              for i in ctrl_buttons],
                                     button_size=P.btn_size,
                                     screen_margins=P.btn_s_pad,
                                     y_offset=P.y_pad,
                                     message_txt=P.control_q)

        # Initialize 'next trial' button
        button_x = 250 if self.handedness == LEFT_HANDED else P.screen_x - 250
        button_y = P.screen_y - 100
        self.next_trial_msg = message(P.next_trial_message,
                                      'default',
                                      blit_txt=False)
        self.next_trial_box = Rectangle(300,
                                        75,
                                        stroke=(2, (255, 255, 255),
                                                STROKE_OUTER))
        self.next_trial_button_loc = (button_x, button_y)
        bounds = [(button_x - 150, button_y - 38),
                  (button_x + 150, button_y + 38)]
        self.add_boundary("next trial button", bounds, RECT_BOUNDARY)

        # Initialize instructions and practice button bar for each condition
        self.instruction_files = {
            PHYS: {
                'text': "physical_group_instructions.txt",
                'frames': "physical_key_frames"
            },
            MOTR: {
                'text': "imagery_group_instructions.txt",
                'frames': "imagery_key_frames"
            },
            CTRL: {
                'text': "control_group_instructions.txt",
                'frames': "control_key_frames"
            }
        }
        self.practice_instructions = message(P.practice_instructions,
                                             "instructions",
                                             align="center",
                                             blit_txt=False)
        practice_buttons = [('Replay', [200, 100], self.practice),
                            ('Practice', [200, 100], self.__practice__),
                            ('Begin', [200, 100], any_key)]
        self.practice_button_bar = ButtonBar(practice_buttons, [200, 100],
                                             P.btn_s_pad,
                                             P.y_pad,
                                             finish_button=False)

        # Import all pre-generated figures needed for the current session
        figures = list(set(self.trial_factory.exp_factors["figure_name"]))
        figures.append(P.practice_figure)
        for f in figures:
            if f != "random":
                ui_request()
                fig_path = os.path.join(P.resources_dir, "figures", f)
                self.test_figures[f] = TraceLabFigure(fig_path)
Exemplo n.º 7
0
    def setup(self):

        # Bandit Variables

        self.high_payout_baseline = 12
        self.low_payout_baseline = 8
        self.total_score = None
        self.penalty = -5

        # Stimulus Sizes

        thick_rect_border = deg_to_px(0.5)
        thin_rect_border = deg_to_px(0.1)
        star_size = deg_to_px(0.6)
        star_thickness = deg_to_px(0.1)
        square_size = deg_to_px(3)
        text_size = deg_to_px(0.65)
        large_text_size = deg_to_px(0.85)

        # Generate bandit colours from colour wheel

        self.bandit_colour_combos = []
        if P.blocks_per_experiment > 4:
            msg = (
                "Only 4 sets of colours available, experiment script must be modified if more"
                "than 4 blocks total are wanted.")
            raise RuntimeError(msg)
        for angle in [0, 45, 90, 135]:
            combo = [const_lum[angle], const_lum[angle + 180]]
            self.bandit_colour_combos.append(combo)
        random.shuffle(self.bandit_colour_combos)

        # Stimulus Drawbjects

        self.thick_rect = Rectangle(
            square_size, stroke=[thick_rect_border, WHITE, STROKE_CENTER])
        self.thin_rect = Rectangle(
            square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER])
        self.left_bandit = Rectangle(
            square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER])
        self.right_bandit = Rectangle(
            square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER])
        self.neutral_box = self.thin_rect.render()
        self.star = Asterisk(star_size, star_thickness, fill=WHITE)
        self.star_cueback = Asterisk(star_size * 2,
                                     star_thickness * 2,
                                     fill=WHITE)
        self.star_muted = Asterisk(star_size, star_thickness, fill=GREY)
        self.probe = Ellipse(int(0.75 * square_size), fill=WHITE).render()

        # Layout

        box_offset = deg_to_px(8.0)
        self.left_box_loc = (P.screen_c[0] - box_offset, P.screen_c[1])
        self.right_box_loc = (P.screen_c[0] + box_offset, P.screen_c[1])

        # Timing

        # Note: cotoa = cue-offset target-onset asynchrony
        self.cotoa_min = 700  # ms
        self.cotoa_max = 1000  # ms
        self.feedback_exposure_period = 1.25  # sec

        # EyeLink Boundaries

        fix_bounds = [P.screen_c, square_size / 2]
        self.el.add_boundary('fixation', fix_bounds, CIRCLE_BOUNDARY)

        # Experiment Messages

        self.txtm.styles[
            'default'].font_size = text_size  # re-define default font size in degrees
        self.txtm.add_style("score up", large_text_size, PASTEL_GREEN)
        self.txtm.add_style("score down", large_text_size, PASTEL_RED)
        self.txtm.add_style("timeout", large_text_size, WHITE)

        err_txt = "{0}\n\nPress any key to continue."
        lost_fixation_txt = err_txt.format(
            "Eyes moved! Please keep your eyes on the asterisk.")
        too_soon_txt = err_txt.format(
            "Responded too soon! Please wait until the 'go' signal to "
            "make a response.")
        probe_timeout_txt = err_txt.format(
            "No response detected! Please answer louder or faster.")
        bandit_timeout_txt = err_txt.format("Bandit selection timed out!")
        wrong_response_txt = err_txt.format(
            "Wrong response type!\nPlease make vocal responses "
            "to probes and keypress responses to bandits.")

        self.err_msgs = {
            'fixation':
            message(lost_fixation_txt, align='center', blit_txt=False),
            'too_soon':
            message(too_soon_txt, align='center', blit_txt=False),
            'probe_timeout':
            message(probe_timeout_txt,
                    'timeout',
                    align='center',
                    blit_txt=False),
            'bandit_timeout':
            message(bandit_timeout_txt,
                    'timeout',
                    align='center',
                    blit_txt=False),
            'wrong_response':
            message(wrong_response_txt, align='center', blit_txt=False)
        }

        # Initialize separate ResponseCollectors for probe and bandit responses

        self.probe_rc = ResponseCollector(uses=[RC_AUDIO, RC_KEYPRESS])
        self.bandit_rc = ResponseCollector(uses=[RC_AUDIO, RC_KEYPRESS])

        # Initialize ResponseCollector keymap

        self.keymap = KeyMap(
            'bandit_response',  # Name
            ['z', '/'],  # UI labels
            ["left", "right"],  # Data labels
            [sdl2.SDLK_z, sdl2.SDLK_SLASH]  # SDL2 Keysyms
        )

        # Add practice block of 20 trials to start of experiment

        if P.run_practice_blocks:
            self.insert_practice_block(1, trial_counts=20)
Exemplo n.º 8
0
class IOR_Reward(klibs.Experiment):

    #TODO: Add checking for audio responses on bandit trials (keyboard might make this difficult?)

    def __init__(self, *args, **kwargs):
        super(IOR_Reward, self).__init__(*args, **kwargs)

    def setup(self):

        # Bandit Variables

        self.high_payout_baseline = 12
        self.low_payout_baseline = 8
        self.total_score = None
        self.penalty = -5

        # Stimulus Sizes

        thick_rect_border = deg_to_px(0.5)
        thin_rect_border = deg_to_px(0.1)
        star_size = deg_to_px(0.6)
        star_thickness = deg_to_px(0.1)
        square_size = deg_to_px(3)
        text_size = deg_to_px(0.65)
        large_text_size = deg_to_px(0.85)

        # Generate bandit colours from colour wheel

        self.bandit_colour_combos = []
        if P.blocks_per_experiment > 4:
            msg = (
                "Only 4 sets of colours available, experiment script must be modified if more"
                "than 4 blocks total are wanted.")
            raise RuntimeError(msg)
        for angle in [0, 45, 90, 135]:
            combo = [const_lum[angle], const_lum[angle + 180]]
            self.bandit_colour_combos.append(combo)
        random.shuffle(self.bandit_colour_combos)

        # Stimulus Drawbjects

        self.thick_rect = Rectangle(
            square_size, stroke=[thick_rect_border, WHITE, STROKE_CENTER])
        self.thin_rect = Rectangle(
            square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER])
        self.left_bandit = Rectangle(
            square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER])
        self.right_bandit = Rectangle(
            square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER])
        self.neutral_box = self.thin_rect.render()
        self.star = Asterisk(star_size, star_thickness, fill=WHITE)
        self.star_cueback = Asterisk(star_size * 2,
                                     star_thickness * 2,
                                     fill=WHITE)
        self.star_muted = Asterisk(star_size, star_thickness, fill=GREY)
        self.probe = Ellipse(int(0.75 * square_size), fill=WHITE).render()

        # Layout

        box_offset = deg_to_px(8.0)
        self.left_box_loc = (P.screen_c[0] - box_offset, P.screen_c[1])
        self.right_box_loc = (P.screen_c[0] + box_offset, P.screen_c[1])

        # Timing

        # Note: cotoa = cue-offset target-onset asynchrony
        self.cotoa_min = 700  # ms
        self.cotoa_max = 1000  # ms
        self.feedback_exposure_period = 1.25  # sec

        # EyeLink Boundaries

        fix_bounds = [P.screen_c, square_size / 2]
        self.el.add_boundary('fixation', fix_bounds, CIRCLE_BOUNDARY)

        # Experiment Messages

        self.txtm.styles[
            'default'].font_size = text_size  # re-define default font size in degrees
        self.txtm.add_style("score up", large_text_size, PASTEL_GREEN)
        self.txtm.add_style("score down", large_text_size, PASTEL_RED)
        self.txtm.add_style("timeout", large_text_size, WHITE)

        err_txt = "{0}\n\nPress any key to continue."
        lost_fixation_txt = err_txt.format(
            "Eyes moved! Please keep your eyes on the asterisk.")
        too_soon_txt = err_txt.format(
            "Responded too soon! Please wait until the 'go' signal to "
            "make a response.")
        probe_timeout_txt = err_txt.format(
            "No response detected! Please answer louder or faster.")
        bandit_timeout_txt = err_txt.format("Bandit selection timed out!")
        wrong_response_txt = err_txt.format(
            "Wrong response type!\nPlease make vocal responses "
            "to probes and keypress responses to bandits.")

        self.err_msgs = {
            'fixation':
            message(lost_fixation_txt, align='center', blit_txt=False),
            'too_soon':
            message(too_soon_txt, align='center', blit_txt=False),
            'probe_timeout':
            message(probe_timeout_txt,
                    'timeout',
                    align='center',
                    blit_txt=False),
            'bandit_timeout':
            message(bandit_timeout_txt,
                    'timeout',
                    align='center',
                    blit_txt=False),
            'wrong_response':
            message(wrong_response_txt, align='center', blit_txt=False)
        }

        # Initialize separate ResponseCollectors for probe and bandit responses

        self.probe_rc = ResponseCollector(uses=[RC_AUDIO, RC_KEYPRESS])
        self.bandit_rc = ResponseCollector(uses=[RC_AUDIO, RC_KEYPRESS])

        # Initialize ResponseCollector keymap

        self.keymap = KeyMap(
            'bandit_response',  # Name
            ['z', '/'],  # UI labels
            ["left", "right"],  # Data labels
            [sdl2.SDLK_z, sdl2.SDLK_SLASH]  # SDL2 Keysyms
        )

        # Add practice block of 20 trials to start of experiment

        if P.run_practice_blocks:
            self.insert_practice_block(1, trial_counts=20)

    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 setup_response_collector(self):

        # Configure probe response collector
        self.probe_rc.terminate_after = [2000, TK_MS]
        self.probe_rc.display_callback = self.probe_callback
        self.probe_rc.display_args = [self.trial_type == BOTH]
        self.probe_rc.flip = True
        self.probe_rc.keypress_listener.key_map = self.keymap
        self.probe_rc.keypress_listener.interrupts = True
        self.probe_rc.audio_listener.interrupts = True

        # Configure bandit response collector
        self.bandit_rc.terminate_after = [2000, TK_MS]
        self.bandit_rc.display_callback = self.bandit_callback
        self.bandit_rc.flip = True
        self.bandit_rc.keypress_listener.key_map = self.keymap
        self.bandit_rc.keypress_listener.interrupts = True
        if self.trial_type == BANDIT and not P.ignore_vocal_for_bandits:
            self.bandit_rc.audio_listener.interrupts = True
        else:
            self.bandit_rc.audio_listener.interrupts = False

    def trial_prep(self):

        # Reset error flag
        self.targets_shown = False
        self.err = None

        # If probed trial, establish location of probe (default: left box)
        self.probe_loc = self.right_box_loc if self.probe_location == RIGHT else self.left_box_loc

        if self.high_value_location == LEFT:
            self.left_bandit.fill = self.high_value_color
            self.right_bandit.fill = self.low_value_color
            self.low_value_location = RIGHT
        else:
            self.left_bandit.fill = self.low_value_color
            self.right_bandit.fill = self.high_value_color
            self.low_value_location = LEFT
        self.left_bandit.render()
        self.right_bandit.render()

        # Randomly choose cue off-target on asynchrony (cotoa) on each trial
        self.cotoa = self.random_interval(self.cotoa_min, self.cotoa_max)

        # Add timecourse of events to EventManager
        events = [[1000, 'cue_on']]
        events.append([events[-1][0] + 200, 'cue_off'])
        events.append([events[-1][0] + 200, 'cueback_off'])
        events.append([events[-2][0] + self.cotoa,
                       'target_on'])  # either probe or bandits
        if self.trial_type in [BANDIT, BOTH]:
            events.append([events[-1][0] + 500,
                           'nogo_end'])  # should reduce to 500 or less
        for e in events:
            self.evm.register_ticket(ET(e[1], e[0]))

        # Perform drift correct on EyeLink before trial start
        self.el.drift_correct()

    def trial(self):

        if P.development_mode:
            trial_info = (
                "\ntrial_type: '{0}', high_val_loc: '{1}', probe_loc: '{2}', "
                "cue_loc: '{3}', winning_bandit: '{4}'")
            print(
                trial_info.format(self.trial_type, self.high_value_location,
                                  self.probe_location, self.cue_location,
                                  self.winning_bandit))

        while self.evm.before('target_on', True) and not self.err:

            self.confirm_fixation()
            self.present_neutral_boxes()

            if self.evm.between('cue_on', 'cue_off'):
                if self.cue_location in [LEFT, DOUBLE]:
                    blit(self.thick_rect, 5, self.left_box_loc)
                if self.cue_location in [RIGHT, DOUBLE]:
                    blit(self.thick_rect, 5, self.right_box_loc)
            elif self.evm.between('cue_off', 'cueback_off'):
                blit(self.star_cueback, 5, P.screen_c)

            flip()

        self.targets_shown = True  # after bandits or probe shown, don't recycle trial on user error
        if self.trial_type in [BANDIT, BOTH] and not self.err:
            while self.evm.before('nogo_end') and not self.err:
                if key_pressed():
                    self.show_error_message('too_soon')
                    self.err = "early_response"
                    break
                self.confirm_fixation()
                self.bandit_callback(before_go=True)
                flip()

        #  PROBE RESPONSE PERIOD
        if self.trial_type in [PROBE, BOTH] and not self.err:
            self.probe_rc.collect()
            if not self.err:
                if len(self.probe_rc.keypress_listener.responses):
                    self.show_error_message('wrong_response')
                    self.err = 'keypress_on_probe'
                elif len(self.probe_rc.audio_listener.responses) == 0:
                    self.show_error_message('probe_timeout')
                    if self.probe_rc.audio_listener.stream_error:
                        self.err = 'microphone_error'
                    else:
                        self.err = 'probe_timeout'

        #  BANDIT RESPONSE PERIOD
        if self.trial_type in [BANDIT, BOTH] and not self.err:
            self.bandit_rc.collect()
            if self.trial_type == BANDIT and P.ignore_vocal_for_bandits == False:
                if len(self.bandit_rc.audio_listener.responses):
                    self.show_error_message('wrong_response')
                    self.err = 'vocal_on_bandit'

        # Retrieve collected response data before logging to database
        if self.err:
            bandit_choice, bandit_rt, reward = ['NA', 'NA', 'NA']
            probe_rt = 'NA'
        else:
            self.err = 'NA'
            # Retreive responses from RepsponseCollector(s) and record data
            if self.trial_type in [BANDIT, BOTH]:
                bandit_choice = self.bandit_rc.keypress_listener.response(
                    value=True, rt=False)
                bandit_rt = self.bandit_rc.keypress_listener.response(
                    value=False, rt=True)
                if bandit_rt == TIMEOUT:
                    self.show_error_message('bandit_timeout')
                    reward = 'NA'
                else:
                    # determine bandit payout (reward) and display feedback to participant
                    reward = self.feedback(bandit_choice)
            else:
                bandit_choice, bandit_rt, reward = ['NA', 'NA', 'NA']

            if self.trial_type in [PROBE, BOTH]:
                probe_rt = self.probe_rc.audio_listener.response(value=False,
                                                                 rt=True)
            else:
                probe_rt = 'NA'

        # Clear any remaining stimuli from screen before trial end
        clear()

        return {
            "block_num":
            P.block_number,
            "trial_num":
            P.trial_number,
            "trial_type":
            self.trial_type,
            "cue_loc":
            self.cue_location,
            "cotoa":
            self.cotoa,
            "high_value_col":
            self.high_value_color[:3] if self.trial_type != PROBE else "NA",
            "low_value_col":
            self.low_value_color[:3] if self.trial_type != PROBE else "NA",
            "high_value_loc":
            self.high_value_location if self.trial_type != PROBE else "NA",
            "winning_bandit":
            self.winning_bandit if self.trial_type != PROBE else "NA",
            "bandit_choice":
            bandit_choice,
            "bandit_rt":
            bandit_rt,
            "reward":
            reward,
            "probe_loc":
            self.probe_location if self.trial_type != BANDIT else "NA",
            "probe_rt":
            probe_rt,
            "err":
            self.err
        }

    def trial_clean_up(self):

        # Clear responses from response collectors before next trial
        self.probe_rc.audio_listener.reset()
        self.probe_rc.keypress_listener.reset()
        self.bandit_rc.audio_listener.reset()
        self.bandit_rc.keypress_listener.reset()

    def clean_up(self):
        pass

    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 bandit_payout(self, value):
        mean = self.high_payout_baseline if value == HIGH else self.low_payout_baseline
        # sample from normal distribution with sd of 1 and round to nearest int
        return int(random.gauss(mean, 1) + 0.5)

    def confirm_fixation(self):
        if not self.el.within_boundary('fixation', EL_GAZE_POS):
            self.show_error_message('fixation')
            if self.targets_shown:
                self.err = 'left_fixation'
            else:
                raise TrialException('gaze left fixation')  # recycle trial

    def show_error_message(self, msg_key):
        fill()
        blit(self.err_msgs[msg_key], location=P.screen_c, registration=5)
        flip()
        any_key()

    def random_interval(self, lower, upper):
        # utility function to generate random time intervals with a given range
        # that are multiples of the current refresh rate (e.g. 16.7ms for a 60Hz monitor)
        min_flips = int(round(lower / P.refresh_time))
        max_flips = int(round(upper / P.refresh_time))
        return random.choice(range(min_flips, max_flips + 1,
                                   1)) * P.refresh_time

    def present_neutral_boxes(self):
        fill()
        blit(self.star, 5, P.screen_c)
        blit(self.neutral_box, 5, self.left_box_loc)
        blit(self.neutral_box, 5, self.right_box_loc)

    def bandit_callback(self, before_go=False):
        fill()
        blit(self.star if before_go else self.star_muted, 5, P.screen_c)
        blit(self.left_bandit, 5, self.left_box_loc)
        blit(self.right_bandit, 5, self.right_box_loc)

    def probe_callback(self, mixed=False):
        self.confirm_fixation()
        if mixed:
            self.bandit_callback(True)
        else:
            self.present_neutral_boxes()

        probe_loc = self.right_box_loc if self.probe_location == RIGHT else self.left_box_loc
        blit(self.probe, 5, probe_loc)
Exemplo n.º 9
0
    def setup(self):

        # Stimulus sizes
        thick_rect_border = deg_to_px(0.5)
        thin_rect_border = deg_to_px(0.1)
        star_size = deg_to_px(0.6)
        star_thickness = deg_to_px(0.1)
        square_size = deg_to_px(3)
        large_text_size = 0.65

        # Stimulus drawbjects
        self.thick_rect = Rectangle(
            square_size, stroke=[thick_rect_border, WHITE, STROKE_CENTER])
        self.thin_rect = Rectangle(
            square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER])
        self.neutral_box = self.thin_rect.render()

        self.star = Asterisk(star_size, star_thickness, fill=WHITE)
        self.star_cueback = Asterisk(star_size * 2,
                                     star_thickness * 2,
                                     fill=WHITE)

        self.go = FixationCross(star_size, star_thickness, fill=BLACK)
        self.go.render()
        self.nogo = FixationCross(star_size,
                                  star_thickness,
                                  fill=BLACK,
                                  rotation=45)
        self.nogo.render()

        self.left_bandit = Ellipse(int(0.75 * square_size))
        self.right_bandit = Ellipse(int(0.75 * square_size))
        self.probe = Ellipse(int(0.75 * square_size))

        # Layout
        box_offset = deg_to_px(8.0)
        self.left_box_loc = (P.screen_c[0] - box_offset, P.screen_c[1])
        self.right_box_loc = (P.screen_c[0] + box_offset, P.screen_c[1])

        # Set cotoa
        self.cotoa = 800  # ms

        self.feedback_exposure_period = 1.25  # sec

        # Bandit payout variables
        self.high_payout_baseline = 12
        self.low_payout_baseline = 8
        self.total_score = None
        self.penalty = -5

        # Generate colours from colour wheel
        self.target_colours = [const_lum[0], const_lum[120], const_lum[240]]
        random.shuffle(self.target_colours)

        # Assign to bandits & neutral probe
        self.high_value_colour = self.target_colours[0]
        self.low_value_colour = self.target_colours[1]
        self.neutral_value_colour = self.target_colours[2]

        # EyeLink Boundaries
        fix_bounds = [P.screen_c, square_size / 2]
        self.el.add_boundary('fixation', fix_bounds, CIRCLE_BOUNDARY)

        # Initialize response collectors
        self.probe_rc = ResponseCollector(uses=RC_KEYPRESS)
        self.bandit_rc = ResponseCollector(uses=RC_KEYPRESS)

        # Initialize ResponseCollector keymaps
        self.bandit_keymap = KeyMap(
            'bandit_response',  # Name
            ['z', '/'],  # UI labels
            ["left", "right"],  # Data labels
            [sdl2.SDLK_z, sdl2.SDLK_SLASH]  # SDL2 Keysyms
        )
        self.probe_keymap = KeyMap('probe_response', ['spacebar'], ["pressed"],
                                   [sdl2.SDLK_SPACE])

        # Experiment Messages
        self.txtm.add_style("payout", large_text_size, WHITE)
        self.txtm.add_style("timeout", large_text_size, WHITE)

        err_txt = "{0}\n\nPress any key to continue."
        lost_fixation_txt = err_txt.format(
            "Eyes moved! Please keep your eyes on the asterisk.")
        probe_timeout_txt = err_txt.format(
            "No response detected! Please respond as fast and as accurately as possible."
        )
        bandit_timeout_txt = err_txt.format("Bandit selection timed out!")
        response_on_nogo_txt = err_txt.format(
            "\'nogo\' signal (x) presented\nPlease only respond when you see "
            "the \'go\' signal (+).")

        self.err_msgs = {
            'fixation':
            message(lost_fixation_txt, align='center', blit_txt=False),
            'probe_timeout':
            message(probe_timeout_txt,
                    'timeout',
                    align='center',
                    blit_txt=False),
            'bandit_timeout':
            message(bandit_timeout_txt,
                    'timeout',
                    align='center',
                    blit_txt=False),
            'response_on_nogo':
            message(response_on_nogo_txt, align='center', blit_txt=False)
        }

        self.rest_break_txt = err_txt.format(
            "Whew! that was tricky eh? Go ahead and take a break before continuing."
        )
        self.end_of_block_txt = "You're done the first task! Please buzz the researcher to let them know!"

        # Insert bandit block
        if P.run_practice_blocks:
            self.insert_practice_block(1, trial_counts=P.trials_bandit_block)
Exemplo n.º 10
0
class IOR_Reward_V2(klibs.Experiment):
    def setup(self):

        # Stimulus sizes
        thick_rect_border = deg_to_px(0.5)
        thin_rect_border = deg_to_px(0.1)
        star_size = deg_to_px(0.6)
        star_thickness = deg_to_px(0.1)
        square_size = deg_to_px(3)
        large_text_size = 0.65

        # Stimulus drawbjects
        self.thick_rect = Rectangle(
            square_size, stroke=[thick_rect_border, WHITE, STROKE_CENTER])
        self.thin_rect = Rectangle(
            square_size, stroke=[thin_rect_border, WHITE, STROKE_CENTER])
        self.neutral_box = self.thin_rect.render()

        self.star = Asterisk(star_size, star_thickness, fill=WHITE)
        self.star_cueback = Asterisk(star_size * 2,
                                     star_thickness * 2,
                                     fill=WHITE)

        self.go = FixationCross(star_size, star_thickness, fill=BLACK)
        self.go.render()
        self.nogo = FixationCross(star_size,
                                  star_thickness,
                                  fill=BLACK,
                                  rotation=45)
        self.nogo.render()

        self.left_bandit = Ellipse(int(0.75 * square_size))
        self.right_bandit = Ellipse(int(0.75 * square_size))
        self.probe = Ellipse(int(0.75 * square_size))

        # Layout
        box_offset = deg_to_px(8.0)
        self.left_box_loc = (P.screen_c[0] - box_offset, P.screen_c[1])
        self.right_box_loc = (P.screen_c[0] + box_offset, P.screen_c[1])

        # Set cotoa
        self.cotoa = 800  # ms

        self.feedback_exposure_period = 1.25  # sec

        # Bandit payout variables
        self.high_payout_baseline = 12
        self.low_payout_baseline = 8
        self.total_score = None
        self.penalty = -5

        # Generate colours from colour wheel
        self.target_colours = [const_lum[0], const_lum[120], const_lum[240]]
        random.shuffle(self.target_colours)

        # Assign to bandits & neutral probe
        self.high_value_colour = self.target_colours[0]
        self.low_value_colour = self.target_colours[1]
        self.neutral_value_colour = self.target_colours[2]

        # EyeLink Boundaries
        fix_bounds = [P.screen_c, square_size / 2]
        self.el.add_boundary('fixation', fix_bounds, CIRCLE_BOUNDARY)

        # Initialize response collectors
        self.probe_rc = ResponseCollector(uses=RC_KEYPRESS)
        self.bandit_rc = ResponseCollector(uses=RC_KEYPRESS)

        # Initialize ResponseCollector keymaps
        self.bandit_keymap = KeyMap(
            'bandit_response',  # Name
            ['z', '/'],  # UI labels
            ["left", "right"],  # Data labels
            [sdl2.SDLK_z, sdl2.SDLK_SLASH]  # SDL2 Keysyms
        )
        self.probe_keymap = KeyMap('probe_response', ['spacebar'], ["pressed"],
                                   [sdl2.SDLK_SPACE])

        # Experiment Messages
        self.txtm.add_style("payout", large_text_size, WHITE)
        self.txtm.add_style("timeout", large_text_size, WHITE)

        err_txt = "{0}\n\nPress any key to continue."
        lost_fixation_txt = err_txt.format(
            "Eyes moved! Please keep your eyes on the asterisk.")
        probe_timeout_txt = err_txt.format(
            "No response detected! Please respond as fast and as accurately as possible."
        )
        bandit_timeout_txt = err_txt.format("Bandit selection timed out!")
        response_on_nogo_txt = err_txt.format(
            "\'nogo\' signal (x) presented\nPlease only respond when you see "
            "the \'go\' signal (+).")

        self.err_msgs = {
            'fixation':
            message(lost_fixation_txt, align='center', blit_txt=False),
            'probe_timeout':
            message(probe_timeout_txt,
                    'timeout',
                    align='center',
                    blit_txt=False),
            'bandit_timeout':
            message(bandit_timeout_txt,
                    'timeout',
                    align='center',
                    blit_txt=False),
            'response_on_nogo':
            message(response_on_nogo_txt, align='center', blit_txt=False)
        }

        self.rest_break_txt = err_txt.format(
            "Whew! that was tricky eh? Go ahead and take a break before continuing."
        )
        self.end_of_block_txt = "You're done the first task! Please buzz the researcher to let them know!"

        # Insert bandit block
        if P.run_practice_blocks:
            self.insert_practice_block(1, trial_counts=P.trials_bandit_block)

    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 setup_response_collector(self):

        # Configure probe response collector
        self.probe_rc.terminate_after = [1500, TK_MS]
        self.probe_rc.display_callback = self.probe_callback
        self.probe_rc.flip = True
        self.probe_rc.keypress_listener.key_map = self.probe_keymap
        self.probe_rc.keypress_listener.interrupts = True

        # Configure bandit response collector
        self.bandit_rc.terminate_after = [1500, TK_MS]
        self.bandit_rc.display_callback = self.bandit_callback
        self.bandit_rc.flip = True
        self.bandit_rc.keypress_listener.key_map = self.bandit_keymap
        self.bandit_rc.keypress_listener.interrupts = True

    def trial_prep(self):
        # Reset error flag
        self.targets_shown = False
        self.err = None

        # BANDIT PROPERTIES
        if P.practicing:
            self.cotoa = 'NA'
            # Establish location & colour of bandits

            if self.high_value_location == LEFT:
                self.left_bandit.fill = self.high_value_colour
                self.right_bandit.fill = self.low_value_colour
                self.low_value_location = RIGHT
            else:
                self.left_bandit.fill = self.low_value_colour
                self.right_bandit.fill = self.high_value_colour
                self.low_value_location = LEFT
            self.left_bandit.render()
            self.right_bandit.render()

        # PROBE PROPERTIES
        else:
            # Rest breaks
            if P.trial_number % (P.trials_per_block / P.breaks_per_block) == 0:
                if P.trial_number < P.trials_per_block:
                    fill()
                    msg = message(self.rest_break_txt,
                                  'timeout',
                                  blit_txt=False)
                    blit(msg, 5, P.screen_c)
                    flip()
                    any_key()

            # Establish & assign probe location
            self.probe_loc = self.right_box_loc if self.probe_location == RIGHT else self.left_box_loc
            # go/nogo signal always presented w/probe
            self.go_nogo_loc = self.probe_loc

            # Establish & assign probe colour
            if self.probe_colour == HIGH:
                self.probe.fill = self.high_value_colour
            elif self.probe_colour == LOW:
                self.probe.fill = self.low_value_colour
            else:
                self.probe.fill = self.neutral_value_colour
            self.probe.render()

        # Add timecourse of events to EventManager
        if P.practicing:  # Bandit trials
            events = [[1000, 'target_on']]
        else:  # Probe trials
            events = [[1000, 'cue_on']]
            events.append([events[-1][0] + 200, 'cue_off'])
            events.append([events[-1][0] + 200, 'cueback_off'])
            events.append([events[-2][0] + 800, 'target_on'])
        for e in events:
            self.evm.register_ticket(ET(e[1], e[0]))

        # Perform drift correct on Eyelink before trial start
        self.el.drift_correct()

    def trial(self):

        # BANDIT TRIAL
        if P.practicing:
            cotoa, probe_rt = ['NA', 'NA']  # Don't occur in bandit blocks

            # Present placeholders
            while self.evm.before('target_on', True) and not self.err:
                self.confirm_fixation()
                self.present_neutral_boxes()
                flip()

            # BANDIT RESPONSE PERIOD
            self.targets_shown = True  # After bandits shown, don't recycle trial

            # Present bandits and listen for response
            self.bandit_rc.collect()

            # If wrong response made
            if self.err:
                bandit_choice, bandit_rt, reward = ['NA', 'NA', 'NA']
            else:
                self.err = 'NA'
                # Retrieve responses from ResponseCollector(s) & record data
                bandit_choice = self.bandit_rc.keypress_listener.response(
                    value=True, rt=False)
                bandit_rt = self.bandit_rc.keypress_listener.response(
                    value=False, rt=True)

                if bandit_rt == TIMEOUT:
                    self.show_error_message('bandit_timeout')
                    reward = 'NA'
                else:
                    # Determine bandit payout & display
                    reward = self.feedback(bandit_choice)

        # PROBE TRIAL
        else:
            bandit_choice, bandit_rt, reward = [
                'NA', 'NA', 'NA'
            ]  # Don't occur in probe trials

            # Present placeholders & confirm fixation
            while self.evm.before('target_on', True):
                self.confirm_fixation()
                self.present_neutral_boxes()

                # Present cue
                if self.evm.between('cue_on', 'cue_off'):
                    if self.cue_location == LEFT:
                        blit(self.thick_rect, 5, self.left_box_loc)
                    else:
                        blit(self.thick_rect, 5, self.right_box_loc)
                # Present cueback
                elif self.evm.between('cue_off', 'cueback_off'):
                    blit(self.star_cueback, 5, P.screen_c)

                flip()

            # PROBE RESPONSE PERIOD
            self.targets_shown = True  # After probe shown, don't recycle trial
            # Present probes & listen for response
            self.probe_rc.collect()

            # If 'go' trial, check for response
            if self.go_no_go == GO:
                # If wrong response made
                if self.err:
                    probe_rt = 'NA'
                # If correct response OR timeout
                else:
                    self.err = 'NA'
                    probe_rt = self.probe_rc.keypress_listener.response(
                        value=False, rt=True)
                    if probe_rt == TIMEOUT:
                        self.show_error_message('probe_timeout')
                        probe_rt = 'NA'
            # Similarly, for 'nogo' trials
            else:
                probe_rt = 'NA'
                # If response made, penalize
                if len(self.probe_rc.keypress_listener.responses):
                    self.show_error_message('response_on_nogo')
                    self.err = 'response_on_nogo'
                # If no response, continue as normal
                else:
                    self.err = 'NA'
        # Return trial data
        return {
            "block_num": P.block_number,
            "trial_num": P.trial_number,
            "block_type": "BANDIT" if P.practicing else "PROBE",
            "high_value_col":
            self.high_value_colour[:3] if P.practicing else 'NA',
            "high_value_loc":
            self.high_value_location if P.practicing else 'NA',
            "low_value_col":
            self.low_value_colour[:3] if P.practicing else 'NA',
            "low_value_loc": self.low_value_location if P.practicing else 'NA',
            "winning_trial": self.winning_trial if P.practicing else 'NA',
            "bandit_selected": self.bandit_selected if P.practicing else 'NA',
            "bandit_rt": bandit_rt,
            "reward": reward,
            "cue_loc": self.cue_location if not P.practicing else 'NA',
            "cotoa": self.cotoa if not P.practicing else 'NA',
            "probe_loc": self.probe_location if not P.practicing else 'NA',
            "probe_col": self.probe_colour if not P.practicing else 'NA',
            "go_no_go": self.go_no_go if not P.practicing else 'NA',
            "probe_rt": probe_rt,
            "err": self.err
        }
        # Clear remaining stimuli from screen
        clear()

    def trial_clean_up(self):
        # Clear responses from responses collectors before next trial
        self.probe_rc.keypress_listener.reset()
        self.bandit_rc.keypress_listener.reset()

    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()

    # Determines & presents feedback
    def feedback(self, response):

        # Keep count of bandit choices
        if response == self.high_value_location:
            self.bandit_selected = HIGH
            self.times_selected_high = self.times_selected_high + 1
            # Occasionally probe participant learning
            if self.times_selected_high in [5, 10, 15]:
                self.query_learning(HIGH)

        else:
            self.bandit_selected = LOW
            self.time_selected_low = self.time_selected_low + 1
            if self.time_selected_low in [5, 10, 15]:
                self.query_learning(LOW)

        # Determine payout
        if self.winning_trial == YES:
            points = self.bandit_payout(value=self.bandit_selected)
            msg = message("You won {0} points!".format(points),
                          "payout",
                          blit_txt=False)
        else:
            points = self.penalty  # -5
            msg = message("You lost 5 points!", "payout", blit_txt=False)

        # Running point total
        self.total_score += points
        feedback = [points, msg]

        # Present payout
        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]

    # Calculates bandit payout
    def bandit_payout(self, value):
        mean = self.high_payout_baseline if value == HIGH else self.low_payout_baseline
        # sample from normal distribution with sd of 1 and round to nearest int
        return int(random.gauss(mean, 1) + 0.5)

    # Confirms whether Ss are fixating
    def confirm_fixation(self):
        if not self.el.within_boundary('fixation', EL_GAZE_POS):
            self.show_error_message('fixation')
            if self.targets_shown:
                self.err = 'left_fixation'
            else:
                raise TrialException('gaze left fixation')  # recycle trial

    # Presents error messages
    def show_error_message(self, msg_key):
        fill()
        blit(self.err_msgs[msg_key], location=P.screen_c, registration=5)
        flip()
        any_key()

    # Utility function to generate random time intervals with a given range
    # that are multiples of the current refresh rate (e.g. 16.7ms for a 60Hz monitor)
    def random_interval(self, lower, upper):
        min_flips = int(round(lower / P.refresh_time))
        max_flips = int(round(upper / P.refresh_time))
        return random.choice(range(min_flips, max_flips + 1,
                                   1)) * P.refresh_time

    # Presents neutral boxes, duh
    def present_neutral_boxes(self):
        fill()
        blit(self.star, 5, P.screen_c)
        blit(self.neutral_box, 5, self.left_box_loc)
        blit(self.neutral_box, 5, self.right_box_loc)

    # Presents bandits
    def bandit_callback(self, before_go=False):
        self.confirm_fixation()
        self.present_neutral_boxes()

        blit(self.left_bandit, 5, self.left_box_loc)
        blit(self.right_bandit, 5, self.right_box_loc)

    # Presents probes
    def probe_callback(self):
        self.confirm_fixation()
        self.present_neutral_boxes()

        # Present probe & go/nogo stimulus
        if self.go_no_go == GO:
            blit(self.probe, 5, self.probe_loc)
            blit(self.go, 5, self.probe_loc)
        else:
            blit(self.probe, 5, self.probe_loc)
            blit(self.nogo, 5, self.probe_loc)

    # Assesses learning by asking Ss their anticipated trial earnings
    def query_learning(self, bandit):
        if bandit == HIGH:
            anticipated_reward_high = query(user_queries.experimental[0])
            anticipated_reward_survey = {
                'participant_id': P.participant_id,
                'anticipated_reward_high': anticipated_reward_high,
                'anticipated_reward_low': "NA"
            }
        else:
            anticipated_reward_low = query(user_queries.experimental[1])
            anticipated_reward_survey = {
                'participant_id': P.participant_id,
                'anticipated_reward_high': "NA",
                'anticipated_reward_low': anticipated_reward_low
            }

        self.db.insert(anticipated_reward_survey, table='surveys')