コード例 #1
0
    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
コード例 #2
0
 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
コード例 #3
0
ファイル: BaseTrial.py プロジェクト: Fperdreau/EasyExp
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)
コード例 #4
0
ファイル: BaseTrial.py プロジェクト: Fperdreau/EasyExp
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
コード例 #5
0
ファイル: BaseTrial.py プロジェクト: Fperdreau/EasyExp
 def __init__(self, max_duration):
     self.__max_duration = max_duration
     self.__timer = Timer(max_duration=max_duration)
コード例 #6
0
ファイル: BaseTrial.py プロジェクト: Fperdreau/EasyExp
    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
コード例 #7
0
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)
コード例 #8
0
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