def main_launcher(): options = parse_arguments() # flip the default vs the individual launchers - wait for all of the backends options.wait = True # save the total state _opts = get_printable_options_dict(options, include_experiment_and_playlist=True) with open(options.record_file.replace('.h5', '.config.yml'), 'wt') as f: yaml.safe_dump(_opts, f) log = logging.getLogger('flyvr.main') flyvr_shared_state = SharedState(options=options, logger=None, where='main') # start the IPC bus first as it is needed by many subsystems ipc_bus = ConcurrentTask(task=run_main_relay, comms=None, taskinitargs=[]) ipc_bus.start() # start the GUI gui = ConcurrentTask(task=run_main_state_gui, comms=None, taskinitargs=[None, True]) gui.start() backend_wait = [BACKEND_FICTRAC] trac_drv = _get_fictrac_driver(options, log) if trac_drv is not None: fictrac_task = ConcurrentTask(task=trac_drv.run, comms=None, taskinitargs=[options]) fictrac_task.start() # wait till fictrac is processing frames flyvr_shared_state.wait_for_backends(BACKEND_FICTRAC) log.info('fictrac is ready') # start the other mainloops # these always run hwio = ConcurrentTask(task=run_phidget_io, comms=None, taskinitargs=[options]) backend_wait.append(BACKEND_HWIO) hwio.start() daq = ConcurrentTask(task=run_io, comms=None, taskinitargs=[options]) backend_wait.append(BACKEND_DAQ) daq.start() # these are optional if options.keepalive_video or options.playlist.get('video'): video = ConcurrentTask(task=run_video_server, comms=None, taskinitargs=[options]) backend_wait.append(BACKEND_VIDEO) video.start() else: video = None log.info( 'not starting video backend (playlist empty or keepalive_video not specified)' ) if options.keepalive_audio or options.playlist.get('audio'): audio = ConcurrentTask(task=run_sound_server, comms=None, taskinitargs=[options]) backend_wait.append(BACKEND_AUDIO) audio.start() else: audio = None log.info( 'not starting video backend (playlist empty or keepalive_video not specified)' ) log.info('waiting %ss for %r to be ready' % (60, backend_wait)) if flyvr_shared_state.wait_for_backends(*backend_wait, timeout=60): if options.delay < 0: log.info('waiting for manual start signal') flyvr_shared_state.wait_for_start() elif options.delay >= 0: if options.delay > 0: log.info('delaying startup %ss' % options.delay) time.sleep(options.delay) log.info('sending start signal') flyvr_shared_state.signal_start() for i in itertools.count(): try: inputimeout( '\n---------------\nPress any key to finish\n---------------\n\n' if i == 0 else '', 1) flyvr_shared_state.signal_stop().join(timeout=5) break except TimeoutOccurred: if flyvr_shared_state.is_stopped(): # stopped from elsewhere (gui) break else: log.error( 'not all required backends became ready - please check logs for ' 'error messages') flyvr_shared_state.signal_stop() log.info('stopped') for task in (ipc_bus, gui, hwio, daq, video, audio): if task is not None: log.debug('closing subprocess: %r' % task) task.close() log.info('finished')
def run(self, options=None): """ Start the the FicTrac process and block till it closes. This function will poll a shared memory region for changes in tracking data and invoke a control function when they occur. FicTrac is assumed to exist on the system path. Args: options: options loaded from FlyVR config file. If None, driver runs without logging enabled, this is useful for testing. :return: """ if options is not None: setup_logging(options) setup_experiment(options) if options.experiment: self._log.info('initialized experiment %r' % options.experiment) self.experiment = options.experiment # fixme: this should be threaded and context manager to close log_server = DatasetLogServer() flyvr_shared_state = SharedState( options=options, logger=log_server.start_logging_server(options.record_file), where=BACKEND_FICTRAC) if self.experiment: # noinspection PyProtectedMember self.experiment._set_shared_state(flyvr_shared_state) # Setup dataset to log FicTrac data to. flyvr_shared_state.logger.create( "/fictrac/output", shape=[2048, NUM_FICTRAC_FIELDS], maxshape=[None, NUM_FICTRAC_FIELDS], dtype=np.float64, chunks=(2048, NUM_FICTRAC_FIELDS)) flyvr_shared_state.logger.log("/fictrac/output", H5_DATA_VERSION, attribute_name='__version') else: flyvr_shared_state = None self.fictrac_signals = new_mmap_signals_buffer() # Start FicTrac with open(self.console_output_file, "wb") as out: self.fictrac_process = subprocess.Popen( [self.fictrac_bin_fullpath, self.config_file], stdout=out, stderr=subprocess.STDOUT) # Setup the shared memory conneciton and peek at the frame counter data = new_mmap_shmem_buffer() first_frame_count = data.frame_cnt old_frame_count = data.frame_cnt running = True self._log.info("waiting for fictrac updates in shared memory") semaphore = self._open_fictrac_semaphore() # Process FicTrac updates in shared memory while (self.fictrac_process.poll() is None) and running and semaphore: # Acquire the semaphore copy the current fictrac state. try: semaphore.acquire(timeout_ms=1000) data_copy = SHMEMFicTracState() ctypes.pointer(data_copy)[0] = data semaphore.release() except FileNotFoundError: # Semaphore is gone, lets shutdown things. break except OSError: break new_frame_count = data_copy.frame_cnt if old_frame_count != new_frame_count: # If this is our first frame incremented, then send a signal to the # that we have started processing frames if old_frame_count == first_frame_count: if flyvr_shared_state: _ = flyvr_shared_state.signal_ready( BACKEND_FICTRAC) if new_frame_count - old_frame_count != 1: # self.fictrac_process.terminate() self._log.error( "frame counter jumped by more than 1 (%s vs %s)" % (old_frame_count, new_frame_count)) old_frame_count = new_frame_count # Log the FicTrac data to our master log file. if flyvr_shared_state: flyvr_shared_state.logger.log( '/fictrac/output', fictrac_state_to_vec(data_copy)) if self.experiment is not None: self.experiment.process_state(data_copy) # If we detect it is time to shutdown, kill the FicTrac process if flyvr_shared_state and flyvr_shared_state.is_stopped(): running = False # Try to close up the semaphore try: if semaphore: semaphore.close() except OSError: pass self.stop() # blocks self._log.info('fictrac process finished') # Get the fictrac process return code if self.fictrac_process.returncode is not None and self.fictrac_process.returncode != 0: self._log.error( 'fictrac failed because of an application error. Consult the fictrac console output file' ) if flyvr_shared_state: flyvr_shared_state.runtime_error(2)
class FlyVRStateGui(QWidget): STATE = [ 'FICTRAC_FRAME_NUM', 'SOUND_OUTPUT_NUM_SAMPLES_WRITTEN', 'VIDEO_OUTPUT_NUM_FRAMES', 'DAQ_OUTPUT_NUM_SAMPLES_WRITTEN', 'DAQ_INPUT_NUM_SAMPLES_READ' ] FPS = 30 def __init__(self, app, quit_app_on_stop): # noinspection PyArgumentList super().__init__(parent=None) self._app = app self._quit_app_on_stop = quit_app_on_stop self._entries = {} self._tick = 0 self._fn0 = self._t0 = 0 self._lbl_backends = None self._lbl_started = None self._lbl_fps = None self._init_ui() self.flyvr_shared_state = SharedState(options=None, logger=None, where='gui') self._timer = QTimer() # noinspection PyUnresolvedReferences self._timer.timeout.connect(self._update_state) self._timer.start(1000 / FlyVRStateGui.FPS) def _update_state(self): for s in FlyVRStateGui.STATE: v = getattr(self.flyvr_shared_state, s) self._entries[s].setText(str(v or 0)) self._lbl_backends.setText(', '.join( self.flyvr_shared_state.backends_ready)) self._lbl_started.setText(str(self.flyvr_shared_state.is_started())) # every second-ish if (self._tick % self.FPS) == 0: fictrac_fps = (self.flyvr_shared_state.FICTRAC_FRAME_NUM - self._fn0) / (time.time() - self._t0) self._lbl_fps.setText('%.1f' % fictrac_fps) self._fn0 = self.flyvr_shared_state.FICTRAC_FRAME_NUM self._t0 = time.time() self._tick += 1 if self._quit_app_on_stop: if self.flyvr_shared_state.is_stopped(): self._app.quit() def _clicked_clear(self): for s in FlyVRStateGui.STATE: self._entries[s].setText('') def _clicked_start(self): self.flyvr_shared_state.signal_start() def _clicked_stop(self): self.flyvr_shared_state.signal_stop().join(2) # noinspection PyUnresolvedReferences,PyArgumentList def _init_ui(self): self.setWindowTitle('FlyVR') layout = QGridLayout() # noinspection PyArgumentList def _build_label(_name, _row): _lbl = QLabel('&%s' % _name, self) _edt = QLineEdit(self) _edt.setReadOnly(True) _lbl.setBuddy(_edt) layout.addWidget(_lbl, _row, 0) layout.addWidget(_edt, _row, 1, 1, 2) return _edt row = 0 for s in FlyVRStateGui.STATE: self._entries[s] = _build_label(s, row) row += 1 self._lbl_fps = _build_label('FicTrac Framerate', row) row += 1 self._lbl_backends = _build_label('Backends Ready', row) row += 1 self._lbl_started = _build_label('Experiment Started', row) row += 1 # clear = QPushButton('&Clear') # clear.clicked.connect(self._clicked_clear) # layout.addWidget(clear, row, 2) # row += 1 start = QPushButton('&Start Experiment') start.clicked.connect(self._clicked_start) stop = QPushButton('&Stop FlyVR') stop.clicked.connect(self._clicked_stop) layout.addWidget(start, row, 1) layout.addWidget(stop, row, 2) row += 1 self.setLayout(layout)