示例#1
0
def get_movement(touchpad):

    raw_input("Hit <ENTER> before you start moving")
    print("OK, go now")

    #-- Wait until touchpad is touched
    while not touchpad.get_touch_data().touched:
        time.sleep(0.01)

    start_time = u.get_time()

    x = []
    y = []

    #-- Get movement data
    while True:
        td = touchpad.get_touch_data()
        if not td.touched:
            break

        x.append(td.x)
        y.append(td.y)

    movement_time = u.get_time() - start_time

    return movement_time, x, y
示例#2
0
def initialize_trial(exp_info, trial):
    """
    Initialize a trial
    
    :type exp_info: trajtracker.paradigms.num2pos.ExperimentInfo 
    :type trial: trajtracker.paradigms.num2pos.TrialInfo 
    """

    exp_info.start_point.reset()
    exp_info.numberline.reset()  # mark the line as yet-untouched

    exp_info.stimuli.present()  # reset the display

    common.update_text_target_for_trial(exp_info,
                                        trial,
                                        use_numeric_target_as_default=True)
    common.update_generic_target_for_trial(exp_info, trial)
    if exp_info.fixation is not None:
        common.update_fixation_for_trial(exp_info, trial)

    exp_info.numberline.target = trial.target

    exp_info.event_manager.dispatch_event(
        ttrk.events.TRIAL_INITIALIZED, 0,
        u.get_time() - exp_info.session_start_time)

    #-- Update the display to present stuff that may have been added by the TRIAL_INITIALIZED event listeners
    exp_info.stimuli.present()

    if exp_info.config.stimulus_then_move:
        trial.targets_t0 = u.get_time() - trial.start_time
示例#3
0
def initialize_trial(exp_info, trial):
    """
    Initialize a trial

    :type exp_info: trajtracker.paradigms.dchoice.ExperimentInfo 
    :type trial: trajtracker.paradigms.dchoice.TrialInfo 
    """

    exp_info.start_point.reset()
    for hotspot in exp_info.response_hotspots:
        hotspot.reset()

    #-- Reset the display for this trial
    exp_info.stimuli.present()

    common.update_text_target_for_trial(exp_info, trial)
    common.update_generic_target_for_trial(exp_info, trial)

    if exp_info.left_resp_text is not None:
        update_response_for_trial(exp_info, trial)

    if exp_info.fixation is not None:
        common.update_fixation_for_trial(exp_info, trial)

    exp_info.event_manager.dispatch_event(
        ttrk.events.TRIAL_INITIALIZED, 0,
        u.get_time() - exp_info.session_start_time)

    # -- Update the display to present stuff that may have been added by the TRIAL_INITIALIZED event listeners
    exp_info.stimuli.present()

    if exp_info.config.stimulus_then_move:
        trial.targets_t0 = u.get_time() - trial.start_time
示例#4
0
def trial_failed_common(err, exp_info, trial):
    """
    Called when the trial failed for any reason 
    (only when a strict error occurred; pointing at an incorrect location does not count as failure) 

    :type err: ExperimentError
    :type exp_info: trajtracker.paradigms.common.BaseExperimentInfo
    :type trial: trajtracker.paradigms.common.BaseTrialInfo 
    """

    ttrk.log_write("ERROR in trial ({:}). Message shown to subject: {:}".format(err.err_code, err.message))

    curr_time = u.get_time()

    trial.duration = curr_time - trial.start_time
    time_in_session = curr_time - exp_info.session_start_time

    if not trial.stopped_moving_event_dispatched:
        exp_info.event_manager.dispatch_event(FINGER_STOPPED_MOVING, trial.duration, time_in_session)
        trial.stopped_moving_event_dispatched = True

    exp_info.event_manager.dispatch_event(ttrk.events.TRIAL_FAILED, trial.duration, time_in_session)

    exp_info.errmsg_textbox.unload()
    exp_info.errmsg_textbox.text = err.message
    exp_info.errmsg_textbox.visible = True

    exp_info.sound_err.play()
