def __init__(self, *args, **kwargs): # , source, destination, fps, size, gui, serial self.log = logging.getLogger(__name__) QtGui.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) # Spotter main class, handles Grabber, Writer, Tracker, Chatter self.__spotter_ref = Spotter(*args, **kwargs) # Status Bar self.status_bar = StatusBar(self) self.statusBar().addWidget(self.status_bar) # Side bar widget self.side_bar = SideBar.SideBar(self) self.ui.frame_parameters.addWidget(self.side_bar) # Exit Signals self.ui.actionE_xit.setShortcut('Ctrl+Q') self.ui.actionE_xit.setStatusTip('Exit Spotter') self.connect(self.ui.actionE_xit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()')) # About window self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about) # Menu Bar items # File self.connect(self.ui.actionFile, QtCore.SIGNAL('triggered()'), self.file_open_video) self.connect(self.ui.actionCamera, QtCore.SIGNAL('triggered()'), self.file_open_device) # Configuration self.connect(self.ui.actionLoadConfig, QtCore.SIGNAL('triggered()'), self.load_config) self.connect(self.ui.actionSaveConfig, QtCore.SIGNAL('triggered()'), self.save_config) self.connect(self.ui.actionRemoveTemplate, QtCore.SIGNAL('triggered()'), self.side_bar.remove_all_tabs) # Toolbar items self.connect(self.ui.actionRecord, QtCore.SIGNAL('toggled(bool)'), self.record_video) self.connect(self.ui.actionSourceProperties, QtCore.SIGNAL('triggered()'), self.spotter.grabber.get_capture_properties) # Serial/Arduino Connection status indicator self.arduino_indicator = SerialIndicator(self.spotter.chatter) self.ui.toolBar.addWidget(self.arduino_indicator) # OpenGL frame self.gl_frame = GLFrame(AA=True) self.ui.frame_video.addWidget(self.gl_frame) self.gl_frame.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) # handling mouse events by the tabs for selection of regions etc. self.gl_frame.sig_event.connect(self.mouse_event_to_tab) # Loading template list in folder default_path = os.path.join(os.path.abspath(DIR_CONFIG), DEFAULT_TEMPLATE) self.template_default = self.parse_config(default_path, True) #list_of_files = [f for f in os.listdir(DIR_TEMPLATES) if f.lower().endswith('ini')] # Main Window states self.center_window() self.connect(self.ui.actionOnTop, QtCore.SIGNAL('toggled(bool)'), self.toggle_window_on_top) # Starts main frame grabber loop self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.refresh) self.timer.start(GUI_REFRESH_INTERVAL) self.stopwatch = QtCore.QElapsedTimer() self.stopwatch.start()
class Main(QtGui.QMainWindow): gui_refresh_offset = 0 __spotter_ref = None def __init__(self, *args, **kwargs): # , source, destination, fps, size, gui, serial self.log = logging.getLogger(__name__) QtGui.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) # Spotter main class, handles Grabber, Writer, Tracker, Chatter self.__spotter_ref = Spotter(*args, **kwargs) # Status Bar self.status_bar = StatusBar(self) self.statusBar().addWidget(self.status_bar) # Side bar widget self.side_bar = SideBar.SideBar(self) self.ui.frame_parameters.addWidget(self.side_bar) # Exit Signals self.ui.actionE_xit.setShortcut('Ctrl+Q') self.ui.actionE_xit.setStatusTip('Exit Spotter') self.connect(self.ui.actionE_xit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()')) # About window self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about) # Menu Bar items # File self.connect(self.ui.actionFile, QtCore.SIGNAL('triggered()'), self.file_open_video) self.connect(self.ui.actionCamera, QtCore.SIGNAL('triggered()'), self.file_open_device) # Configuration self.connect(self.ui.actionLoadConfig, QtCore.SIGNAL('triggered()'), self.load_config) self.connect(self.ui.actionSaveConfig, QtCore.SIGNAL('triggered()'), self.save_config) self.connect(self.ui.actionRemoveTemplate, QtCore.SIGNAL('triggered()'), self.side_bar.remove_all_tabs) # Toolbar items self.connect(self.ui.actionRecord, QtCore.SIGNAL('toggled(bool)'), self.record_video) self.connect(self.ui.actionSourceProperties, QtCore.SIGNAL('triggered()'), self.spotter.grabber.get_capture_properties) # Serial/Arduino Connection status indicator self.arduino_indicator = SerialIndicator(self.spotter.chatter) self.ui.toolBar.addWidget(self.arduino_indicator) # OpenGL frame self.gl_frame = GLFrame(AA=True) self.ui.frame_video.addWidget(self.gl_frame) self.gl_frame.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) # handling mouse events by the tabs for selection of regions etc. self.gl_frame.sig_event.connect(self.mouse_event_to_tab) # Loading template list in folder default_path = os.path.join(os.path.abspath(DIR_CONFIG), DEFAULT_TEMPLATE) self.template_default = self.parse_config(default_path, True) #list_of_files = [f for f in os.listdir(DIR_TEMPLATES) if f.lower().endswith('ini')] # Main Window states self.center_window() self.connect(self.ui.actionOnTop, QtCore.SIGNAL('toggled(bool)'), self.toggle_window_on_top) # Starts main frame grabber loop self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.refresh) self.timer.start(GUI_REFRESH_INTERVAL) self.stopwatch = QtCore.QElapsedTimer() self.stopwatch.start() @property def spotter(self): return self.__spotter_ref ############################################################################### ## FRAME REFRESH ############################################################################### def refresh(self): # TODO: I ain't got no clue as to why reducing the interval drastically improves the frame rate # TODO: Maybe the interval immediately resets the counter and starts it up? elapsed = self.stopwatch.restart() #self.log.debug("Updating spotter") if self.spotter.update() is None: pass #return # update the OpenGL frame #self.log.debug("Updating GL") if not (self.gl_frame.width and self.gl_frame.height): return self.gl_frame.update_world(self.spotter) # Update the currently open tab #self.log.debug("Updating side bar") self.side_bar.update_current_page() # check if the refresh rate needs adjustment #self.log.debug("Updating GUI refresh rate") self.adjust_refresh_rate() # based on stopwatch, show GUI refresh rate #self.log.debug("Updating GUI refresh rate display") self.status_bar.update_fps(elapsed) def adjust_refresh_rate(self, forced=None): """ Change GUI refresh rate according to frame rate of video source, or keep at 1000/GUI_REFRESH_INTERVAL Hz for cameras to not miss too many frames """ self.gui_refresh_offset = self.status_bar.sb_offset.value() if forced is not None: self.timer.setInterval(forced) return if self.spotter.source_type == 'file': if not self.status_bar.sb_offset.isEnabled(): self.status_bar.sb_offset.setEnabled(True) try: interval = int( 1000.0 / self.spotter.grabber.fps) + self.gui_refresh_offset except (ValueError, TypeError): interval = 0 if interval < 0: interval = 1 self.status_bar.sb_offset.setValue( interval - int(1000.0 / self.spotter.grabber.fps)) if self.spotter.grabber.fps != 0 and self.timer.interval( ) != interval: self.timer.setInterval(interval) self.log.debug( "Changed main loop update rate to match file. New: %d", self.timer.interval()) else: if self.status_bar.sb_offset.isEnabled(): self.status_bar.sb_offset.setEnabled(False) #self.status_bar.sb_offset.setValue(0) if self.timer.interval() != GUI_REFRESH_INTERVAL: self.timer.setInterval(GUI_REFRESH_INTERVAL) self.log.debug( "Changed main loop update rate to be fast. New: %d", self.timer.interval()) def record_video(self, state, filename=None): """ Control recording of grabbed video. """ # TODO: Select output video file name. self.log.debug("Toggling writer recording state") if state: if filename is None: filename = QtGui.QFileDialog.getSaveFileName( self, 'Open Video', './recordings/') if len(filename): self.spotter.start_writer(str(filename) + '.avi') else: self.spotter.stop_writer() def mouse_event_to_tab(self, event_type, event): """ Hand the mouse event to the active tab. Tabs may handle mouse events differently, and depending on internal states (e.g. selections) """ current_tab = self.side_bar.get_child_page() if current_tab: try: if current_tab.accept_events: current_tab.process_event(event_type, event) except AttributeError: pass def about(self): """ About message box. Credits. Links. Jokes. """ QtGui.QMessageBox.about( self, "About", """<b>Spotter</b> v%s <p>Copyright © 2012-2013 <a href=mailto:[email protected]>Ronny Eichler</a>. <p>This application is under heavy development. Use at your own risk. <p>Python %s - PyQt4 version %s - on %s""" % (__version__, platform.python_version(), QtCore.QT_VERSION_STR, platform.system())) def center_window(self): """ Centers main window on screen. Doesn't quite work on multi-monitor setups, as the whole screen-area is taken. But as long as the window ends up in a predictable position... """ screen = QtGui.QDesktopWidget().screenGeometry() window_size = self.geometry() self.move((screen.width() - window_size.width()) / 2, (screen.height() - window_size.height()) / 2) def toggle_window_on_top(self, state): """ Have main window stay on top. According to the setWindowFlags documentation, the window will hide after changing flags, requiring either a .show() or a .raise(). These may have different behaviors on different platforms!""" # TODO: Test on Linux, OSX, Win8 if state: self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) self.show() else: self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowStaysOnTopHint) self.show() def file_open_video(self): """ Open a video file. Should finish current spotter if any by closing it to allow all frames/settings to be saved properly. Then instantiate a new spotter. TODO: Open file dialog in a useful folder. E.g. store the last used one """ # Windows 7 uses 'HOMEPATH' instead of 'HOME' #path = os.getenv('HOME') #if not path: # path = os.getenv('HOMEPATH') filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Video', './recordings') # path if len(filename): self.log.debug('File dialog given %s', str(filename)) self.spotter.grabber.start(str(filename)) def file_open_device(self): """ Open camera as frame source """ self.spotter.grabber.start(source=0, size=(640, 360)) def closeEvent(self, event): """ Exiting the interface has to kill the spotter class and subclasses properly, especially the writer and serial handles, otherwise division by zero might be imminent. """ if NO_EXIT_CONFIRMATION: reply = QtGui.QMessageBox.Yes else: reply = QtGui.QMessageBox.question(self, 'Exiting...', 'Are you sure?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: self.spotter.exit() event.accept() else: event.ignore() ############################################################################### ## TEMPLATES handling ############################################################################### def parse_config(self, path, run_validate=True): """ Template parsing and validation. """ template = configobj.ConfigObj(path, file_error=True, stringify=True, configspec=DIR_SPECIFICATION) if run_validate: validator = validate.Validator() results = template.validate(validator) if not results is True: self.log.error("Template error in file %s", path) for (section_list, key, _) in configobj.flatten_errors(template, results): if key is not None: self.log.error( 'The "%s" key in the section "%s" failed validation', key, ', '.join(section_list)) else: self.log.error('The following section was missing:%s ', ', '.join(section_list)) return None return template def load_config(self, filename=None, directory=DIR_TEMPLATES): """ Opens file dialog to choose template file and starts parsing it """ if filename is None: filename = str( QtGui.QFileDialog.getOpenFileName(self, 'Open Template', directory)) if not len(filename): return None self.log.debug("Opening template %s", filename) template = self.parse_config(filename) if template is not None: abs_pos = template['TEMPLATE']['absolute_positions'] for f_key, f_val in template['FEATURES'].items(): self.side_bar.add_feature(f_val, f_key, focus_new=False) for o_key, o_val in template['OBJECTS'].items(): self.side_bar.add_object(o_val, o_key, focus_new=False) for r_key, r_val in template['REGIONS'].items(): self.side_bar.add_region(r_val, r_key, shapes=template['SHAPES'], abs_pos=abs_pos, focus_new=False) def save_config(self, filename=None, directory=DIR_TEMPLATES): """ Store a full set of configuration to file. """ config = configobj.ConfigObj(indent_type=' ') if filename is None: filename = str( QtGui.QFileDialog.getSaveFileName(self, 'Save Template', directory)) if not len(filename): return config.filename = filename # General options and comment config['TEMPLATE'] = {} config['TEMPLATE']['name'] = filename config['TEMPLATE']['date'] = '_'.join(map(str, time.localtime())[0:3]) config['TEMPLATE']['description'] = 'new template' config['TEMPLATE']['absolute_positions'] = True config['TEMPLATE']['resolution'] = self.spotter.grabber.size # Features config['FEATURES'] = {} for f in self.spotter.tracker.leds: section = { 'type': 'LED', 'range_hue': f.range_hue, 'range_sat': f.range_sat, 'range_val': f.range_val, 'range_area': f.range_area, 'fixed_pos': f.fixed_pos } config['FEATURES'][str(f.label)] = section # Objects config['OBJECTS'] = {} for o in self.spotter.tracker.oois: features = [f.label for f in o.linked_leds] analog_out = len(o.magnetic_signals) > 0 section = {'features': features, 'analog_out': analog_out} if analog_out: section['analog_signal'] = [s[0] for s in o.magnetic_signals] section['pin_pref'] = [s[1] for s in o.magnetic_signals] section['trace'] = o.traced config['OBJECTS'][str(o.label)] = section # Shapes shapelist = [] #rng = (self.gl_frame.width, self.gl_frame.height) for r in self.spotter.tracker.rois: for s in r.shapes: if not s in shapelist: shapelist.append(s) config['SHAPES'] = {} for s in shapelist: section = {'p1': s.points[0], 'p2': s.points[1], 'type': s.shape} # if one would store the points normalized instead of absolute # But that would require setting the flag in TEMPLATES section #section = {'p1': geom.norm_points(s.points[0], rng), # 'p2': geom.norm_points(s.points[1], rng), # 'type': s.shape} config['SHAPES'][str(s.label)] = section # Regions config['REGIONS'] = {} for r in self.spotter.tracker.rois: mo = r.magnetic_objects section = { 'shapes': [s.label for s in r.shapes], 'digital_out': True, 'digital_collision': [o[0].label for o in mo], 'pin_pref': [o[1] for o in mo], 'color': r.active_color[0:3] } config['REGIONS'][str(r.label)] = section config['SERIAL'] = {} config['SERIAL']['auto'] = self.spotter.chatter.auto config['SERIAL']['last_port'] = self.spotter.chatter.serial_port # and finally config.write()
def __init__(self, *args, **kwargs): # , source, destination, fps, size, gui, serial self.log = logging.getLogger(__name__) QtGui.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) # Spotter main class, handles Grabber, Writer, Tracker, Chatter # self.spotter_queue= multiprocessing.Queue(16) self.__spotter_ref = Spotter(*args, **kwargs) # Status Bar self.status_bar = StatusBar(self) self.statusBar().addWidget(self.status_bar) # Side bar widget self.side_bar = SideBar.SideBar(self) self.ui.frame_parameters.addWidget(self.side_bar) # Exit Signals #self.ui.actionE_xit.setShortcut('Ctrl+Q') #self.ui.actionE_xit.setStatusTip('Exit Spotter') #self.connect(self.ui.actionE_xit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()')) # About window self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about) # Open File self.connect(self.ui.actionFile, QtCore.SIGNAL('toggled(bool)'), self.file_open_video) # Open Video self.connect(self.ui.actionCamera, QtCore.SIGNAL('toggled(bool)'), self.file_open_device) # Configuration # Load template self.connect(self.ui.actionLoadConfig, QtCore.SIGNAL('triggered()'), self.load_config) # Save template self.connect(self.ui.actionSaveConfig, QtCore.SIGNAL('triggered()'), self.save_config) #remove all templates self.connect(self.ui.actionRemoveTemplate, QtCore.SIGNAL('triggered()'), self.side_bar.remove_all_tabs) #turns GUI on/off --> stabilizes framerate self.connect(self.ui.actionGUI_on_off, QtCore.SIGNAL('toggled(bool)'), self.GUI_timers) # Toolbar items #record video self.connect(self.ui.actionRecord, QtCore.SIGNAL('toggled(bool)'), self.record_video) #record data log self.connect(self.ui.actionLogger, QtCore.SIGNAL('toggled(bool)'), self.start_log) #outputs the results for each object to a separate figure self.connect(self.ui.actionReset, QtCore.SIGNAL('triggered()'), self.reset_hist) #clears output history, and resets filters self.connect(self.ui.actionGraph, QtCore.SIGNAL('triggered()'), self.output_graph) #show action properties #self.connect(self.ui.actionSourceProperties, QtCore.SIGNAL('triggered()'),self.props) # Serial/Arduino Connection status indicator self.arduino_indicator = SerialIndicator(self.spotter.chatter) self.ui.toolBar.addWidget(self.arduino_indicator) # OpenGL frame self.gl_frame = GLFrame(AA=True) self.ui.frame_video.addWidget(self.gl_frame) self.gl_frame.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) # handling mouse events by the tabs for selection of regions etc. self.gl_frame.sig_event.connect(self.mouse_event_to_tab) # Loading template list in folder default_path = os.path.join(os.path.abspath(DIR_CONFIG), DEFAULT_TEMPLATE) self.template_default = self.parse_config(default_path, True) #list_of_files = [f for f in os.listdir(DIR_TEMPLATES) if f.lower().endswith('ini')] # Main Window states self.center_window() self.connect(self.ui.actionOnTop, QtCore.SIGNAL('toggled(bool)'), self.toggle_window_on_top) #Outputs FPS signal self.connect(self.ui.actionFPS_test, QtCore.SIGNAL('toggled(bool)'), self.trackFPS) #asynchronously updates GUI self.connect(self.ui.actionSpeed_up, QtCore.SIGNAL('toggled(bool)'), self.speedUp) # Starts main frame grabber loop self.timerGL = QtCore.QTimer(self) self.timerGL.timeout.connect(self.refresh) self.timerSide = QtCore.QTimer(self) self.timerSide.timeout.connect(self.side_bar.update_current_page) # self.stopwatch = QtCore.QElapsedTimer() self.stopwatch.start() #Main timer for updating Spotter self.timer2 = QtCore.QTimer(self) self.timer2.timeout.connect(self.spotterUpdate) #SPOTTER_REFRESH_INTERVAL=int(1000.0/self.spotter.grabber.capture.get(5)) self.timer2.start(SPOTTER_REFRESH_INTERVAL) self.ui.actionSpeed_up.setChecked(True) self.ui.actionFPS_test.setChecked(True)
class Main(QtGui.QMainWindow): gui_refresh_offset = 0 avg_fps = 0 frame_counter = 0 async = False frames_to_skip = 0 #only updates GUI in every x frame __spotter_ref = None def __init__(self, *args, **kwargs): # , source, destination, fps, size, gui, serial self.log = logging.getLogger(__name__) QtGui.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) # Spotter main class, handles Grabber, Writer, Tracker, Chatter # self.spotter_queue= multiprocessing.Queue(16) self.__spotter_ref = Spotter(*args, **kwargs) # Status Bar self.status_bar = StatusBar(self) self.statusBar().addWidget(self.status_bar) # Side bar widget self.side_bar = SideBar.SideBar(self) self.ui.frame_parameters.addWidget(self.side_bar) # Exit Signals #self.ui.actionE_xit.setShortcut('Ctrl+Q') #self.ui.actionE_xit.setStatusTip('Exit Spotter') #self.connect(self.ui.actionE_xit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()')) # About window self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about) # Open File self.connect(self.ui.actionFile, QtCore.SIGNAL('toggled(bool)'), self.file_open_video) # Open Video self.connect(self.ui.actionCamera, QtCore.SIGNAL('toggled(bool)'), self.file_open_device) # Configuration # Load template self.connect(self.ui.actionLoadConfig, QtCore.SIGNAL('triggered()'), self.load_config) # Save template self.connect(self.ui.actionSaveConfig, QtCore.SIGNAL('triggered()'), self.save_config) #remove all templates self.connect(self.ui.actionRemoveTemplate, QtCore.SIGNAL('triggered()'), self.side_bar.remove_all_tabs) #turns GUI on/off --> stabilizes framerate self.connect(self.ui.actionGUI_on_off, QtCore.SIGNAL('toggled(bool)'), self.GUI_timers) # Toolbar items #record video self.connect(self.ui.actionRecord, QtCore.SIGNAL('toggled(bool)'), self.record_video) #record data log self.connect(self.ui.actionLogger, QtCore.SIGNAL('toggled(bool)'), self.start_log) #outputs the results for each object to a separate figure self.connect(self.ui.actionReset, QtCore.SIGNAL('triggered()'), self.reset_hist) #clears output history, and resets filters self.connect(self.ui.actionGraph, QtCore.SIGNAL('triggered()'), self.output_graph) #show action properties #self.connect(self.ui.actionSourceProperties, QtCore.SIGNAL('triggered()'),self.props) # Serial/Arduino Connection status indicator self.arduino_indicator = SerialIndicator(self.spotter.chatter) self.ui.toolBar.addWidget(self.arduino_indicator) # OpenGL frame self.gl_frame = GLFrame(AA=True) self.ui.frame_video.addWidget(self.gl_frame) self.gl_frame.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) # handling mouse events by the tabs for selection of regions etc. self.gl_frame.sig_event.connect(self.mouse_event_to_tab) # Loading template list in folder default_path = os.path.join(os.path.abspath(DIR_CONFIG), DEFAULT_TEMPLATE) self.template_default = self.parse_config(default_path, True) #list_of_files = [f for f in os.listdir(DIR_TEMPLATES) if f.lower().endswith('ini')] # Main Window states self.center_window() self.connect(self.ui.actionOnTop, QtCore.SIGNAL('toggled(bool)'), self.toggle_window_on_top) #Outputs FPS signal self.connect(self.ui.actionFPS_test, QtCore.SIGNAL('toggled(bool)'), self.trackFPS) #asynchronously updates GUI self.connect(self.ui.actionSpeed_up, QtCore.SIGNAL('toggled(bool)'), self.speedUp) # Starts main frame grabber loop self.timerGL = QtCore.QTimer(self) self.timerGL.timeout.connect(self.refresh) self.timerSide = QtCore.QTimer(self) self.timerSide.timeout.connect(self.side_bar.update_current_page) # self.stopwatch = QtCore.QElapsedTimer() self.stopwatch.start() #Main timer for updating Spotter self.timer2 = QtCore.QTimer(self) self.timer2.timeout.connect(self.spotterUpdate) #SPOTTER_REFRESH_INTERVAL=int(1000.0/self.spotter.grabber.capture.get(5)) self.timer2.start(SPOTTER_REFRESH_INTERVAL) self.ui.actionSpeed_up.setChecked(True) self.ui.actionFPS_test.setChecked(True) @property def spotter(self): return self.__spotter_ref ############################################################################### ## FRAME RELATED ############################################################################### def trackFPS(self, state): """Outputs a digital signal on D3 for the frame rate (each state change is a frame)""" p = self.spotter.chatter.pins('digital') if len(p) > 0: if state: if len(p) > 0 and p[-1].slot is not None: p[-1].slot.detach_pin() self.log.debug("D3 pin detached from object.") self.spotter.fpstest.attach_pin(p[-1]) self.spotter.FPStest = True self.log.debug("FPS tracking started on D3 pin.") else: self.spotter.FPStest = False self.spotter.fpstest.deattach_pin() self.log.debug("FPS tracking stopped on D3 pin.") return def spotterUpdate(self): if self.spotter.update() is None: return if self.spotter.spotterelapsed > 0: self.avg_fps = self.avg_fps * 0.95 + 0.05 * 1000. / self.spotter.spotterelapsed else: self.avg_fps = 0 self.status_bar.update_fps(self.avg_fps) if self.spotter.GUI_off == False: if self. async == False: if self.frame_counter < self.frames_to_skip: self.frame_counter = self.frame_counter + 1 else: self.frame_counter = 0 self.refresh() self.side_bar.update_current_page()
class Main(QtGui.QMainWindow): gui_refresh_offset = 0 __spotter_ref = None def __init__(self, *args, **kwargs): # , source, destination, fps, size, gui, serial self.log = logging.getLogger(__name__) QtGui.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) # Spotter main class, handles Grabber, Writer, Tracker, Chatter self.__spotter_ref = Spotter(*args, **kwargs) # Status Bar self.status_bar = StatusBar(self) self.statusBar().addWidget(self.status_bar) # Side bar widget self.side_bar = SideBar.SideBar(self) self.ui.frame_parameters.addWidget(self.side_bar) # Exit Signals self.ui.actionE_xit.setShortcut('Ctrl+Q') self.ui.actionE_xit.setStatusTip('Exit Spotter') self.connect(self.ui.actionE_xit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()')) # About window self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self.about) # Menu Bar items # File self.connect(self.ui.actionFile, QtCore.SIGNAL('triggered()'), self.file_open_video) self.connect(self.ui.actionCamera, QtCore.SIGNAL('triggered()'), self.file_open_device) # Configuration self.connect(self.ui.actionLoadConfig, QtCore.SIGNAL('triggered()'), self.load_config) self.connect(self.ui.actionSaveConfig, QtCore.SIGNAL('triggered()'), self.save_config) self.connect(self.ui.actionRemoveTemplate, QtCore.SIGNAL('triggered()'), self.side_bar.remove_all_tabs) # Toolbar items self.connect(self.ui.actionRecord, QtCore.SIGNAL('toggled(bool)'), self.record_video) self.connect(self.ui.actionSourceProperties, QtCore.SIGNAL('triggered()'), self.spotter.grabber.get_capture_properties) # Serial/Arduino Connection status indicator self.arduino_indicator = SerialIndicator(self.spotter.chatter) self.ui.toolBar.addWidget(self.arduino_indicator) # OpenGL frame self.gl_frame = GLFrame(AA=True) self.ui.frame_video.addWidget(self.gl_frame) self.gl_frame.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) # handling mouse events by the tabs for selection of regions etc. self.gl_frame.sig_event.connect(self.mouse_event_to_tab) # Loading template list in folder default_path = os.path.join(os.path.abspath(DIR_CONFIG), DEFAULT_TEMPLATE) self.template_default = self.parse_config(default_path, True) #list_of_files = [f for f in os.listdir(DIR_TEMPLATES) if f.lower().endswith('ini')] # Main Window states self.center_window() self.connect(self.ui.actionOnTop, QtCore.SIGNAL('toggled(bool)'), self.toggle_window_on_top) # Starts main frame grabber loop self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.refresh) self.timer.start(GUI_REFRESH_INTERVAL) self.stopwatch = QtCore.QElapsedTimer() self.stopwatch.start() @property def spotter(self): return self.__spotter_ref ############################################################################### ## FRAME REFRESH ############################################################################### def refresh(self): # TODO: I ain't got no clue as to why reducing the interval drastically improves the frame rate # TODO: Maybe the interval immediately resets the counter and starts it up? elapsed = self.stopwatch.restart() #self.log.debug("Updating spotter") if self.spotter.update() is None: pass #return # update the OpenGL frame #self.log.debug("Updating GL") if not (self.gl_frame.width and self.gl_frame.height): return self.gl_frame.update_world(self.spotter) # Update the currently open tab #self.log.debug("Updating side bar") self.side_bar.update_current_page() # check if the refresh rate needs adjustment #self.log.debug("Updating GUI refresh rate") self.adjust_refresh_rate() # based on stopwatch, show GUI refresh rate #self.log.debug("Updating GUI refresh rate display") self.status_bar.update_fps(elapsed) def adjust_refresh_rate(self, forced=None): """ Change GUI refresh rate according to frame rate of video source, or keep at 1000/GUI_REFRESH_INTERVAL Hz for cameras to not miss too many frames """ self.gui_refresh_offset = self.status_bar.sb_offset.value() if forced is not None: self.timer.setInterval(forced) return if self.spotter.source_type == 'file': if not self.status_bar.sb_offset.isEnabled(): self.status_bar.sb_offset.setEnabled(True) try: interval = int(1000.0/self.spotter.grabber.fps) + self.gui_refresh_offset except (ValueError, TypeError): interval = 0 if interval < 0: interval = 1 self.status_bar.sb_offset.setValue(interval - int(1000.0/self.spotter.grabber.fps)) if self.spotter.grabber.fps != 0 and self.timer.interval() != interval: self.timer.setInterval(interval) self.log.debug("Changed main loop update rate to match file. New: %d", self.timer.interval()) else: if self.status_bar.sb_offset.isEnabled(): self.status_bar.sb_offset.setEnabled(False) #self.status_bar.sb_offset.setValue(0) if self.timer.interval() != GUI_REFRESH_INTERVAL: self.timer.setInterval(GUI_REFRESH_INTERVAL) self.log.debug("Changed main loop update rate to be fast. New: %d", self.timer.interval()) def record_video(self, state, filename=None): """ Control recording of grabbed video. """ # TODO: Select output video file name. self.log.debug("Toggling writer recording state") if state: if filename is None: filename = QtGui.QFileDialog.getSaveFileName(self, 'Open Video', './recordings/') if len(filename): self.spotter.start_writer(str(filename)+'.avi') else: self.spotter.stop_writer() def mouse_event_to_tab(self, event_type, event): """ Hand the mouse event to the active tab. Tabs may handle mouse events differently, and depending on internal states (e.g. selections) """ current_tab = self.side_bar.get_child_page() if current_tab: try: if current_tab.accept_events: current_tab.process_event(event_type, event) except AttributeError: pass def about(self): """ About message box. Credits. Links. Jokes. """ QtGui.QMessageBox.about(self, "About", """<b>Spotter</b> v%s <p>Copyright © 2012-2013 <a href=mailto:[email protected]>Ronny Eichler</a>. <p>This application is under heavy development. Use at your own risk. <p>Python %s - PyQt4 version %s - on %s""" % (__version__, platform.python_version(), QtCore.QT_VERSION_STR, platform.system())) def center_window(self): """ Centers main window on screen. Doesn't quite work on multi-monitor setups, as the whole screen-area is taken. But as long as the window ends up in a predictable position... """ screen = QtGui.QDesktopWidget().screenGeometry() window_size = self.geometry() self.move((screen.width() - window_size.width()) / 2, (screen.height() - window_size.height()) / 2) def toggle_window_on_top(self, state): """ Have main window stay on top. According to the setWindowFlags documentation, the window will hide after changing flags, requiring either a .show() or a .raise(). These may have different behaviors on different platforms!""" # TODO: Test on Linux, OSX, Win8 if state: self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) self.show() else: self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowStaysOnTopHint) self.show() def file_open_video(self): """ Open a video file. Should finish current spotter if any by closing it to allow all frames/settings to be saved properly. Then instantiate a new spotter. TODO: Open file dialog in a useful folder. E.g. store the last used one """ # Windows 7 uses 'HOMEPATH' instead of 'HOME' #path = os.getenv('HOME') #if not path: # path = os.getenv('HOMEPATH') filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Video', './recordings') # path if len(filename): self.log.debug('File dialog given %s', str(filename)) self.spotter.grabber.start(str(filename)) def file_open_device(self): """ Open camera as frame source """ self.spotter.grabber.start(source=0, size=(640, 360)) def closeEvent(self, event): """ Exiting the interface has to kill the spotter class and subclasses properly, especially the writer and serial handles, otherwise division by zero might be imminent. """ if NO_EXIT_CONFIRMATION: reply = QtGui.QMessageBox.Yes else: reply = QtGui.QMessageBox.question(self, 'Exiting...', 'Are you sure?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: self.spotter.exit() event.accept() else: event.ignore() ############################################################################### ## TEMPLATES handling ############################################################################### def parse_config(self, path, run_validate=True): """ Template parsing and validation. """ template = configobj.ConfigObj(path, file_error=True, stringify=True, configspec=DIR_SPECIFICATION) if run_validate: validator = validate.Validator() results = template.validate(validator) if not results is True: self.log.error("Template error in file %s", path) for (section_list, key, _) in configobj.flatten_errors(template, results): if key is not None: self.log.error('The "%s" key in the section "%s" failed validation', key, ', '.join(section_list)) else: self.log.error('The following section was missing:%s ', ', '.join(section_list)) return None return template def load_config(self, filename=None, directory=DIR_TEMPLATES): """ Opens file dialog to choose template file and starts parsing it """ if filename is None: filename = str(QtGui.QFileDialog.getOpenFileName(self, 'Open Template', directory)) if not len(filename): return None self.log.debug("Opening template %s", filename) template = self.parse_config(filename) if template is not None: abs_pos = template['TEMPLATE']['absolute_positions'] for f_key, f_val in template['FEATURES'].items(): self.side_bar.add_feature(f_val, f_key, focus_new=False) for o_key, o_val in template['OBJECTS'].items(): self.side_bar.add_object(o_val, o_key, focus_new=False) for r_key, r_val in template['REGIONS'].items(): self.side_bar.add_region(r_val, r_key, shapes=template['SHAPES'], abs_pos=abs_pos, focus_new=False) def save_config(self, filename=None, directory=DIR_TEMPLATES): """ Store a full set of configuration to file. """ config = configobj.ConfigObj(indent_type=' ') if filename is None: filename = str(QtGui.QFileDialog.getSaveFileName(self, 'Save Template', directory)) if not len(filename): return config.filename = filename # General options and comment config['TEMPLATE'] = {} config['TEMPLATE']['name'] = filename config['TEMPLATE']['date'] = '_'.join(map(str, time.localtime())[0:3]) config['TEMPLATE']['description'] = 'new template' config['TEMPLATE']['absolute_positions'] = True config['TEMPLATE']['resolution'] = self.spotter.grabber.size # Features config['FEATURES'] = {} for f in self.spotter.tracker.leds: section = {'type': 'LED', 'range_hue': f.range_hue, 'range_sat': f.range_sat, 'range_val': f.range_val, 'range_area': f.range_area, 'fixed_pos': f.fixed_pos} config['FEATURES'][str(f.label)] = section # Objects config['OBJECTS'] = {} for o in self.spotter.tracker.oois: features = [f.label for f in o.linked_leds] analog_out = len(o.magnetic_signals) > 0 section = {'features': features, 'analog_out': analog_out} if analog_out: section['analog_signal'] = [s[0] for s in o.magnetic_signals] section['pin_pref'] = [s[1] for s in o.magnetic_signals] section['trace'] = o.traced config['OBJECTS'][str(o.label)] = section # Shapes shapelist = [] #rng = (self.gl_frame.width, self.gl_frame.height) for r in self.spotter.tracker.rois: for s in r.shapes: if not s in shapelist: shapelist.append(s) config['SHAPES'] = {} for s in shapelist: section = {'p1': s.points[0], 'p2': s.points[1], 'type': s.shape} # if one would store the points normalized instead of absolute # But that would require setting the flag in TEMPLATES section #section = {'p1': geom.norm_points(s.points[0], rng), # 'p2': geom.norm_points(s.points[1], rng), # 'type': s.shape} config['SHAPES'][str(s.label)] = section # Regions config['REGIONS'] = {} for r in self.spotter.tracker.rois: mo = r.magnetic_objects section = {'shapes': [s.label for s in r.shapes], 'digital_out': True, 'digital_collision': [o[0].label for o in mo], 'pin_pref': [o[1] for o in mo], 'color': r.active_color[0:3]} config['REGIONS'][str(r.label)] = section config['SERIAL'] = {} config['SERIAL']['auto'] = self.spotter.chatter.auto config['SERIAL']['last_port'] = self.spotter.chatter.serial_port # and finally config.write()