def __init__(self, timings=None, logger=None): """ State constructor Parameters ---------- :param timings: dictionary specifying states timings (in seconds) if duration is False, then the state machine will stay within the same state until told otherwise if duration is 0.0, then the state machine will immediately move to the next state if duration is float > 0.0, then the state machine will stay in the same state for the given duration :type timings: dict :param logger: application logger :type logger: logging.logger|CustomLogger """ self.__durations = timings if timings is not None else dict() self.__logger = logger self.__state = None self.__current = None self.__next_state = None self.__runtime = Timer() self.__runtime.start() self.__counter = 0 self._syncer = None self.__move_on_requested = False
def start(self): """ Start state :return: """ self.__timer = Timer(max_duration=self.__max_duration) self.__timer.start() self.__start_time = self.__timer.get_time('start') self.__running = True
class TimeUp(object): """ A simple Timeup animation """ def __init__(self, max_duration): self.__max_duration = max_duration self.__timer = Timer(max_duration=max_duration) @property def text(self): """ :rtype: str :return: """ if self.__timer.get_time('start') is None: self.__timer.start() return "Experiment will continue in {0:1.1f} s".format( self.__timer.countdown)
class Loading(object): """ A simple loading animation """ __dft_text = 'Loading ' __animation_style = '.' __animation = '' __msg = '' __n = 0 __dt = 0.500 __timer = Timer() @property def text(self): """ :rtype: str :return: """ if self.__timer.get_time('start') is None: self.__timer.start() if self.__n == 0: self.__animation = '' if self.__timer.get_time('elapsed') >= self.__dt: self.__animation = '{}{}'.format(self.__animation, self.__animation_style) self.__n += 1 if self.__n > 3: self.__n = 0 # Reset timer self.__timer.reset() # Update message self.__msg = "{}{}".format(self.__dft_text, self.__animation) return self.__msg
def __init__(self, max_duration): self.__max_duration = max_duration self.__timer = Timer(max_duration=max_duration)
def __init__(self, exp_core=Core): """ Class constructor Parameters ---------- :param Core exp_core: Core object :type exp_core: Core """ ################################## # DO NOT MODIFY THE LINES BELLOW # ################################## super(BaseTrial, self).__init__() self.core = exp_core # EasyExp Core instance self.screen = exp_core.screen # Screen instance self.trial = exp_core.trial # Trial instance self.user = exp_core.user # User instance self.parameters = exp_core.parameters # Experiment parameters self.ptw = self.screen.ptw # Window pointer self.logger = exp_core.logger # EasyExp logger self.textToDraw = BaseTrial.__homeMsg # Default welcome message self.status = True # Experiment status (True=running) self._running = False # First Experiment initialization completed self._initialized = False # First trial initialization completed self.validTrial = True # Trial validity self.threads = dict() # Thread container self.lock = Lock # Thread Lock self.add_syncer(2, self.lock) # Add sync manager # State Machine # ============= # Default states duration self.durations = { 'loading': 0.0, "quit": 0.0, "idle": False, "pause": self.trial.pause_duration, "init": 0.0, "end": 0.0 } # Custom states duration specified in parameters.json self.durations.update(self.parameters['durations']) self.state = 'loading' self.next_state = 'idle' # Display options # if set to False, then display will not be automatically cleared at the end of each trial. This allows # continuous rendering with no blank between trials. self.clearAll = True # Events triggers # =============== # Default triggers are moveOnRequested, pauseRequested, startTrigger and quitRequested. # They should not be modified. However, you can add new triggers: 'trigger_name': False self.triggers = { 'moveOnRequested': False, 'pauseRequested': False, 'startTrigger': False, 'response_given': False, 'quitRequested': False } # Stimuli # ======= # Stimulus container # See BaseTrial.init_stimuli() for documentation about how to add stimuli self.stimuli = Stimuli() # Default stimuli self.default_stimuli = Stimuli() self.__init_default_stimuli() # Timers # ====== # Add your timers to this dictionary. # Default timer is timers['runtime'] and it should not be removed # # Example: # timers = {'timer_name': Timer()} # # Then, to start the timer # timers['timer_name'].start() # # Stop the timer # timers['timer_name'].stop() # # Get elapsed time # print(timers['timer_name'].get_time('elapsed') # # Reset timer # timers['timer_name'].reset() self.timers = { 'runtime': Timer(), # DO NOT MODIFY 'loading': Timer() } # Data # ==== # Data field that will be output into the data file should be specified here. self.data = dict() # Storage # ======= # Any customized variable (e.g. not defined in the settings, parameters or conditions file) should be added to # this dictionary. # e.g. self.storage['my_variable'] = value self.storage = dict() # Keyboard/Mouse Inputs # Calls UserInput observer # Usage: # # Create instance # Inputs = UserInput() # Inputs.add_device('keyboard') # arguments are (device type, device arguments: optional) # Inputs.add_device('mouse', visible=False, newPos=None, win=win) # # # Add listeners # Inputs.add_listener('keyboard', 'a', K_a) # arguments are: device_type, key label, key code (Pygame constant) # Inputs.add_listener('keyboard', 'quit', K_ESCAPE) # Inputs.add_listener('mouse', 'left', 0) # # Update status # Inputs.update() # Access watched key's status # Inputs.get_status('_name_of_key') # returns True or False # ================ self.buttons = UserInput() # Input devices used in the experiment self.buttons.add_device('keyboard') self.buttons.add_device('mouse', visible=False, newPos=None, win=self.ptw) # GUI related keys # quit, pause and move on keys must be specified self.buttons.add_listener('keyboard', 'pause', pygame.K_SPACE) self.buttons.add_listener('keyboard', 'quit', pygame.K_q) self.buttons.add_listener('mouse', 'move_on', 0) # Devices # Devices should be implemented in RunTrial::init_devices() method. # ======= self.devices = exp_core.devices # Audio/Video # =========== # Initialize audio self.sounds = dict() self.movie = None
class StateMachine(object): """ StateMachine class Handles transition between states API: - StateMachine.next_state = "next_state": set next state - StateMachine.state = "state_name": set current state - StateMachine.start(): start current state - StateMachine.stop(): stop current state - StateMachine.change_state(force_move_on=False): move to next state if conditions are reached. - StateMachine.move_on(): force transition to next state """ def __init__(self, timings=None, logger=None): """ State constructor Parameters ---------- :param timings: dictionary specifying states timings (in seconds) if duration is False, then the state machine will stay within the same state until told otherwise if duration is 0.0, then the state machine will immediately move to the next state if duration is float > 0.0, then the state machine will stay in the same state for the given duration :type timings: dict :param logger: application logger :type logger: logging.logger|CustomLogger """ self.__durations = timings if timings is not None else dict() self.__logger = logger self.__state = None self.__current = None self.__next_state = None self.__runtime = Timer() self.__runtime.start() self.__counter = 0 self._syncer = None self.__move_on_requested = False @property def logger(self): """ Get logger :return: """ return self.__logger if self.__logger is not None else logging.getLogger( version.__app_name__) @logger.setter def logger(self, logger): """ Set logger :param logger: logger :return: """ self.__logger = logger @property def current(self): """ Get current state :return: """ return self.__current @property def state(self): """ Get current state :return: """ return self.__state @state.setter def state(self, new_state): """ Set current state :param new_state: new state :type new_state: str :return: """ self.__state = new_state @property def next_state(self): """ Get next state :return: """ return self.__next_state @next_state.setter def next_state(self, next_state): """ Set next state :param next_state: next state :type next_state: str :return: """ self.__next_state = next_state @property def durations(self): """ Get states durations :return: """ return self.__durations @durations.setter def durations(self, timings): """ Update durations or set new durations :param timings: :type timings: dict :return: """ self.__durations.update(timings) def add_syncer(self, nb_threads, lock=None): """ Add Syncer instance :param lock: :param nb_threads: :return: """ if self._syncer is None: self._syncer = Syncer(nb_thread=nb_threads, lock=lock) def singleshot(self, name='default', target=None, **kwargs): """ Fire singleshot event :param name: event's label :param target: target function (optional) :param kwargs: function arguments (optional) :rtype: bool """ if self.current is not None: return self.current.singleshot(name=name, target=target, **kwargs) else: return False def start(self): """ This function handles transition between states :rtype: bool """ # If we enter a new state self.__current = State(self.state, duration=self.__durations[self.state]) self.__current.start() msg = "[{0}] '{1}' state begins [t={2:1.3f}]".format( __name__, self.state.upper(), self.current.start_time - self.__runtime.get_time('start')) self.__logger.info(msg) self.__move_on_requested = False def stop(self): """ Stop current state :return: """ if self.current is not None: # Stop current state self.current.stop() self.__logger.info(self) # Reset state watcher self.__reset_syncer() # Set new state self.state = self.next_state # Unset current state self.__current = None def __reset_syncer(self): """ Reset state watcher :return: void """ if self._syncer is not None: # self._syncer.get(self.state) self._syncer.reset() def __is_state_completed(self): """ Check if current state has been executed by every running threads :return: """ return self._syncer.completed( self.state) if self._syncer is not None else True def change_state(self): """ This function handles transition between states :rtype: bool """ if self.current is None: # If we enter a new state self.start() return True # If we transition to the next state if self.__is_state_completed() and ( (self.durations[self.state] is False and self.__move_on_requested) or (self.durations[self.state] is not False and self.current.status and self.current.running)): # Stop current state self.stop() return False else: return False def jump(self): """ Move directly to next state :return: bool """ if self.current is not None: # If we enter a new state self.stop() return True return False def request_move_on(self): """ Request transition to next state. This only has an effect if the current state has no maximum duration defined. :return: void """ if not self.__move_on_requested: self.logger.info('[{}] STATE: {} - Move on requested'.format( __name__, self.state)) self.__move_on_requested = True def __str__(self): """ Print State information :return: """ return "[{0}]: '{1}' state ends [DUR: {3:.3f}s | NEXT: {4}]".format( __name__, self.state.upper(), self.__current.start_time - self.__runtime.get_time('start'), self.current.duration, self.next_state)
class State(object): """ Handle information and methods associated with a state of the state machine Usage: state = State('my_state', duration=2.0) state.start() while state.status: if not state.status: state.stop() break """ def __init__(self, name, duration=False): """ State constructor :param name: :param duration: """ self.__name = name # Name of the state self.__status = False # Status: False if the state has finished self.__max_duration = duration # Maximum duration of the state (can be False (no limit), 0.0, or float > 0.0) self.__timer = None # State timer self.__start_time = None # State starting time self.__end_time = None # State ending time self.__duration = 0.0 # State duration self.__running = False # Has the state started already self.__executed = False self.__singleshot = SingleShot() @property def running(self): return self.__running @property def executed(self): """ Set state as executed :return: """ return self.__executed def execute(self): """ Set state as executed :return: """ if not self.__executed: self.__executed = True @property def status(self): """ Return status of current state: True if it is time to move on :return: """ if self.__running and self.__max_duration is not False: self.__status = self.__timer.get_time( 'elapsed') >= self.__max_duration else: self.__status = False return self.__status def singleshot(self, name, target=None, **kwargs): """ Handles single shot events specific to this state :param name: :param target: :param kwargs: :return: """ if target is not None: return self.__singleshot.run(name, target=target, **kwargs) else: return self.__singleshot[name] @property def duration(self): self.__duration = self.__timer.get_time('elapsed') return self.__duration @property def start_time(self): return self.__start_time @property def end_time(self): return self.__end_time def start(self): """ Start state :return: """ self.__timer = Timer(max_duration=self.__max_duration) self.__timer.start() self.__start_time = self.__timer.get_time('start') self.__running = True def stop(self): """ End state :return: """ # Stop and reset timer self.__timer.stop() self.__end_time = self.__timer.get_time('stop') self.__running = False