示例#5
0
def on_finger_started_moving(exp_info, trial):
    """
    This function should be called when the finger leaves the "start" area and starts moving

    :type exp_info: trajtracker.paradigms.common.BaseExperimentInfo
    :type trial: trajtracker.paradigms.common.BaseTrialInfo 
    """

    t = u.get_time()
    time_in_trial = t - trial.start_time
    time_in_session = t - exp_info.session_start_time
    trial.time_started_moving = time_in_trial

    #-- This event is dispatched before calling present(), because it might trigger operations that
    #-- show/hide stuff
    exp_info.event_manager.dispatch_event(FINGER_STARTED_MOVING, time_in_trial, time_in_session)

    exp_info.stimuli.present()

    if not exp_info.config.stimulus_then_move:
        trial.targets_t0 = u.get_time() - trial.start_time
示例#6
0
def wait_until_finger_moves(exp_info, trial):
    """
    The function returns after the finger started moving (or on error)

    :type exp_info: trajtracker.paradigms.num2pos.ExperimentInfo
    :type trial: trajtracker.paradigms.common.BaseTrialInfo 

    :return: None if all OK; if trial should terminate, a tuple with two values:
             (1) RunTrialResult.xxx (2) An ExperimentError object
    """

    # -- Wait for the participant to start moving the finger
    if exp_info.config.is_fixation_zoom:
        # noinspection PyUnusedLocal
        def on_loop_callback(time_in_trial, time_in_session):
            exp_info.fixation.update_xyt(time_in_session=time_in_session)
            return update_movement_in_traj_sensitive_objects(exp_info, trial, False)
    else:
        # noinspection PyUnusedLocal
        def on_loop_callback(time_in_trial, time_in_session):
            return update_movement_in_traj_sensitive_objects(exp_info, trial, False)

    err = exp_info.start_point.wait_until_exit(exp_info.xpy_exp,
                                               on_loop_present=exp_info.stimuli,
                                               on_loop_callback=on_loop_callback,
                                               event_manager=exp_info.event_manager,
                                               trial_start_time=trial.start_time,
                                               session_start_time=exp_info.session_start_time,
                                               max_wait_time=trial.finger_moves_max_time)
    if err is not None:
        return RunTrialResult.Failed, err

    if exp_info.start_point.state == StartPoint.State.aborted:
        #-- Finger lifted
        show_fixation(exp_info, False)
        return RunTrialResult.Aborted, None

    elif exp_info.start_point.state == StartPoint.State.error:
        #-- Invalid start direction
        return RunTrialResult.Failed, ExperimentError("StartedSideways", "Start the trial by moving straight, not sideways!")

    elif exp_info.start_point.state == StartPoint.State.timeout:
        #-- Finger moved too late
        return RunTrialResult.Failed, ExperimentError("FingerMovedTooLate", "You moved too late")

    if trial.finger_moves_min_time is not None and u.get_time() - trial.start_time < trial.finger_moves_min_time:
        #-- Finger moved too early
        return RunTrialResult.Failed, ExperimentError("FingerMovedTooEarly", "You moved too early")

    on_finger_started_moving(exp_info, trial)

    return None
示例#7
0
def on_finger_touched_screen(exp_info, trial):
    """
    This function should be called when the finger touches the screen

    :type exp_info: trajtracker.paradigms.common.BaseExperimentInfo
    :type trial: trajtracker.paradigms.common.BaseTrialInfo
    """

    exp_info.errmsg_textbox.visible = False

    show_fixation(exp_info)

    exp_info.event_manager.dispatch_event(ttrk.events.TRIAL_STARTED, 0,
                                          u.get_time() - exp_info.session_start_time)

    exp_info.stimuli.present()

    trial.start_time = u.get_time()

    #-- Reset all trajectory-sensitive objects
    for obj in exp_info.trajectory_sensitive_objects + exp_info.touch_sensitive_objects:
        obj.reset(0)
