示例#1
0
    def start_trial_button(self):

        fill()
        blit(self.next_trial_box,
             5,
             self.next_trial_button_loc,
             flip_x=P.flip_x)
        blit(self.next_trial_msg,
             5,
             self.next_trial_button_loc,
             flip_x=P.flip_x)
        flip()

        if P.demo_mode or P.dm_always_show_cursor:
            show_mouse_cursor()

        flush()
        clicked = False
        while not clicked:
            event_queue = pump(True)
            for e in event_queue:
                if e.type == SDL_MOUSEBUTTONDOWN:
                    clicked = self.within_boundary("next trial button",
                                                   [e.button.x, e.button.y])
                elif e.type == SDL_KEYDOWN:
                    ui_request(e.key.keysym)

        if not (P.demo_mode or P.dm_always_show_cursor):
            hide_mouse_cursor()
示例#2
0
    def log_and_recycle_trial(self, err_type):
        """
		Renders an error message to the screen and wait for a response. When a
		response is made, the incomplete trial data is logged to the trial_err
		table and the trial is recycled.
		
		"""
        flush()
        fill()
        message(self.err_msgs[err_type], registration=5, location=P.screen_c)
        flip()
        any_key()
        err_data = {
            "participant_id": P.participant_id,
            "block_num": P.block_number,
            "trial_num": P.trial_number,
            "session_type":
            'saccade' if P.saccade_response_cond else 'keypress',
            "cue_location": self.cue_location,
            "target_location": self.target_location,
            "start_axis": self.start_axis,
            "box_rotation": self.rotation_dir if self.animation_trial else NA,
            "animation_trial": boolean_to_logical(self.animation_trial),
            "err_type": err_type
        }
        self.database.insert(data=err_data, table="trials_err")
        raise TrialException(self.err_msgs[err_type])
示例#3
0
    def trial_prep(self):

        # Infer the cue location based on starting axis (ie. left and top boxes are 'box 1',
        # bottom and right are 'box 2')
        if self.cue_location == BOX_1:
            self.cue_location = LEFT if self.start_axis is H_START_AXIS else TOP
        else:
            self.cue_location = RIGHT if self.start_axis is H_START_AXIS else BOTTOM

        # Reset trial flags
        self.before_target = True
        self.target_acquired = False
        self.moved_eyes_during_rc = False

        # Add timecourse of events to EventManager
        self.evm.register_tickets([
            ("cross fix end", 300),
            ("circle fix end", 1100),  #800ms after cross fix end
            ("cue end", 1400),  #300ms after circle fix end
            ("circle box end", 1600),  #200ms after cue end
            ("animation end", 1900),  #300ms after circle box end
            ("asterisk end", 2060),  #160ms after animation end
            ("task end", 4560)  #2500ms after asterisk end
        ])

        # Perform drift correct with red fixation cross, changing to white upon
        # completion
        self.display_refresh(self.start_axis, self.cross_r)
        self.el.drift_correct(fill_color=BLACK, el_draw_fixation=EL_FALSE)
        self.display_refresh(self.start_axis, self.cross_w)
        flush()
示例#4
0
    def block(self):

        # Show block message at start of every block
        header = "Block {0} of {1}".format(P.block_number,
                                           P.blocks_per_experiment)
        if P.practicing:
            header = "This is a practice block. ({0})".format(header)
            practice_msg = "During this block you will be given feedback for your responses."
            msg = message(header + "\n" + practice_msg,
                          align="center",
                          blit_txt=False)
        else:
            msg = message(header, blit_txt=False)

        message_interval = CountDown(1)
        while message_interval.counting():
            ui_request()  # Allow quitting during loop
            fill()
            blit(msg, 8, (P.screen_c[0], P.screen_y * 0.4))
            flip()
        flush()

        start_msg = message("Press any key to start.", blit_txt=False)
        fill()
        blit(msg, 8, (P.screen_c[0], P.screen_y * 0.4))
        blit(start_msg, 5, [P.screen_c[0], P.screen_y * 0.6])
        flip()
        any_key()
