Example #1
0
    def _default__band_timer(self):
        """ Create the default timer for the band state changes.

        """
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(self._on_band_timer)
        return timer
Example #2
0
    def _postVisibilityChange(self, visible):
        """ Post a visibility changed event for the dock item.

        This method collapses the post on a timer and will not emit
        the event when the visibility temporarily toggles bettwen
        states.

        Parameters
        ----------
        visible : bool
            True if the item was show, False if the item was hidden.

        """
        area = self.rootDockArea()
        if area is not None and area.dockEventsEnabled():
            vis_changed = self._vis_changed
            if vis_changed is None:
                timer = QTimer()
                timer.setSingleShot(True)
                timer.timeout.connect(self._onVisibilityTimer)
                self._vis_changed = (timer, visible)
                timer.start()
            else:
                timer, old_visible = vis_changed
                if old_visible != visible:
                    self._vis_changed = None
                    timer.stop()
    def __init__(self):
        self._reads = {}
        self._writes = {}
        self._notifiers = {}
        self._timer = QTimer()
        self._timer.setSingleShot(True)
        self._timer.timeout.connect(self.iterate)

        if QCoreApplication.instance() is None:
            # Application Object has not been started yet
            self.qApp = QCoreApplication([])
            self._ownApp = True
        else:
            self.qApp = QCoreApplication.instance()
            self._ownApp = False
        self._blockApp = None
        posixbase.PosixReactorBase.__init__(self)
Example #4
0
    def _postVisibilityChange(self, visible):
        """ Post a visibility changed event for the dock item.

        This method collapses the post on a timer and will not emit
        the event when the visibility temporarily toggles bettwen
        states.

        Parameters
        ----------
        visible : bool
            True if the item was show, False if the item was hidden.

        """
        area = self.rootDockArea()
        if area is not None and area.dockEventsEnabled():
            vis_changed = self._vis_changed
            if vis_changed is None:
                timer = QTimer()
                timer.setSingleShot(True)
                timer.timeout.connect(self._onVisibilityTimer)
                self._vis_changed = (timer, visible)
                timer.start()
            else:
                timer, old_visible = vis_changed
                if old_visible != visible:
                    self._vis_changed = None
                    timer.stop()
Example #5
0
    def alert(self, level, on=250, off=250, repeat=4, persist=False):
        """ Set the alert level on the dock item.

        This will override any currently applied alert level.

        Parameters
        ----------
        level : unicode
            The alert level token to apply to the dock item.

        on : int
            The duration of the 'on' cycle, in ms. A value of -1 means
            always on.

        off : int
            The duration of the 'off' cycle, in ms. If 'on' is -1, this
            value is ignored.

        repeat : int
            The number of times to repeat the on-off cycle. If 'on' is
            -1, this value is ignored.

        persist : bool
            Whether to leave the alert in the 'on' state when the cycles
            finish. If 'on' is -1, this value is ignored.

        """
        if self._alert_data is not None:
            self.clearAlert()
        app = QApplication.instance()
        app.focusChanged.connect(self._onAppFocusChanged)
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(self._onAlertTimer)
        on, off, repeat = max(-1, on), max(0, off), max(1, repeat)
        self._alert_data = _AlertData(timer, level, on, off, repeat, persist)
        if on < 0:
            self.alerted.emit(level)
        else:
            self._onAlertTimer()
Example #6
0
    def alert(self, level, on=250, off=250, repeat=4, persist=False):
        """ Set the alert level on the dock item.

        This will override any currently applied alert level.

        Parameters
        ----------
        level : unicode
            The alert level token to apply to the dock item.

        on : int
            The duration of the 'on' cycle, in ms. A value of -1 means
            always on.

        off : int
            The duration of the 'off' cycle, in ms. If 'on' is -1, this
            value is ignored.

        repeat : int
            The number of times to repeat the on-off cycle. If 'on' is
            -1, this value is ignored.

        persist : bool
            Whether to leave the alert in the 'on' state when the cycles
            finish. If 'on' is -1, this value is ignored.

        """
        if self._alert_data is not None:
            self.clearAlert()
        app = QApplication.instance()
        app.focusChanged.connect(self._onAppFocusChanged)
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(self._onAlertTimer)
        on, off, repeat = max(-1, on), max(0, off), max(1, repeat)
        self._alert_data = _AlertData(timer, level, on, off, repeat, persist)
        if on < 0:
            self.alerted.emit(level)
        else:
            self._onAlertTimer()