示例#8
0
def trial_succeeded_common(exp_info, trial):
    """
    Called when the trial succeeded

    :type exp_info: trajtracker.paradigms.common.BaseExperimentInfo
    :type trial: trajtracker.paradigms.common.BaseTrialInfo
    """

    ttrk.log_write("Trial ended successfully")

    curr_time = u.get_time()
    trial.duration = curr_time - trial.start_time
    time_in_session = curr_time - exp_info.session_start_time
    exp_info.event_manager.dispatch_event(ttrk.events.TRIAL_SUCCEEDED, trial.duration, time_in_session)
示例#9
0
def init_experiment(exp_info):
    """
    Initialize the experiment environment

    :type exp_info: trajtrackerp.common.BaseExperimentInfo
    """

    exp_info.session_start_localtime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))

    # noinspection PyUnresolvedReferences
    if exp_info.config.shuffle_trials:
        random.shuffle(exp_info.trials)

    exp_info.trajtracker.init_output_file()

    exp_info.session_start_time = u.get_time()
示例#10
0
def update_movement_in_traj_sensitive_objects(exp_info, trial, within_movement_time=True):
    """
    Update the trajectory-sensitive objects about the mouse/finger movement

    :type exp_info: trajtracker.paradigms.common.BaseExperimentInfo
    :type trial: trajtracker.paradigms.common.BaseTrialInfo
    :param within_movement_time: Indicates whether the this is currently the finger's movement time
                   (between start of detected movement and a response being made). The function may also be called
                   outside this time interval.
    :return: None if all is OK; or an ExperimentError object if one of the validators issued an error
    """

    curr_time = u.get_time()
    clicked = ttrk.env.mouse.check_button_pressed(0)
    position = ttrk.env.mouse.position

    time_in_trial = curr_time - trial.start_time
    time_in_session = curr_time - exp_info.session_start_time

    if not within_movement_time:
        #-- Only recording finger trajectory
        err = exp_info.trajtracker.update_xyt(position, time_in_trial, time_in_session)
        if err is not None:
            return err
        return None

    #-- Invoke all trajectory/touch-sensitive objects

    for obj in exp_info.touch_sensitive_objects:
        err = obj.update_touching(clicked, time_in_trial, time_in_session)
        if err is not None:
            return err

    if clicked:
        for obj in exp_info.trajectory_sensitive_objects:
            err = obj.update_xyt(position, time_in_trial, time_in_session)
            if err is not None:
                return err

    exp_info.event_manager.on_frame(time_in_trial, time_in_session)

    return None