示例#5
0
    def block_msg(self, text):

        msg = message(text, 'block_msg', blit_txt=False)
        fill()
        blit(msg, 5, P.screen_c)
        flip()
        flush()
        any_key()
    def trial(self):


        while self.evm.before('cue_on'):
            self.wait_time()

        self.display_refresh(cue=True)

        while self.evm.before('cue_off'):
            self.wait_time()


        self.display_refresh()

        while self.evm.before('target_on'):
            self.wait_time()

        flush()

        self.display_refresh(target=True)

        if P.saccade_response_cond:
            self.record_saccades()
            keypress_rt = 'NA'

        if P.keypress_response_cond:
            self.rc.collect()
            keypress_rt = self.rc.keypress_listener.response(rt=True, value=False)

        clear()
        smart_sleep(1000)

        if P.keypress_response_cond:
            if self.target_location == "catch" and keypress_rt != TIMEOUT:
                fill()
                message(self.err_msgs['early'], registration=5, location=P.screen_c)
                flip()
                any_key()
            elif self.moved_eyes_during_rc:
                fill()
                message("Moved eyes during response interval!", registration=5, location=P.screen_c)
                flip()
                any_key()

        return {
            "block_num": P.block_number,
            "trial_num": P.trial_number,
            'session_type': 'saccade' if P.saccade_response_cond else 'keypress',
            'box_alignment': self.box_alignment,
            'cue_location': self.cue_location,
            'target_location': self.target_location,
            'target_acquired': str(self.target_acquired).upper() if P.saccade_response_cond else NA,
            'keypress_rt': keypress_rt if P.keypress_response_cond else NA,
            'moved_eyes': str(self.moved_eyes_during_rc).upper() if P.keypress_response_cond else NA

        }
示例#7
0
 def show_logo(self):
     from klibs.KLUtilities import flush
     from klibs.KLUserInterface import any_key
     from klibs.KLGraphics import fill, blit, flip
     from klibs.KLGraphics import NumpySurface as NpS
     logo = NpS(P.logo_file_path)
     flush()
     for i in (1, 2):
         fill()
         blit(logo, 5, P.screen_c)
         flip()
     any_key()
    def trial_prep(self):

        if P.development_mode:
            print "\ntrial factors"
            print "======================"

            fill()
            msg = "Box Alignment: {0}\nCue Location: {1}\nTarget Location: {2}".format(
                self.box_alignment, self.cue_location, self.target_location)
            print msg
            print "======================"
            message(msg, registration=5, location=P.screen_c, blit_txt=True)
            flip()

            any_key()

            clear()
            any_key()


        self.placeholder = self.construct_placeholder()
        self.cue = self.construct_cue()

        self.cue_loc = self.locations[self.cue_location]

        self.set_box_positions()

        self.target_trial = False

        if self.target_location != 'catch':
            self.target_trial = True
            self.target_loc = self.get_target_location()

        self.evm.register_tickets([
            ('cue_on', 1000),       # Cue appears 1000ms after drift check
            ('cue_off', 1100),      # Cue removed after 100ms
            ('target_on', 1960),    # Target appears 860ms after cue removal
            ('task_end', 4460)     # 2500ms to respond to target before trial aborts
        ])

        # Reset trial flags
        self.before_target = True
        self.target_acquired = False
        self.moved_eyes_during_rc = False

        self.display_refresh()
        self.el.drift_correct(fill_color=BLACK, draw_target=EL_FALSE)
        self.fix.fill = WHITE
        self.display_refresh()
        flush()
    def block(self):

        block_num = P.block_number
        block_count = P.blocks_per_experiment

        # Display progress messages at start of blocks
        if block_num > 1:
            flush()
            fill()
            block_msg = "Completed block {0} of {1}. Press any key to continue."
            block_msg = block_msg.format(block_num - 1, block_count)
            message(block_msg, registration=5, location=P.screen_c)
            flip()
            any_key()
示例#10
0
    def get_peak_during(self, period, msg=None):
        """Determines the peak loudness value recorded over a given period. Displays a visual
		callback that shows the current input volume and the loudest peak encounteredduring the
		interval so far.
		
		Args:
			period (numeric): the number of seconds to record input for.
			msg (:obj:`~klibs.KLGraphics.KLNumpySurface.NumpySurface`, optional): a rendered message
				to display in the top-right corner of the screen during the sampling loop.

		Returns:
			int: the loudest peak of all samples recorded during the period.

		"""
        local_peak = 0
        last_sample = 0
        if msg:
            msg = message(msg, blit_txt=False)

        flush()
        self.stream.start()
        sample_period = CountDown(period + 0.05)
        while sample_period.counting():
            ui_request()
            sample = self.stream.sample().peak
            if sample_period.elapsed() < 0.05:
                # Sometimes 1st or 2nd peaks are extremely high for no reason, so ignore first 50ms
                continue
            if sample > local_peak:
                local_peak = sample
            sample_avg = (sample + last_sample) / 2
            peak_circle = peak(5, int(
                (local_peak / 32767.0) * P.screen_y * 0.8))
            sample_circle = peak(
                5, int((sample_avg / 32767.0) * P.screen_y * 0.8))
            last_sample = sample

            fill()
            blit(Ellipse(peak_circle, fill=[255, 145, 0]),
                 location=P.screen_c,
                 registration=5)
            blit(Ellipse(sample_circle, fill=[84, 60, 182]),
                 location=P.screen_c,
                 registration=5)
            if msg:
                blit(msg, location=[25, 25], registration=7)
            flip()
        self.stream.stop()
        return local_peak