Example #7
0
 def start_timer(self, name, duration, callback):
     log.debug('Starting %f sec. timer %s', duration, name)
     timer = QTimer()
     timer.timeout.connect(callback)
     timer.setSingleShot(True)
     timer.start(duration * 1e3)
     self._timers[name] = timer
    def _start_timer(self, duration, event):
        # The duration can be specified as a string naming the context variable
        # to extract.
        if isinstance(duration, basestring):
            duration = self.context.get_value(duration)
        log.debug('Timer for {} with duration {}'.format(event, duration))
        receiver = partial(self.handle_event, event)

        if duration == 0:
            deferred_call(receiver)
        else:
            self.timer = QTimer()
            self.timer.timeout.connect(receiver)    # set up new callback
            self.timer.setSingleShot(True)          # call only once
            self.timer.start(duration*1e3)
Example #9
0
 def start_timer(self, name, duration, callback):
     try:
         timer = QTimer()
         timer.timeout.connect(callback)
         timer.setSingleShot(True)
         timer.start(int(duration * 1e3))
         self._timers[name] = timer
     except Exception as e:
         log.error(
             f'Attempt to start timer {name} with duration {duration} sec failed'
         )
         raise
class QtReactor(posixbase.PosixReactorBase):
    implements(IReactorFDSet)

    def __init__(self):
        self._reads = {}
        self._writes = {}
        self._notifiers = {}
        self._timer = QTimer()
        self._timer.setSingleShot(True)
        self._timer.timeout.connect(self.iterate)

        if QCoreApplication.instance() is None:
            # Application Object has not been started yet
            self.qApp = QCoreApplication([])
            self._ownApp = True
        else:
            self.qApp = QCoreApplication.instance()
            self._ownApp = False
        self._blockApp = None
        posixbase.PosixReactorBase.__init__(self)

    def _add(self, xer, primary, type):
        """
        Private method for adding a descriptor from the event loop.

        It takes care of adding it if  new or modifying it if already added
        for another state (read -> read/write for example).
        """
        if xer not in primary:
            primary[xer] = TwistedSocketNotifier(None, self, xer, type)

    def addReader(self, reader):
        """
        Add a FileDescriptor for notification of data available to read.
        """
        self._add(reader, self._reads, QSocketNotifier.Read)

    def addWriter(self, writer):
        """
        Add a FileDescriptor for notification of data available to write.
        """
        self._add(writer, self._writes, QSocketNotifier.Write)

    def _remove(self, xer, primary):
        """
        Private method for removing a descriptor from the event loop.

        It does the inverse job of _add, and also add a check in case of the fd
        has gone away.
        """
        if xer in primary:
            notifier = primary.pop(xer)
            notifier.shutdown()

    def removeReader(self, reader):
        """
        Remove a Selectable for notification of data available to read.
        """
        self._remove(reader, self._reads)

    def removeWriter(self, writer):
        """
        Remove a Selectable for notification of data available to write.
        """
        self._remove(writer, self._writes)

    def removeAll(self):
        """
        Remove all selectables, and return a list of them.
        """
        rv = self._removeAll(self._reads, self._writes)
        return rv

    def getReaders(self):
        return self._reads.keys()

    def getWriters(self):
        return self._writes.keys()

    def callLater(self, howlong, *args, **kargs):
        rval = super(QtReactor, self).callLater(howlong, *args, **kargs)
        self.reactorInvocation()
        return rval

    def reactorInvocation(self):
        self._timer.stop()
        self._timer.setInterval(0)
        self._timer.start()

    def _iterate(self, delay=None, fromqt=False):
        """
        See twisted.internet.interfaces.IReactorCore.iterate.
        """
        self.runUntilCurrent()
        self.doIteration(delay, fromqt)

    iterate = _iterate

    def doIteration(self, delay=None, fromqt=False):
        """
        This method is called by a Qt timer or by network activity
        on a file descriptor
        """
        if not self.running and self._blockApp:
            self._blockApp.quit()
        self._timer.stop()
        delay = max(delay, 1)
        if not fromqt:
            self.qApp.processEvents(QEventLoop.AllEvents, delay * 1000)
        if self.timeout() is None:
            timeout = 0.1
        elif self.timeout() == 0:
            timeout = 0
        else:
            timeout = self.timeout()
        self._timer.setInterval(timeout * 1000)
        self._timer.start()

    def runReturn(self, installSignalHandlers=True):
        self.startRunning(installSignalHandlers=installSignalHandlers)
        self.reactorInvocation()

    def run(self, installSignalHandlers=True):
        if self._ownApp:
            self._blockApp = self.qApp
        else:
            self._blockApp = QEventLoop()
        self.runReturn()
        self._blockApp.exec_()