示例#11
0
def run_trial(exp_info, trial, trial_already_initiated):
    """
    Run a single trial
    
    :type exp_info: trajtracker.paradigms.num2pos.ExperimentInfo 
    :type trial: trajtracker.paradigms.num2pos.TrialInfo
    :param trial_already_initiated: Indicates if the "start" point was already touched
    
    :return: RunTrialResult
    """

    config = exp_info.config

    initialize_trial(exp_info, trial)

    if trial_already_initiated:
        exp_info.start_point.mark_as_initialized()

    else:
        exp_info.start_point.wait_until_startpoint_touched(
            exp_info.xpy_exp,
            on_loop_present=exp_info.stimuli,
            event_manager=exp_info.event_manager,
            trial_start_time=trial.start_time,
            session_start_time=exp_info.session_start_time)
    on_finger_touched_screen(exp_info, trial)

    rc = common.wait_until_finger_moves(exp_info, trial)
    if rc is not None:
        if rc[1] is not None:
            trial_failed(rc[1], exp_info, trial)
        return rc[0]

    nl = exp_info.numberline
    time_response_made = None

    while True:  # This loop runs once per frame

        curr_time = u.get_time()

        #-- Inform relevant objects (validators, trajectory tracker, event manager, etc.) of the progress
        err = common.update_movement_in_traj_sensitive_objects(exp_info, trial)
        if err is not None:
            trial_failed(err, exp_info, trial)
            return RunTrialResult.Failed

        #-- Check if the number line was reached
        if nl.touched and time_response_made is None:

            time_response_made = curr_time
            common.on_response_made(exp_info, trial, curr_time)

            #-- Validate that the response wasn't too far off the number line's ends
            max_excess = exp_info.config.max_response_excess
            if max_excess is not None and (nl.response_value < nl.min_value or
                                           nl.response_value > nl.max_value):
                excess = (nl.min_value - nl.response_value) if (nl.response_value < nl.min_value) \
                    else (nl.response_value - nl.max_value)
                excess /= (nl.max_value - nl.min_value)
                if excess > max_excess:
                    trial_failed(
                        ExperimentError("ResponseTooFar",
                                        "Please point at the number line"),
                        exp_info, trial)
                    return RunTrialResult.Failed

            #-- Validate that the response wasn't too fast
            min_movement_time = trial.csv_data['min_movement_time'] if (
                'min_movement_time'
                in trial.csv_data) else exp_info.config.min_movement_time
            if trial.movement_time < min_movement_time:
                trial_failed(
                    ExperimentError(
                        ttrk.validators.InstantaneousSpeedValidator.
                        err_too_fast, "Please move more slowly"), exp_info,
                    trial)
                return RunTrialResult.Failed

            play_success_sound(exp_info, trial)

            if exp_info.config.post_response_target:
                exp_info.numberline.show_target_pointer_on(trial.target)

        #-- Successful end-of-trial conditions
        if time_response_made is not None:

            time_in_trial = curr_time - trial.start_time

            if not ttrk.env.mouse.check_button_pressed(0):
                #-- Finger was lifted
                trial.time_finger_lifted = time_in_trial
                exp_info.event_manager.dispatch_event(
                    FINGER_LIFTED, time_in_trial,
                    curr_time - exp_info.session_start_time)
                break

            if curr_time > time_response_made + config.max_post_response_record_duration:
                #-- post-response duration has expired
                break

        xpy.io.Keyboard.process_control_keys()

        #-- Update all displayable elements.
        #-- This is done when the loop ends, not when it starts, because there was another present()
        #-- call just before the loop, inside wait_until_finger_moves()
        exp_info.stimuli.present()

    #-- Main task ended successfully

    #-- Optionally, run additional stages
    run_trial_result = common.run_post_trial_operations(exp_info, trial)
    if run_trial_result in (RunTrialResult.Succeeded,
                            RunTrialResult.SucceededAndProceed):
        trial_succeeded(exp_info, trial)

    return run_trial_result
