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