示例#11
0
    def block(self):
        
        # Determine probe bias for block and generate list of probe locs accordingly
        
        if P.block_number > 3:
            self.probe_bias = "left"
            nonbiased_loc = "right"
        else:
            self.probe_bias = "right"
            nonbiased_loc = "left"
        loc_list = [self.probe_bias]*4 + [nonbiased_loc]
        self.probe_locs = loc_list * int(P.trials_per_block/float(len(loc_list))+1)
        random.shuffle(self.probe_locs)
        
        # At the start of each block, display a start message (block progress if experimental block,
        # practice message if practice block). After 3000ms, keypress will start first trial.
        
        probe_msg = (
            "During this block, the colour target will appear more often on the "
            "{0} and less often on the {1}.".format(self.probe_bias, nonbiased_loc)
        )
        header = "Block {0} of {1}".format(P.block_number, P.blocks_per_experiment)
        if P.practicing:
            header = "This is a practice block. ({0})".format(header)
        if P.block_number > 1:
            msg = message(header+"\n"+probe_msg, align="center", blit_txt=False)
        else:
            msg = message(header, blit_txt=False)

        message_interval = CountDown(1)
        while message_interval.counting():
            ui_request() # Allow quitting during loop
            fill()
            blit(msg, 8, (P.screen_c[0], P.screen_y*0.4))
            flip()
        flush()
        
        fill()
        blit(msg, 8, (P.screen_c[0], P.screen_y*0.4))
        message("Press any key to start.", registration=5, location=[P.screen_c[0], P.screen_y*0.6])
        flip()
        any_key()

        # When running participants, send halfway point and last-block notifications to researcher via Slack

        if not P.development_mode:
            if P.block_number == 3: # If participant is halfway done
                slack_message("Halfway done ({0}/{1})".format(P.block_number, P.blocks_per_experiment))
