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