示例#12
0
    def wait_until_exit(self,
                        exp,
                        on_loop_callback=None,
                        on_loop_present=None,
                        event_manager=None,
                        trial_start_time=None,
                        session_start_time=None,
                        max_wait_time=None):
        """
        Wait until the finger leaves the starting area
    
        The *on_loop_xxx* and *event_manager* parameters define what to do on each iteration of the loop that  
        waits for the area to be touched. 
        If neither on_loop_callback nor on_loop_present are provided, the function will wait for 15 ms 
        on each loop iteration.
        
        If several on_loop parameters are provided, they will be invoked in this order:
        *callback - event manager.on_frame() - present()*.
        
        :param exp: The Expyriment experiment object
        :param on_loop_callback: A function to call on each loop iteration. 
                                 If the function returns any value other than *None*, the waiting will
                                 be terminated and that value will be returned.
                                 The function gets 2 arguments: time_in_trial, time_in_session
        :param on_loop_present: A visual object that will be present()ed on each loop iteration.
        :param event_manager: The event manager's on_frame() will be called on each loop iteration.
                              If you provide an event manager, you also have to provide trial_start_time and
                              session_start_time (whose values were obtained by :func:`trajtracker.utils.get_time` 
        :param max_wait_time: Maximal time (in seconds) to wait
        :return: The value returned by the on_loop_callback function (in case it returned anything other than None).
                 Otherwise the function returns None. Use :attr:`~trajtracker.movement.StartPoint.state` to
                 learn about the StartPoint's exit status.
        """

        self._log_func_enters(
            "wait_until_exit",
            ["exp", on_loop_callback, on_loop_present, event_manager])

        _u.validate_func_arg_type(self,
                                  "wait_until_startpoint_touched",
                                  "max_wait_time",
                                  max_wait_time,
                                  numbers.Number,
                                  none_allowed=True)
        _u.validate_func_arg_not_negative(self,
                                          "wait_until_startpoint_touched",
                                          "max_wait_time", max_wait_time)
        if event_manager is not None:
            _u.validate_func_arg_type(self,
                                      "wait_until_startpoint_touched",
                                      "trial_start_time",
                                      trial_start_time,
                                      numbers.Number,
                                      none_allowed=True)
            _u.validate_func_arg_type(self, "wait_until_startpoint_touched",
                                      "session_start_time", session_start_time,
                                      numbers.Number)

        time_started_waiting = u.get_time()

        #-- Wait
        while self._state not in [
                StartPoint.State.start, StartPoint.State.error
        ]:

            curr_time = u.get_time()
            time_in_trial = None if trial_start_time is None else curr_time - trial_start_time
            time_in_session = None if session_start_time is None else curr_time - session_start_time

            if ttrk.env.mouse.check_button_pressed(0):
                #-- Finger still touching screen
                finger_pos = ttrk.env.mouse.position
                self.check_xy(finger_pos[0], finger_pos[1])
            else:
                #-- Finger lifted
                self._log_func_returns("wait_until_exit",
                                       StartPoint.State.aborted)
                self._state = StartPoint.State.aborted
                return None

            if max_wait_time is not None and u.get_time(
            ) - time_started_waiting >= max_wait_time:
                self._state = StartPoint.State.timeout
                self._log_func_returns("wait_until_exit",
                                       StartPoint.State.timeout)
                return None

            # Invoke custom operations on each loop iteration
            if on_loop_callback is not None:
                retval = on_loop_callback(time_in_trial, time_in_session)
                if retval is not None:
                    return retval

            if event_manager is not None:
                event_manager.on_frame(time_in_trial, time_in_session)

            if on_loop_present is not None:
                on_loop_present.present()

            if on_loop_present is None and on_loop_callback is None:
                exp.clock.wait(15)

            xpy.io.Keyboard.process_control_keys()

        self._log_func_returns("wait_until_exit", self._state)
        return None