示例#12
0
    def clean_up(self):

        if self.session_number == self.session_count and P.enable_learned_figures_querying:
            self.fig_dir = os.path.join(self.p_dir, "learned")
            if not os.path.exists(self.fig_dir):
                os.makedirs(self.fig_dir)

            learned_fig_num = 1
            if query(user_queries.experimental[3]) == "y":
                self.origin_pos = (P.screen_c[0], int(P.screen_y * 0.8))
                self.origin_boundary = [self.origin_pos, P.origin_size // 2]
                while True:
                    self.setup_response_collector()
                    self.rc.draw_listener.add_boundaries([
                        ('start', self.origin_boundary, CIRCLE_BOUNDARY),
                        ('stop', self.origin_boundary, CIRCLE_BOUNDARY)
                    ])
                    self.start_trial_button()
                    self.capture_learned_figure(learned_fig_num)
                    if query(user_queries.experimental[4]) == "y":
                        learned_fig_num += 1
                    else:
                        break

        # if the entire experiment is successfully completed, update the sessions_completed column
        q_str = "UPDATE `participants` SET `sessions_completed` = ? WHERE `id` = ?"
        self.db.query(q_str,
                      QUERY_UPD,
                      q_vars=[self.session_number, P.participant_id])

        # log session data to database
        session_data = {
            'participant_id': P.participant_id,
            'user_id': self.user_id,
            'session_number': self.session_number,
            'completed': now(True)
        }
        self.db.insert(session_data, "sessions")

        # show 'experiment complete' message before exiting experiment
        msg = message(P.experiment_complete_message,
                      "instructions",
                      blit_txt=False)
        flush()
        fill()
        blit(msg, registration=5, location=P.screen_c)
        flip()
        any_key()
示例#13
0
 def collect_response(self):
     self.start = time.time()
     finished = False
     selection = None
     last_selected = None
     flush()
     mt_start = None
     while not finished:
         show_mouse_cursor()
         events = pump(True)
         for e in events:
             if e.type == sdl2.SDL_KEYDOWN:
                 ui_request(e.key.keysym)
             elif e.type == sdl2.SDL_MOUSEBUTTONDOWN:
                 selection = None
                 for b in self.buttons:
                     if self.within_boundary(b.button_text,
                                             [e.button.x, e.button.y]):
                         self.toggle(b)
                         if not self.rt:
                             self.rt = time.time() - self.start
                             mt_start = time.time()
                         if b.active:
                             selection = b
                             last_selected = b
                             if callable(b.callback):
                                 if self.finish_b is None:
                                     return b.callback
                                 else:
                                     b.callback()
                     try:
                         if self.finish_b.active and self.within_boundary(
                                 "Done", [e.button.x, e.button.y]):
                             self.response = int(last_selected.button_text)
                             self.mt = time.time() - mt_start
                             finished = True
                     except AttributeError:
                         pass
         try:
             self.finish_b.active = selection is not None
         except AttributeError:
             pass
         self.render()
     fill()
     flip()
     hide_mouse_cursor()
示例#14
0
    def physical_trial(self):

        self.rc.collect()
        self.rt = self.rc.draw_listener.start_time
        self.drawing = self.rc.draw_listener.responses[0][0]
        self.it = self.rc.draw_listener.first_sample_time - self.rt
        self.mt = self.rc.draw_listener.responses[0][1]

        if self.feedback_type in (FB_ALL, FB_RES) and not self.__practicing__:
            flush()
            fill()
            blit(self.figure.render(trace=self.drawing),
                 5,
                 P.screen_c,
                 flip_x=P.flip_x)
            flip()
            start = time.time()
            while time.time() - start < P.feedback_duration / 1000.0:
                ui_request()
示例#15
0
    def slide(self):
        show_mouse_cursor()
        self.blit()

        dragging = False
        while True:
            if not dragging:
                m_pos = mouse_pos()
                for event in pump(True):
                    if event.type == sdl2.SDL_KEYDOWN:
                        ui_request(event.key.keysym)
                    elif event.type in (sdl2.SDL_MOUSEBUTTONDOWN,
                                        sdl2.SDL_MOUSEBUTTONUP):
                        within_button = self.within_boundary("button", m_pos)
                        if self.button_active and within_button:
                            return self.response
                dragging = self.within_boundary("handle", m_pos)
            if dragging:
                button_up = False
                off_handle = False
                for event in pump(True):
                    if event.type == sdl2.SDL_KEYDOWN:
                        ui_request(event.key.keysym)
                    elif event.type == sdl2.SDL_MOUSEBUTTONUP:
                        button_up = True

                off_handle = not self.within_boundary("handle", mouse_pos())

                if off_handle or button_up:
                    dragging = False
                    self.response = self.handle_value()
                    self.button_active = True
                    flush()
                    return -1
                self.handle_pos = mouse_pos()[0]
                self.blit()
        return False
示例#16
0
    def block(self):

        block_num = P.block_number
        block_count = P.blocks_per_experiment

        # Display progress messages at start of blocks
        if block_num > 1:
            flush()
            fill()
            block_msg = "Completed block {0} of {1}. Press any key to continue."
            block_msg = block_msg.format(block_num - 1, block_count)
            message(block_msg, registration=5, location=P.screen_c)
            flip()
            any_key()

        # When running participants, notify researcher at halfway point & last block via Slack
        if P.slack_messaging and not P.development_mode:
            if block_num == (
                (block_count + 1) / 2) + 1:  # If participant is halfway done
                slack_message("Halfway done ({0}/{1})".format(
                    block_num, block_count))
            elif block_num == block_count:  # If participant is on last block
                slack_message("On last block ({0}/{1})".format(
                    block_num, block_count))
示例#17
0
    def instructions(self):

        p1 = (
            "During this task, you will presented with a sequence of numbers in the middle of "
            "the screen.\n\nPress any key to see an example.")
        p2 = (
            "Your task will be to press the [space] key as quickly as possible whenever a "
            "number other than {0} appears, and to withhold your response whenever the number "
            "is {0}.\n\nPress any key to continue.".format(P.target))
        p3 = (
            "Occasionally, the task will be interrupted by screens asking you about your "
            "focus just prior.\nWhen this happens, please select the most accurate "
            "response using the mouse cursor.\n\nPress any key to see an example."
        )
        p4 = (
            "After responding, you will be asked how much effort you "
            "were putting into staying focused on the task before the interruption.\n\n"
            "When this happens, please answer using the scale from 0% to 100% that will appear "
            "on screen. Your responses will be anonymous so please answer honestly.\n\n"
            "Press any key to see an example.")

        msg1 = message(p1, 'normal', blit_txt=False, align='center')
        msg2 = message(p2,
                       'normal',
                       blit_txt=False,
                       align='center',
                       wrap_width=int(P.screen_x * 0.8))
        msg3 = message(p3,
                       'normal',
                       blit_txt=False,
                       align='center',
                       wrap_width=int(P.screen_x * 0.8))
        msg4 = message(p4,
                       'normal',
                       blit_txt=False,
                       align='center',
                       wrap_width=int(P.screen_x * 0.8))

        # First page of instructions

        flush()
        fill()
        blit(msg1, 5, P.screen_c)
        flip()
        any_key(allow_mouse_click=False)

        # Example stimuli

        numlist = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        random.shuffle(numlist)
        for n in numlist[1:5]:
            trialtime = CountDown(1.125)
            numsize = random.choice(self.sizes)
            while trialtime.counting():
                ui_request()
                fill()
                mask_on = trialtime.elapsed() > 0.25
                if mask_on:
                    blit(self.mask_x, 5, P.screen_c)
                    blit(self.mask_ring, 5, P.screen_c)
                else:
                    blit(self.digits[n][numsize], 5, P.screen_c)
                flip()

        # Task explanation/illustration

        flush()
        fill()
        blit(msg2, 5, P.screen_c)
        flip()
        any_key(allow_mouse_click=False)

        # Probe explanation + example probe

        fill()
        blit(msg3, 5, P.screen_c)
        flip()
        any_key(allow_mouse_click=False)
        self.probe.collect()

        # Slider explanation + example slider

        fill()
        blit(msg4, 5, P.screen_c)
        flip()
        any_key(allow_mouse_click=False)
        self.get_effort()
示例#18
0
    def trial(self):

        while self.evm.before("cross fix end"):
            self.jc_wait_time()
            self.display_refresh(self.start_axis, self.cross_w)

        while self.evm.before("circle fix end"):
            self.jc_wait_time()
            self.display_refresh(self.start_axis, self.circle)

        while self.evm.before("cue end"):
            self.jc_wait_time()
            self.display_refresh(self.start_axis,
                                 self.circle,
                                 cue=self.cue_location)

        while self.evm.before("circle box end"):
            self.jc_wait_time()
            self.display_refresh(self.start_axis, self.circle)

        current_frame = 0
        while self.evm.before("animation end"):
            self.jc_wait_time()
            if self.animation_trial:
                if current_frame < self.animation_frames:
                    if self.evm.trial_time_ms > (
                            current_frame * self.frame_duration + 1600):
                        box_locs = self.frames[self.start_axis][
                            self.rotation_dir][current_frame]
                        self.display_refresh(box_locs, self.asterisk)
                        current_frame += 1
            else:
                self.display_refresh(self.start_axis, self.asterisk)

        while self.evm.before("asterisk end"):
            self.display_refresh(self.box_axis_during_target(), self.circle)
            self.jc_wait_time()

        flush()
        self.display_refresh(self.box_axis_during_target(),
                             self.circle,
                             target=self.target_location)

        if P.saccade_response_cond:
            self.jc_saccade_data()
            keypress_rt = NA

        if P.keypress_response_cond:
            self.rc.collect()
            keypress_rt = self.rc.keypress_listener.response(rt=True,
                                                             value=False)

        clear()
        smart_sleep(1000)

        if P.keypress_response_cond:
            if self.target_location == "none" and keypress_rt != TIMEOUT:
                fill()
                message(self.err_msgs['early'],
                        registration=5,
                        location=P.screen_c)
                flip()
                any_key()
            elif self.moved_eyes_during_rc:
                fill()
                message("Moved eyes during response interval!",
                        registration=5,
                        location=P.screen_c)
                flip()
                any_key()

        return {
            "block_num":
            P.block_number,
            "trial_num":
            P.trial_number,
            "session_type":
            'saccade' if P.saccade_response_cond else 'keypress',
            "cue_location":
            self.cue_location,
            "target_location":
            self.target_location,
            "start_axis":
            self.start_axis,
            "box_rotation":
            self.rotation_dir if self.animation_trial else NA,
            "animation_trial":
            str(self.animation_trial).upper(),
            "target_acquired":
            str(self.target_acquired).upper()
            if P.saccade_response_cond else NA,
            "keypress_rt":
            keypress_rt,
            "moved_eyes":
            str(self.moved_eyes_during_rc).upper()
            if P.keypress_response_cond else NA
        }
示例#19
0
def query(query_ob, anonymous=False):
    '''Asks the user a question and collects the response via the keyboard. Intended for use with
	the queries contained within a project's user_queries.json file. This function is used
	internally for collecting demographics at the start of each run, but can also be used during
	experiment runtime to collect info from participants based on the queries contained in the
	"experimental" section of the user_queries.json file.

	Args:
		query_ob (:class:`~klibs.KLJSON_Object.AttributeDict`): The object containing the query
			to present. See :obj:`~klibs.KLCommunication.user_queries` for more information.
		anonymous (bool, optional): If True, will immediately return the query object's anonymous
			value without prompting the user (used interally for P.development_mode). Defaults to
			False.
	
	Returns:
		The response to the query, coerced to the type specified by query_ob.format.type (can be
		str, int, float, bool, or None).
	
	Raises:
		ValueError: If the query object's type is not one of "str", "int", "float", "bool", or None,
			or if a query_ob.format.range value is given and the type is not "int" or "float".
		TypeError: If query_ob.accepted is specified and it is not a list of values, or if a
			query_ob.format.range is specified and it is not a two-item list.
			
	'''
    from klibs.KLEnvironment import txtm

    if anonymous:
        try:
            # Check if anon value is an EVAL statement, and if so evaluate it
            eval_statement = re.match(re.compile(u"^EVAL:[ ]*(.*)$"),
                                      query_ob.anonymous_value)
            if eval_statement:
                query_ob.anonymous_value = eval(eval_statement.group(1))
        except TypeError:
            pass
        return query_ob.anonymous_value

    f = query_ob.format
    if f.type not in ("int", "float", "str", "bool", None):
        err = "Invalid data type for query '{0}': {1}".format(
            query_ob.title, f.type)
        raise ValueError(err)

    # Set defaults for styles and positioning if not specified
    if f.styles == 'default':
        f.styles = AttributeDict({
            'query': 'default',
            'input': 'default',
            'error': 'alert'
        })

    locations = AttributeDict({
        'query': AUTO_POS,
        'input': AUTO_POS,
        'error': AUTO_POS
    })
    registrations = AttributeDict({
        'query': AUTO_POS,
        'input': AUTO_POS,
        'error': AUTO_POS
    })
    if f.positions == 'default':
        f.positions = AttributeDict({
            'locations': locations,
            'registrations': registrations
        })
    else:
        if f.positions.locations == 'default':
            f.positions.locations = locations
        if f.positions.registrations == 'default':
            f.positions.registrations = registrations

    q_text = message(query_ob.query,
                     f.styles.query,
                     align='center',
                     blit_txt=False)

    # address automatic positioning
    p = f.positions
    if p.locations.query == AUTO_POS:
        p.locations.query = [P.screen_c[0], int(0.1 * P.screen_y)]
        p.registrations.query = BL_CENTER
    for k in ['input', 'error']:
        if p.locations[k] == AUTO_POS:
            v_pad = q_text.height + 2 * txtm.styles[f.styles.query].line_height
            p.locations[k] = [P.screen_c[0], p.locations.query[1] + v_pad]
            p.registrations[k] = BL_CENTER

    # Create an informative error message for invalid responses
    accepted_responses = query_ob.accepted  # for code readability
    try:
        if accepted_responses:
            try:
                iter(accepted_responses)
                accepted_str = pretty_list(accepted_responses)
                invalid_answer_str = default_strings['invalid_answer'].format(
                    accepted_str)
            except:
                raise TypeError(
                    "The 'accepted' key of a question must be a list of values."
                )
        elif f.range:
            if f.type not in ("int", "float"):
                raise ValueError(
                    "Only queries with numeric types can use the range parameter."
                )
            elif isinstance(f.range, list) == False or len(f.range) != 2:
                raise TypeError(
                    "Query ranges must be two-item lists, containing an upper bound "
                    "and a lower bound.")
            try:
                template = default_strings['out_of_range']
            except KeyError:
                template = "Your answer must be a number between {0} and {1}, inclusive."
            invalid_answer_str = template.format(f.range[0], f.range[1])
    except:
        cso("\n<red>Error encountered while parsing query '{0}':</red>".format(
            query_ob.title))
        raise

    # user input loop; exited by breaking
    input_string = u''  # populated in loop below
    error_string = None
    user_finished = False

    # Clear event queue and draw query text to screen before entering input loop
    flush()
    SDL_StartTextInput()
    fill()
    blit(q_text, p.registrations.query, p.locations.query)
    flip()

    while not user_finished:
        for event in pump(True):

            if event.type == SDL_KEYDOWN:

                error_string = None  # clear error string (if any) on new key event
                ui_request(event.key.keysym)
                sdl_keysym = event.key.keysym.sym

                if sdl_keysym == SDLK_ESCAPE:
                    # Esc clears any existing input
                    input_string = ""

                elif sdl_keysym == SDLK_BACKSPACE:
                    # Backspace removes last character from input
                    input_string = input_string[:-1]

                elif sdl_keysym in (SDLK_KP_ENTER, SDLK_RETURN):
                    # Enter or Return check if a valid response has been made and end loop if it has
                    if len(input_string) > 0:
                        response = input_string
                        # If type is 'int' or 'float', make sure input can be converted to that type
                        if f.type == "int":
                            try:
                                response = int(input_string)
                            except ValueError:
                                error_string = "Please respond with an integer."
                        elif f.type == "float":
                            try:
                                response = float(input_string)
                            except ValueError:
                                error_string = "Please respond with a number."
                        # If no errors yet, check input against list of accepted values (if q has one)
                        if not error_string:
                            if accepted_responses:
                                user_finished = response in accepted_responses
                                if not user_finished:
                                    error_string = invalid_answer_str
                            elif f.range:
                                user_finished = (f.range[0] <= response <=
                                                 f.range[1])
                                if not user_finished:
                                    error_string = invalid_answer_str
                            else:
                                user_finished = True
                    elif query_ob.allow_null is True:
                        user_finished = True
                    else:
                        # If no input and allow_null is false, display error
                        error_string = default_strings['answer_not_supplied']

            elif event.type == SDL_TEXTINPUT:

                input_string += event.text.text.decode('utf-8')
                if f.case_sensitive is False:
                    input_string = input_string.lower()
                input_string = input_string.strip(
                )  # remove any trailing whitespace

            else:
                continue

            # If any text entered or error message encountered, render text for drawing
            if error_string:
                rendered_input = message(error_string,
                                         f.styles.error,
                                         blit_txt=False)
                input_string = ""
            elif len(input_string):
                if f.password:
                    rendered_input = message(len(input_string) * '*',
                                             f.styles.input,
                                             blit_txt=False)
                else:
                    rendered_input = message(input_string,
                                             f.styles.input,
                                             blit_txt=False)
            else:
                rendered_input = None

            # Draw question and any entered response to screen
            fill()
            blit(q_text, p.registrations.query, p.locations.query)
            if rendered_input:
                loc = p.locations.error if error_string else p.locations.input
                reg = p.registrations.error if error_string else p.registrations.input
                blit(rendered_input, reg, loc)
            flip()

    # Once a valid response has been made, clear the screen
    fill()
    flip()
    SDL_StopTextInput()

    if query_ob.allow_null and len(input_string) == 0:
        return None
    elif f.type == "int":
        return int(input_string)
    elif f.type == "str":
        if f.action == QUERY_ACTION_HASH:
            return make_hash(input_string)
        elif f.action == QUERY_ACTION_UPPERCASE:
            return utf8(input_string).upper()
        else:
            return utf8(input_string)
    elif f.type == "float":
        return float(input_string)
    elif f.type == "bool":
        return input_string in f.accept_as_true
    else:
        return input_string
示例#20
0
    def instructions(self):

        p1 = (
            "During this task, you will presented with a sequence of letters in the middle of "
            "the screen.\n\nPress any key to see an example.")
        p2a = (
            "For some blocks, your task will be to indicate whether each letter matches "
            "the letter just before it:")
        p2b = (
            "For others, your task will be to indicate whether each letter matches "
            "the letter two before it:")
        p2c = "(matches for each block type are highlighted)"
        p3 = (
            "Occasionally, the task will be interrupted by screens asking you about your "
            "focus just prior.\nWhen this happens, please select the most accurate "
            "response using the mouse cursor.\n\nPress any key to see an example."
        )

        msg1 = message(p1, 'normal', blit_txt=False, align='center')
        msg2a = message(p2a,
                        'normal',
                        blit_txt=False,
                        wrap_width=int(P.screen_x * 0.8))
        msg2b = message(p2b,
                        'normal',
                        blit_txt=False,
                        wrap_width=int(P.screen_x * 0.8))
        msg2c = message(p2c, 'normal', blit_txt=False)
        msg3 = message(p3,
                       'normal',
                       blit_txt=False,
                       align='center',
                       wrap_width=int(P.screen_x * 0.8))

        # First page of instructions

        flush()
        fill()
        blit(msg1, 5, P.screen_c)
        flip()
        any_key(allow_mouse_click=False)

        # Example stimuli

        for letter in (1, 2, 1):
            trialtime = CountDown(2.5)
            while trialtime.counting():
                mask_on = trialtime.elapsed() > 0.5
                stim = self.mask if mask_on else self.letters[
                    self.letter_set[letter]]
                ui_request()
                fill()
                blit(self.accuracy_rect, 5, P.screen_c)
                blit(self.accuracy_mask, 5, P.screen_c)
                blit(stim, 5, P.screen_c)
                flip()

        # Task explanation/illustration

        fill()
        blit(msg2a, 8, (P.screen_c[0], int(P.screen_y * 0.15)))
        blit(msg2b, 8, (P.screen_c[0], int(P.screen_y * 0.45)))
        blit(msg2c, 2, (P.screen_c[0], int(P.screen_y * 0.8)))
        self.draw_nback_illustration(int(P.screen_y * 0.3), target=5)
        self.draw_nback_illustration(int(P.screen_y * 0.6), target=4)
        flip()
        any_key(allow_mouse_click=False)

        # Probe explanation + example probe

        fill()
        blit(msg3, 5, P.screen_c)
        flip()
        any_key()
        self.probe.collect()
示例#21
0
    def trial(self):

        tone_played = False

        while self.evm.before('target_on', pump_events=True):
            fill()
            blit(self.fixation, 5, P.screen_c)
            if self.evm.between('cue_on',
                                'cue_off') and self.cue_location != None:
                loc = self.cue_locations[self.cue_location]
                blit(self.cue, 5, loc)
            if self.tone_trial and self.evm.after('tone_on'):
                if not tone_played:
                    self.warning_tone.play()
                    tone_played = True
            flip()

        if self.trial_type in ['ANTI', 'EV']:
            fill()
            blit(self.fixation, 5, P.screen_c)
            for shape, loc in self.arrows:
                blit(shape, 5, loc)
            flip()
        self.rc.collect()

        # Get response data and preprocess it before logging to database
        response, rt = self.rc.keypress_listener.response()
        if rt == klibs.TIMEOUT:
            response = 'NA'

        # if ANT trial, determine absolute diff. between y of central arrow and nearest flanker
        if self.trial_type != 'AV':
            ylocs = [arrow[1][1] for arrow in self.arrows]
            l_diff = abs(ylocs[2] - ylocs[1])
            r_diff = abs(ylocs[2] - ylocs[3])
            abs_diff = px_to_deg(min([l_diff, r_diff]))
        else:
            abs_diff = 'NA'

        if self.trial_type == 'ANTI':
            self.ev_offset = 'NA'
            accuracy = int(response == self.target_direction)
        elif self.trial_type == 'EV':
            accuracy = int(response == 'detection')
        elif self.trial_type == 'AV':
            self.ev_offset = 'NA'
            self.target_direction = 'NA'
            self.target_location = 'NA'
            self.congruent = 'NA'
            accuracy = int(response == 'detection')

        if response == 'NA':
            accuracy = 'NA'

        if P.practicing and accuracy is not 1:
            # If on a practice block, show feedback if an incorrect response is made.
            if accuracy is 0:
                feedback = "Incorrect response!\n"
                if 'ANTI' in self.trial_type:
                    feedback += "Please press 'c' for left arrows and 'm' for right arrows."
                elif 'EV' in self.trial_type:
                    feedback += "Please press the space bar for displaced arrows."
                else:
                    feedback += "Please press the space bar for countdown timers."
            else:
                feedback = (
                    "No valid response made!\n"
                    "Please press 'c' for left arrows, 'm' for right arrows, "
                    "and space for displaced arrows or countdowns.")

            feedback_msg = message(feedback,
                                   'block_msg',
                                   blit_txt=False,
                                   align='center')
            fill()
            blit(feedback_msg, 5, P.screen_c)
            flip()
            flush()
            any_key()
            # After feedback acknowledged, return to fixation screen
            fill()
            blit(self.fixation, 5, P.screen_c)
            flip()

        else:
            while self.evm.before('trial_end', pump_events=True):
                fill()
                blit(self.fixation, 5, P.screen_c)
                flip()

        return {
            "session_num": P.session_number,
            "block_num": P.block_number,
            "trial_num": P.trial_number,
            "trial_type": self.trial_type,
            "warning_tone": self.tone_trial,
            "cue_type": self.cue_type,
            "target_location": self.target_location,
            "target_direction": self.target_direction,
            "congruent": self.congruent,
            "displacement": self.ev_offset,
            "abs_displacement":
            abs_diff,  # diff. in dva between middle arrow and nearest flanker
            "response": response,
            "accuracy": accuracy,
            "rt": rt
        }