Example #11
0
class AppetitivePlugin(BasePlugin):
    '''
    Plugin for controlling appetitive experiments that are based on a reward.
    Eventually this may become generic enough that it can be used with aversive
    experiments as well (it may already be sufficiently generic).
    '''
    lock = Typed(object)

    # Current trial
    trial = Int(0)

    # Current number of consecutive nogos
    consecutive_nogo = Int(0)

    # Used by the trial sequence selector to randomly select between go/nogo.
    rng = Typed(np.random.RandomState)
    trial_type = Unicode()
    trial_info = Typed(dict, ())
    trial_state = Typed(TrialState)

    timer = Typed(QTimer)

    event_map = {
        ('rising', 'nose_poke'): Event.np_start,
        ('falling', 'nose_poke'): Event.np_end,
        ('rising', 'reward_contact'): Event.reward_start,
        ('falling', 'reward_contact'): Event.reward_end,
    }

    score_map = {
        ('nogo', 'reward'): TrialScore.false_alarm,
        ('nogo', 'poke'): TrialScore.correct_reject,
        ('nogo', 'no response'): TrialScore.correct_reject,
        ('go', 'reward'): TrialScore.hit,
        ('go', 'poke'): TrialScore.miss,
        ('go', 'no response'): TrialScore.miss,
    }

    def next_selector(self):
        '''
        Determine next trial type
        '''
        try:
            max_nogo = self.context.get_value('max_nogo')
            go_probability = self.context.get_value('go_probability')
            score = self.context.get_value('score')
        except KeyError:
            self.trial_type = 'go_remind'
            return 'remind'

        if self._remind_requested:
            self.trial_type = 'go_remind'
            return 'remind'
        elif self.consecutive_nogo >= max_nogo:
            self.trial_type = 'go_forced'
            return 'go'
        elif score == TrialScore.false_alarm:
            self.trial_type = 'nogo_repeat'
            return 'nogo'
        else:
            if self.rng.uniform() <= go_probability:
                self.trial_type = 'go'
                return 'go'
            else:
                self.trial_type = 'nogo'
                return 'nogo'

    def start_experiment(self):
        self.lock = threading.RLock()
        try:
            self.trial += 1
            #self.context.apply_changes()
            #self.core.invoke_command('psi.data.prepare')
            #self.start_engines()
            #log.debug('Done starting engines')
            self.rng = np.random.RandomState()
            self.context.next_setting(self.next_selector(), save_prior=False)
            self.experiment_state = 'running'
            self.trial_state = TrialState.waiting_for_np_start
            # Evaluates remaining values in context
            self.context.get_values()
            self.invoke_actions('experiment_start', self.get_ts())
        except Exception as e:
            # TODO - provide user interface to notify of errors. How to
            # recover?
            raise

    def start_trial(self):
        self.invoke_actions('trial_start', self.get_ts())
        # TODO - the hold duration will include the update delay. Do we need
        # super-precise tracking of hold period or can it vary by a couple 10s
        # to 100s of msec?
        self.trial_state = TrialState.waiting_for_hold_period
        self.start_timer('hold_duration', Event.hold_duration_elapsed)

    def end_trial(self, response):
        log.debug('Animal responded by {}, ending trial'.format(response))
        self.stop_timer()

        trial_type = self.trial_type.split('_', 1)[0]
        score = self.score_map[trial_type, response]
        self.consecutive_nogo = self.consecutive_nogo + 1 \
            if trial_type == 'nogo' else 0

        response_ts = self.trial_info.setdefault('response_ts', np.nan)
        target_start = self.trial_info.setdefault('target_start', np.nan)
        np_end = self.trial_info.setdefault('np_end', np.nan)
        np_start = self.trial_info.setdefault('np_start', np.nan)

        self.trial_info.update({
            'response': response,
            'trial_type': self.trial_type,
            'score': score.value,
            'correct': score in (TrialScore.correct_reject, TrialScore.hit),
            'response_time': response_ts-target_start,
            'reaction_time': np_end-np_start,
        })
        if score == TrialScore.false_alarm:
            self.invoke_actions('timeout_start', self.get_ts())
            self.trial_state = TrialState.waiting_for_to
            self.start_timer('to_duration', Event.to_duration_elapsed)
        else:
            if score == TrialScore.hit:
                if not self.context.get_value('training_mode'):
                    self.invoke_actions('deliver_reward', self.get_ts())
            self.trial_state = TrialState.waiting_for_iti
            self.start_timer('iti_duration', Event.iti_duration_elapsed)

        self.context.set_values(self.trial_info)
        parameters ={'results': self.context.get_values()}
        self.core.invoke_command('psi.data.process_trial', parameters)

        log.debug('Setting up for next trial')
        # Apply pending changes that way any parameters (such as repeat_FA or
        # go_probability) are reflected in determining the next trial type.
        if self._apply_requested:
            self._apply_changes()
        selector = self.next_selector()
        self.context.next_setting(selector, save_prior=True)
        self.trial += 1
        self.trial_info = {}

    def request_resume(self):
        super(AppetitivePlugin, self).request_resume()
        self.trial_state = TrialState.waiting_for_np_start

    def ao_callback(self, name):
        log.debug('Updating output {}'.format(name))
        self._outputs[name].update()

    def ai_callback(self, name, data):
        parameters = {'name': name, 'data': data}
        self.core.invoke_command('psi.data.process_ai', parameters)

    def di_callback(self, name, data):
        pass

    def et_callback(self, name, edge, event_time):
        if edge == 'processed':
            # sort of a hack ...
            parameters = {'name': 'event_log', 'timestamp': event_time}
            self.core.invoke_command('psi.data.set_current_time', parameters)
        else:
            log.debug('Detected {} on {} at {}'.format(edge, name, event_time))
            event = self.event_map[edge, name]
            self.handle_event(event, event_time)

    def stop_experiment(self):
        self.stop_engines()
        self.experiment_state = 'stopped'
        self.invoke_actions('experiment_end', self.get_ts())
        self.core.invoke_command('psi.data.finalize')

    def pause_experiment(self):
        if self.trial_state == TrialState.waiting_for_np_start:
            deferred_call(self._pause_experiment)

    def _pause_experiment(self):
        self.experiment_state = 'paused'
        self._pause_requested = False

    def apply_changes(self):
        if self.trial_state == TrialState.waiting_for_np_start:
            deferred_call(self._apply_changes)

    def _apply_changes(self):
        self.context.apply_changes()
        self._apply_requested = False
        log.debug('applied changes')

    def handle_event(self, event, timestamp=None):
        # Ensure that we don't attempt to process several events at the same
        # time. This essentially queues the events such that the next event
        # doesn't get processed until `_handle_event` finishes processing the
        # current one.

        # Only events generated by NI-DAQmx callbacks will have a timestamp.
        # Since we want all timing information to be in units of the analog
        # output sample clock, we will capture the value of the sample clock
        # if a timestamp is not provided. Since there will be some delay
        # between the time the event occurs and the time we read the analog
        # clock, the timestamp won't be super-accurate. However, it's not
        # super-important since these events are not reference points around
        # which we would do a perievent analysis. Important reference points
        # would include nose-poke initiation and withdraw, reward contact,
        # sound onset, lights on, lights off. These reference points will
        # be tracked via NI-DAQmx or can be calculated (i.e., we know
        # exactly when the target onset occurs because we precisely specify
        # the location of the target in the analog output buffer).
        try:
            if timestamp is None:
                timestamp = self.get_ts()
            log.debug('{} at {}'.format(event, timestamp))
            log.trace('Emitting handle_event signal')
            deferred_call(self._handle_event, event, timestamp)
        except Exception as e:
            log.exception(e)
            raise

    def _handle_event(self, event, timestamp):
        '''
        Give the current experiment state, process the appropriate response for
        the event that occured. Depending on the experiment state, a particular
        event may not be processed.
        '''
        log.debug('Recieved handle_event signal')
        self.invoke_actions(event.name, timestamp)

        if self.experiment_state == 'paused':
            # If the experiment is paused, don't do anything.
            return

        if self.trial_state == TrialState.waiting_for_np_start:
            if event == Event.np_start:
                # Animal has nose-poked in an attempt to initiate a trial.
                self.trial_state = TrialState.waiting_for_np_duration
                self.start_timer('np_duration', Event.np_duration_elapsed)
                # If the animal does not maintain the nose-poke long enough,
                # this value will be deleted.
                self.trial_info['np_start'] = timestamp

        elif self.trial_state == TrialState.waiting_for_np_duration:
            if event == Event.np_end:
                # Animal has withdrawn from nose-poke too early. Cancel the
                # timer so that it does not fire a 'event_np_duration_elapsed'.
                log.debug('Animal withdrew too early')
                self.stop_timer()
                self.trial_state = TrialState.waiting_for_np_start
                del self.trial_info['np_start']
            elif event == Event.np_duration_elapsed:
                log.debug('Animal initiated trial')
                self.start_trial()

                # We want to deliver the reward immediately when in training
                # mode so the food is already in the hopper. Not sure how
                # *critical* this is?
                if self.context.get_value('training_mode'):
                    if self.trial_type.startswith('go'):
                        self.invoke_actions('deliver_reward', self.get_ts())

        elif self.trial_state == TrialState.waiting_for_hold_period:
            # All animal-initiated events (poke/reward) are ignored during this
            # period but we may choose to record the time of nose-poke withdraw
            # if it occurs.
            if event == Event.np_end:
                # Record the time of nose-poke withdrawal if it is the first
                # time since initiating a trial.
                log.debug('Animal withdrew during hold period')
                if 'np_end' not in self.trial_info:
                    log.debug('Recording np_end')
                    self.trial_info['np_end'] = timestamp
            elif event == Event.hold_duration_elapsed:
                log.debug('Animal maintained poke through hold period')
                self.trial_state = TrialState.waiting_for_response
                self.invoke_actions('response_start', self.get_ts())
                self.start_timer('response_duration',
                                 Event.response_duration_elapsed)

        elif self.trial_state == TrialState.waiting_for_response:
            # If the animal happened to initiate a nose-poke during the hold
            # period above and is still maintaining the nose-poke, they have to
            # manually withdraw and re-poke for us to process the event.
            if event == Event.np_end:
                # Record the time of nose-poke withdrawal if it is the first
                # time since initiating a trial.
                log.debug('Animal withdrew during response period')
                if 'np_end' not in self.trial_info:
                    log.debug('Recording np_end')
                    self.trial_info['np_end'] = timestamp
            elif event == Event.np_start:
                log.debug('Animal repoked')
                self.trial_info['response_ts'] = timestamp
                self.end_trial(response='poke')
                # At this point, trial_info should have been cleared by the
                # `end_trial` function so that we can prepare for the next
                # trial. Save the start of the nose-poke.
                self.trial_info['np_start'] = timestamp
            elif event == Event.reward_start:
                log.debug('Animal went to reward')
                self.trial_info['response_ts'] = timestamp
                self.end_trial(response='reward')
            elif event == Event.response_duration_elapsed:
                log.debug('Animal provided no response')
                self.trial_info['response_ts'] = np.nan
                self.end_trial(response='no response')

        elif self.trial_state == TrialState.waiting_for_to:
            if event == Event.to_duration_elapsed:
                # Turn the light back on
                self.invoke_actions('timeout_end', self.get_ts())
                self.trial_state = TrialState.waiting_for_iti
                self.invoke_actions('iti_start', self.get_ts())
                self.start_timer('iti_duration', Event.iti_duration_elapsed)
            elif event in (Event.reward_start, Event.np_start):
                log.debug('Resetting timeout duration')
                self.stop_timer()
                self.start_timer('to_duration', Event.to_duration_elapsed)

        elif self.trial_state == TrialState.waiting_for_iti:
            if event == Event.iti_duration_elapsed:
                if self._pause_requested:
                    self.pause_experiment()
                    self.trial_state = TrialState.waiting_for_resume
                elif 'np_start' in self.trial_info:
                    # The animal had initiated a nose-poke during the ITI.
                    # Allow this to contribute towards the start of the next
                    # trial by calculating how much is pending in the nose-poke
                    # duration.
                    self.trial_state = TrialState.waiting_for_np_duration
                    current_poke_duration = self.get_ts()-self.trial_info['np_start']
                    poke_duration = self.context.get_value('np_duration')
                    remaining_poke_duration = poke_duration-current_poke_duration
                    delta = max(0, remaining_poke_duration)
                    self.start_timer(delta, Event.np_duration_elapsed)
                else:
                    self.trial_state = TrialState.waiting_for_np_start
            elif event == Event.np_end and 'np_start' in self.trial_info:
                del self.trial_info['np_start']

    def start_timer(self, duration, event):
        deferred_call(self._start_timer, duration, event)

    def stop_timer(self):
        deferred_call(self._stop_timer)

    def _stop_timer(self):
        if self.timer is not None:
            self.timer.timeout.disconnect()
            self.timer.stop()

    def _start_timer(self, duration, event):
        # The duration can be specified as a string naming the context variable
        # to extract.
        if isinstance(duration, basestring):
            duration = self.context.get_value(duration)
        log.debug('Timer for {} with duration {}'.format(event, duration))
        receiver = partial(self.handle_event, event)

        if duration == 0:
            deferred_call(receiver)
        else:
            self.timer = QTimer()
            self.timer.timeout.connect(receiver)    # set up new callback
            self.timer.setSingleShot(True)          # call only once
            self.timer.start(duration*1e3)