示例#13
0
    def wait_until_startpoint_touched(self,
                                      exp,
                                      on_loop_callback=None,
                                      on_loop_present=None,
                                      event_manager=None,
                                      trial_start_time=None,
                                      session_start_time=None,
                                      max_wait_time=None):
        """
        Wait until the starting point is touched.
        
        The *on_loop_xxx* and *event_manager* parameters define what to do on each iteration of the loop that  
        waits for the area to be touched. 
        If neither on_loop_callback nor on_loop_present are provided, the function will wait for 15 ms 
        on each loop iteration.
        
        If several on_loop parameters are provided, they will be invoked in this order:
        *callback - event manager.on_frame() - present()*.
        
        :param exp: The Expyriment experiment object
        :param on_loop_callback: A function (without arguments) to call on each loop iteration.
                                If the function returns any value other than *None*, the waiting will
                                be terminated and that value will be returned.
        :param on_loop_present: A visual object that will be present()ed on each loop iteration.
        :param event_manager: The event manager's on_frame() will be called on each loop iteration.
                              If you provide an event manager, you also have to provide trial_start_time and
                              session_start_time (whose values were obtained by :func:`trajtracker.utils.get_time`
        :param max_wait_time: Maximal time (in seconds) to wait
        :return: The value returned by the on_loop_callback function (in case it returned anything other than None).
                 Otherwise the function returns None. Use :attr:`~trajtracker.movement.StartPoint.state` to
                 learn about the StartPoint's exit status.
        """

        self._log_func_enters(
            "wait_until_startpoint_touched",
            ["exp", on_loop_callback, on_loop_present, event_manager])

        _u.validate_func_arg_type(self,
                                  "wait_until_startpoint_touched",
                                  "max_wait_time",
                                  max_wait_time,
                                  numbers.Number,
                                  none_allowed=True)
        _u.validate_func_arg_not_negative(self,
                                          "wait_until_startpoint_touched",
                                          "max_wait_time", max_wait_time)
        if event_manager is not None:
            _u.validate_func_arg_type(self,
                                      "wait_until_startpoint_touched",
                                      "trial_start_time",
                                      trial_start_time,
                                      numbers.Number,
                                      none_allowed=True)
            _u.validate_func_arg_type(self, "wait_until_startpoint_touched",
                                      "session_start_time", session_start_time,
                                      numbers.Number)

        if self._state != StartPoint.State.reset:
            raise ttrk.InvalidStateError(
                "{:}.wait_until_startpoint_touched() was called without calling reset() first"
                .format(_u.get_type_name(self)))

        time_started_waiting = u.get_time()

        # The "StartPoint" object is expected to run through these states, in this order:
        # State.reset - after the trial initialized
        # State.mouse_up - after the mouse/finger was unclicked/lifted
        # State.init - when the screen was touched/clicked (this is when this function returns)

        while True:

            if not ttrk.env.mouse.check_button_pressed(
                    0) and self._state == StartPoint.State.reset:
                # Mouse/finger is UP
                self._state = StartPoint.State.mouse_up
                self._log_write_if(ttrk.log_debug,
                                   "Mouse unclicked. Setting state=mouse_up",
                                   True)

            elif ttrk.env.mouse.check_button_pressed(
                    0) and self._state == StartPoint.State.mouse_up:
                # Mouse/finger touched the screen
                finger_pos = ttrk.env.mouse.position
                self.check_xy(finger_pos[0], finger_pos[1])

            if max_wait_time is not None and u.get_time(
            ) - time_started_waiting >= max_wait_time:
                self._log_func_returns("wait_until_startpoint_touched", False)
                self._state = StartPoint.State.timeout
                return None

            if self._state == StartPoint.State.init:
                break  # Screen touched - we're done here

            # Invoke custom operations on each loop iteration

            if on_loop_callback is not None:
                retval = on_loop_callback()
                if retval is not None:
                    return retval

            if event_manager is not None:
                curr_time = u.get_time()
                event_manager.on_frame(
                    None if trial_start_time is None else curr_time -
                    trial_start_time, curr_time - session_start_time)

            if on_loop_present is not None:
                on_loop_present.present()

            if on_loop_present is None and on_loop_callback is None:
                exp.clock.wait(15)

            xpy.io.Keyboard.process_control_keys()

        self._log_func_returns("wait_until_startpoint_touched", True)
        return None
示例#14
0
def run_trial(exp_info, trial, trial_already_initiated):
    """
    Run a single trial

    :param exp_info:
    :type exp_info: trajtracker.paradigms.dchoice.ExperimentInfo
    
    :param trial:
    :type trial: trajtracker.paradigms.dchoice.TrialInfo
    
    :param trial_already_initiated: Indicates if the "start" point was already touched

    :return: RunTrialResult
    """

    # Update response button texts for balance
    if 'left_resp_btn.text' in trial.csv_data.keys():

        ## In IAT paradigm split the response button text into a list
        ## and update the button texts with the list items
        if exp_info.config.multiple_response:

            left_resp_list = trial.csv_data['left_resp_btn.text'].split(';')
            right_resp_list = trial.csv_data['right_resp_btn.text'].split(';')

            exp_info.response_buttons[0].text = left_resp_list[0]
            exp_info.response_buttons[1].text = left_resp_list[1]
            exp_info.response_buttons[2].text = right_resp_list[0]
            exp_info.response_buttons[3].text = right_resp_list[1]

        else:
            exp_info.response_buttons[0].text = trial.csv_data[
                'left_resp_btn.text']
            exp_info.response_buttons[1].text = trial.csv_data[
                'right_resp_btn.text']

    # if 'left_resp_btn.text' in trial.csv_data.keys():
    #     exp_info.response_buttons[0].colour = trial.csv_data['left_resp_btn.colour']
    #     exp_info.response_buttons[1].colour = trial.csv_data['right_resp_btn.colour']

    config = exp_info.config

    initialize_trial(exp_info, trial)

    if trial_already_initiated:
        exp_info.start_point.mark_as_initialized()

    else:
        exp_info.start_point.wait_until_startpoint_touched(
            exp_info.xpy_exp,
            on_loop_present=exp_info.stimuli,
            event_manager=exp_info.event_manager,
            trial_start_time=trial.start_time,
            session_start_time=exp_info.session_start_time)

    hide_feedback_stimuli(exp_info)
    common.on_finger_touched_screen(exp_info, trial)

    rc = common.wait_until_finger_moves(exp_info, trial)
    if rc is not None:
        if rc[1] is not None:
            trial_failed(rc[1], exp_info, trial)
        return rc[0]

    time_response_made = None

    while True:  # This loop runs once per frame

        curr_time = u.get_time()

        #-- Inform relevant objects (validators, trajectory tracker, event manager, etc.) of the progress
        err = common.update_movement_in_traj_sensitive_objects(exp_info, trial)
        if err is not None:
            trial_failed(err, exp_info, trial)
            return RunTrialResult.Failed

        #-- Check if a response button was reached
        user_response = get_touched_button(exp_info)
        if user_response is not None and time_response_made is None:

            time_response_made = curr_time
            common.on_response_made(exp_info, trial, curr_time)

            min_movement_time = trial.csv_data['min_movement_time'] if (
                'min_movement_time'
                in trial.csv_data) else exp_info.config.min_movement_time
            if trial.movement_time < min_movement_time:
                trial_failed(
                    ExperimentError(
                        ttrk.validators.InstantaneousSpeedValidator.
                        err_too_fast, "Please move more slowly"), exp_info,
                    trial)
                return RunTrialResult.Failed

            exp_info.sounds_ok[0].play()
            trial.stopped_moving_event_dispatched = True

        #-- Successful end-of-trial conditions
        if time_response_made is not None:

            time_in_trial = curr_time - trial.start_time

            if not ttrk.env.mouse.check_button_pressed(0):
                #-- Finger was lifted
                trial.time_finger_lifted = time_in_trial
                exp_info.event_manager.dispatch_event(
                    FINGER_LIFTED, time_in_trial,
                    curr_time - exp_info.session_start_time)
                break

            if curr_time > time_response_made + config.max_post_response_record_duration:
                #-- post-response duration has expired
                break

        xpy.io.Keyboard.process_control_keys()

        #-- Update all displayable elements.
        #-- This is done when the loop ends, not when it starts, because there was another present()
        #-- call just before the loop, inside wait_until_finger_moves()
        exp_info.stimuli.present()

    #-- Main task ended successfully

    #-- Optionally, run additional stages
    run_trial_result = common.run_post_trial_operations(exp_info, trial)
    if run_trial_result in (RunTrialResult.Succeeded,
                            RunTrialResult.SucceededAndProceed):
        trial_succeeded(exp_info, trial, user_response)

    return run_trial_result