class MainWindow(QMainWindow): def __init__(self, labeltool, parent=None): QMainWindow.__init__(self, parent) self.idletimer = QTimer() self.loader = None self.labeltool = labeltool self.setupGui() self.loadApplicationSettings() self.onAnnotationsLoaded() # Slots def onPluginLoaded(self, action): self.ui.menuPlugins.addAction(action) def onStatusMessage(self, message=''): self.statusBar().showMessage(message, 5000) def onModelDirtyChanged(self, dirty): postfix = "[+]" if dirty else "" if self.labeltool.getCurrentFilename() is not None: self.setWindowTitle("%s - %s %s" % \ (APP_NAME, QFileInfo(self.labeltool.getCurrentFilename()).fileName(), postfix)) else: self.setWindowTitle("%s - Unnamed %s" % (APP_NAME, postfix)) def onMousePositionChanged(self, x, y): self.posinfo.setText("%d, %d" % (x, y)) def startBackgroundLoading(self): self.stopBackgroundLoading(forced=True) self.loader = BackgroundLoader(self.labeltool.model(), self.statusBar(), self.sb_progress) self.idletimer.timeout.connect(self.loader.load) self.loader.finished.connect(self.stopBackgroundLoading) self.statusBar().addWidget(self.sb_progress) self.sb_progress.show() self.idletimer.start() def stopBackgroundLoading(self, forced=False): if not forced: self.statusBar().showMessage("Background loading finished", 5000) self.idletimer.stop() if self.loader is not None: self.idletimer.timeout.disconnect(self.loader.load) self.statusBar().removeWidget(self.sb_progress) self.loader = None def onAnnotationsLoaded(self): self.labeltool.model().dirtyChanged.connect(self.onModelDirtyChanged) self.onModelDirtyChanged(self.labeltool.model().dirty()) self.treeview.setModel(self.labeltool.model()) self.scene.setModel(self.labeltool.model()) self.selectionmodel = QItemSelectionModel(self.labeltool.model()) self.treeview.setSelectionModel(self.selectionmodel) self.treeview.selectionModel().currentChanged.connect(self.labeltool.setCurrentImage) self.property_editor.onModelChanged(self.labeltool.model()) self.startBackgroundLoading() def onCurrentImageChanged(self): new_image = self.labeltool.currentImage() self.scene.setCurrentImage(new_image) self.onFitToWindowModeChanged() self.treeview.scrollTo(new_image.index()) img = self.labeltool.getImage(new_image) if img == None: self.controls.setFilename("") self.selectionmodel.setCurrentIndex(new_image.index(), QItemSelectionModel.ClearAndSelect|QItemSelectionModel.Rows) return h = img.shape[0] w = img.shape[1] self.image_resolution.setText("%dx%d" % (w, h)) # TODO: This info should be obtained from AnnotationModel or LabelTool if isinstance(new_image, FrameModelItem): self.controls.setFrameNumAndTimestamp(new_image.framenum(), new_image.timestamp()) elif isinstance(new_image, ImageFileModelItem): self.controls.setFilename(os.path.basename(new_image['filename'])) self.selectionmodel.setCurrentIndex(new_image.index(), QItemSelectionModel.ClearAndSelect|QItemSelectionModel.Rows) def onFitToWindowModeChanged(self): if self.options["Fit-to-window mode"].isChecked(): self.view.fitInView() def onScaleChanged(self, scale): self.zoominfo.setText("%.2f%%" % (100 * scale, )) def initShortcuts(self, HOTKEYS): self.shortcuts = [] for hotkey in HOTKEYS: assert len(hotkey) >= 2 key = hotkey[0] fun = hotkey[1] desc = "" if len(hotkey) > 2: desc = hotkey[2] if type(fun) == str: fun = import_callable(fun) hk = QAction(desc, self) hk.setShortcut(QKeySequence(key)) hk.setEnabled(True) if hasattr(fun, '__call__'): hk.triggered.connect(bind(fun, self.labeltool)) else: hk.triggered.connect(compose_noargs([bind(f, self.labeltool) for f in fun])) self.ui.menuShortcuts.addAction(hk) self.shortcuts.append(hk) def initOptions(self): self.options = {} for o in ["Fit-to-window mode"]: action = QAction(o, self) action.setCheckable(True) self.ui.menuOptions.addAction(action) self.options[o] = action ### ### GUI/Application setup ###___________________________________________________________________________________________ def setupGui(self): self.ui = uic.loadUi(os.path.join(GUIDIR, "labeltool.ui"), self) # get inserters and items from labels # FIXME for handling the new-style config correctly inserters = dict([(label['attributes']['class'], label['inserter']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'inserter' in label]) items = dict([(label['attributes']['class'], label['item']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'item' in label]) # Property Editor self.property_editor = PropertyEditor(config.LABELS) self.ui.dockProperties.setWidget(self.property_editor) # Scene self.scene = AnnotationScene(self.labeltool, items=items, inserters=inserters) self.property_editor.insertionModeStarted.connect(self.scene.onInsertionModeStarted) self.property_editor.insertionModeEnded.connect(self.scene.onInsertionModeEnded) # SceneView self.view = GraphicsView(self) self.view.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.view.setScene(self.scene) self.central_widget = QWidget() self.central_layout = QVBoxLayout() self.controls = ControlButtonWidget() self.controls.back_button.clicked.connect(self.labeltool.gotoPrevious) self.controls.forward_button.clicked.connect(self.labeltool.gotoNext) self.central_layout.addWidget(self.controls) self.central_layout.addWidget(self.view) self.central_widget.setLayout(self.central_layout) self.setCentralWidget(self.central_widget) self.initShortcuts(config.HOTKEYS) self.initOptions() self.treeview = AnnotationTreeView() self.treeview.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) self.ui.dockAnnotations.setWidget(self.treeview) self.scene.selectionChanged.connect(self.scene.onSelectionChanged) self.treeview.selectedItemsChanged.connect(self.scene.onSelectionChangedInTreeView) self.posinfo = QLabel("-1, -1") self.posinfo.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.posinfo) self.scene.mousePositionChanged.connect(self.onMousePositionChanged) self.image_resolution = QLabel("[no image]") self.image_resolution.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.image_resolution) self.zoominfo = QLabel() self.zoominfo.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.zoominfo) self.view.scaleChanged.connect(self.onScaleChanged) self.onScaleChanged(self.view.getScale()) self.sb_progress = QProgressBar() # View menu self.ui.menu_Views.addAction(self.ui.dockProperties.toggleViewAction()) self.ui.menu_Views.addAction(self.ui.dockAnnotations.toggleViewAction()) # Show the UI. It is important that this comes *after* the above # adding of custom widgets, especially the central widget. Otherwise the # dock widgets would be far to large. self.ui.show() ## connect action signals self.connectActions() def connectActions(self): ## File menu self.ui.actionNew. triggered.connect(self.fileNew) self.ui.actionOpen. triggered.connect(self.fileOpen) self.ui.actionSave. triggered.connect(self.fileSave) self.ui.actionSave_As.triggered.connect(self.fileSaveAs) self.ui.actionExit. triggered.connect(self.close) ## View menu self.ui.actionLocked.toggled.connect(self.onViewsLockedChanged) ## Help menu self.ui.action_About.triggered.connect(self.about) ## Navigation self.ui.action_Add_Image.triggered.connect(self.addMediaFile) self.ui.actionNext. triggered.connect(self.labeltool.gotoNext) self.ui.actionPrevious. triggered.connect(self.labeltool.gotoPrevious) self.ui.actionZoom_In. triggered.connect(functools.partial(self.view.setScaleRelative, 1.2)) self.ui.actionZoom_Out. triggered.connect(functools.partial(self.view.setScaleRelative, 1/1.2)) ## Connections to LabelTool self.labeltool.pluginLoaded. connect(self.onPluginLoaded) self.labeltool.statusMessage. connect(self.onStatusMessage) self.labeltool.annotationsLoaded. connect(self.onAnnotationsLoaded) self.labeltool.currentImageChanged.connect(self.onCurrentImageChanged) ## options menu self.options["Fit-to-window mode"].changed.connect(self.onFitToWindowModeChanged) def loadApplicationSettings(self): settings = QSettings() size = settings.value("MainWindow/Size", QSize(800, 600)) pos = settings.value("MainWindow/Position", QPoint(10, 10)) state = settings.value("MainWindow/State") locked = settings.value("MainWindow/ViewsLocked", False) if isinstance(size, QVariant): size = size.toSize() if isinstance(pos, QVariant): pos = pos.toPoint() if isinstance(state, QVariant): state = state.toByteArray() if isinstance(locked, QVariant): locked = locked.toBool() self.resize(size) self.move(pos) if state is not None: self.restoreState(state) self.ui.actionLocked.setChecked(bool(locked)) def saveApplicationSettings(self): settings = QSettings() settings.setValue("MainWindow/Size", self.size()) settings.setValue("MainWindow/Position", self.pos()) settings.setValue("MainWindow/State", self.saveState()) settings.setValue("MainWindow/ViewsLocked", self.ui.actionLocked.isChecked()) if self.labeltool.getCurrentFilename() is not None: filename = self.labeltool.getCurrentFilename() else: filename = None settings.setValue("LastFile", filename) def okToContinue(self): if self.labeltool.model().dirty(): reply = QMessageBox.question(self, "%s - Unsaved Changes" % (APP_NAME), "Save unsaved changes?", QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if reply == QMessageBox.Cancel: return False elif reply == QMessageBox.Yes: return self.fileSave() return True def fileNew(self): if self.okToContinue(): self.labeltool.clearAnnotations() def fileOpen(self): if not self.okToContinue(): return path = '.' filename = self.labeltool.getCurrentFilename() if (filename is not None) and (len(filename) > 0): path = QFileInfo(filename).path() format_str = ' '.join(self.labeltool.getAnnotationFilePatterns()) fname = QFileDialog.getOpenFileName(self, "%s - Load Annotations" % APP_NAME, path, "%s annotation files (%s)" % (APP_NAME, format_str)) if len(str(fname)) > 0: self.labeltool.loadAnnotations(fname) def fileSave(self): filename = self.labeltool.getCurrentFilename() if filename is None: return self.fileSaveAs() return self.labeltool.saveAnnotations(filename) def fileSaveAs(self): fname = '.' # self.annotations.filename() or '.' format_str = ' '.join(self.labeltool.getAnnotationFilePatterns()) fname = QFileDialog.getSaveFileName(self, "%s - Save Annotations" % APP_NAME, fname, "%s annotation files (%s)" % (APP_NAME, format_str)) if len(str(fname)) > 0: return self.labeltool.saveAnnotations(str(fname)) return False def addMediaFile(self): path = '.' filename = self.labeltool.getCurrentFilename() if (filename is not None) and (len(filename) > 0): path = QFileInfo(filename).path() image_types = [ '*.jpg', '*.bmp', '*.png', '*.pgm', '*.ppm', '*.ppm', '*.tif', '*.gif' ] video_types = [ '*.mp4', '*.mpg', '*.mpeg', '*.avi', '*.mov', '*.vob' ] format_str = ' '.join(image_types + video_types) fnames = QFileDialog.getOpenFileNames(self, "%s - Add Media File" % APP_NAME, path, "Media files (%s)" % (format_str, )) item = None numFiles = len(fnames) progress_bar = QProgressDialog('Importing files...', 'Cancel import', 0, numFiles, self) for fname,c in zip(fnames, range(numFiles)): if len(str(fname)) == 0: continue fname = str(fname) if os.path.isabs(fname): fname = os.path.relpath(fname, str(path)) for pattern in image_types: if fnmatch.fnmatch(fname, pattern): item = self.labeltool.addImageFile(fname) progress_bar.setValue(c) if item is None: return self.labeltool.addVideoFile(fname) progress_bar.close() return item def onViewsLockedChanged(self, checked): features = QDockWidget.AllDockWidgetFeatures if checked: features = QDockWidget.NoDockWidgetFeatures self.ui.dockProperties.setFeatures(features) self.ui.dockAnnotations.setFeatures(features) ### ### global event handling ###______________________________________________________________________________ def closeEvent(self, event): if self.okToContinue(): self.saveApplicationSettings() else: event.ignore() def about(self): QMessageBox.about(self, "About %s" % APP_NAME, """<b>%s</b> version %s <p>This labeling application for computer vision research was developed at the CVHCI research group at KIT. <p>For more details, visit our homepage: <a href="%s">%s</a>""" % (APP_NAME, __version__, ORGANIZATION_DOMAIN, ORGANIZATION_DOMAIN))
class MainWindow(QMainWindow): def __init__(self, labeltool, parent=None): QMainWindow.__init__(self, parent) self.idletimer = QTimer() self.loader = None self.labeltool = labeltool self.setupGui() self.loadApplicationSettings() self.onAnnotationsLoaded() # Slots def onPluginLoaded(self, action): self.ui.menuPlugins.addAction(action) def onStatusMessage(self, message=''): self.statusBar().showMessage(message, 5000) def onModelDirtyChanged(self, dirty): postfix = "[+]" if dirty else "" if self.labeltool.getCurrentFilename() is not None: self.setWindowTitle("%s - %s %s" % \ (APP_NAME, QFileInfo(self.labeltool.getCurrentFilename()).fileName(), postfix)) else: self.setWindowTitle("%s - Unnamed %s" % (APP_NAME, postfix)) def onMousePositionChanged(self, x, y): self.posinfo.setText("%d, %d" % (x, y)) def startBackgroundLoading(self): self.stopBackgroundLoading(forced=True) self.loader = BackgroundLoader(self.labeltool.model(), self.statusBar(), self.sb_progress) self.idletimer.timeout.connect(self.loader.load) self.loader.finished.connect(self.stopBackgroundLoading) self.statusBar().addWidget(self.sb_progress) self.sb_progress.show() self.idletimer.start() def stopBackgroundLoading(self, forced=False): if not forced: self.statusBar().showMessage("Background loading finished", 5000) self.idletimer.stop() if self.loader is not None: self.idletimer.timeout.disconnect(self.loader.load) self.statusBar().removeWidget(self.sb_progress) self.loader = None def onAnnotationsLoaded(self): self.labeltool.model().dirtyChanged.connect(self.onModelDirtyChanged) self.onModelDirtyChanged(self.labeltool.model().dirty()) self.treeview.setModel(self.labeltool.model()) self.scene.setModel(self.labeltool.model()) self.selectionmodel = QItemSelectionModel(self.labeltool.model()) self.treeview.setSelectionModel(self.selectionmodel) self.treeview.selectionModel().currentChanged.connect( self.labeltool.setCurrentImage) self.property_editor.onModelChanged(self.labeltool.model()) self.startBackgroundLoading() def onCurrentImageChanged(self): new_image = self.labeltool.currentImage() self.scene.setCurrentImage(new_image) self.onFitToWindowModeChanged() self.treeview.scrollTo(new_image.index()) img = self.labeltool.getImage(new_image) if img == None: self.controls.setFilename("") self.selectionmodel.setCurrentIndex( new_image.index(), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) return h = img.shape[0] w = img.shape[1] self.image_resolution.setText("%dx%d" % (w, h)) # TODO: This info should be obtained from AnnotationModel or LabelTool if isinstance(new_image, FrameModelItem): self.controls.setFrameNumAndTimestamp(new_image.framenum(), new_image.timestamp()) elif isinstance(new_image, ImageFileModelItem): self.controls.setFilename(os.path.basename(new_image['filename'])) self.selectionmodel.setCurrentIndex( new_image.index(), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) def onFitToWindowModeChanged(self): if self.options["Fit-to-window mode"].isChecked(): self.view.fitInView() def onEnumerateCornersModeChanged(self): if self.options["Enumerate-corners mode"].isChecked(): self.scene.enumerateCorners() self.onCurrentImageChanged() else: self.scene.removeCorners() self.onCurrentImageChanged() def onCopyAnnotationsModeChanged(self): if self.annotationMenu["Copy from previous"].isChecked(): self.copyAnnotations.copy() self.annotationMenu["Copy from previous"].setChecked(False) def onInterpolateRangeModeChanged(self): if self.annotationMenu["Interpolate range"].isChecked(): self.interpolateRange.interpolateRange() self.annotationMenu["Interpolate range"].setChecked(False) def onScaleChanged(self, scale): self.zoominfo.setText("%.2f%%" % (100 * scale, )) def initShortcuts(self, HOTKEYS): self.shortcuts = [] for hotkey in HOTKEYS: assert len(hotkey) >= 2 key = hotkey[0] fun = hotkey[1] desc = "" if len(hotkey) > 2: desc = hotkey[2] if type(fun) == str: fun = import_callable(fun) hk = QAction(desc, self) hk.setShortcut(QKeySequence(key)) hk.setEnabled(True) if hasattr(fun, '__call__'): hk.triggered.connect(bind(fun, self.labeltool)) else: hk.triggered.connect( compose_noargs([bind(f, self.labeltool) for f in fun])) self.ui.menuShortcuts.addAction(hk) self.shortcuts.append(hk) def initOptions(self): self.options = {} for o in ["Fit-to-window mode"]: action = QAction(o, self) action.setCheckable(True) self.ui.menuOptions.addAction(action) self.options[o] = action for o in ["Enumerate-corners mode"]: action = QAction(o, self) action.setCheckable(True) self.ui.menuOptions.addAction(action) self.options[o] = action def initAnnotationMenu(self): self.annotationMenu = {} for a in ["Copy from previous"]: action = QAction(a, self) action.setCheckable(True) self.ui.menuAnnotation.addAction(action) self.annotationMenu[a] = action for a in ["Interpolate range"]: action = QAction(a, self) action.setCheckable(True) self.ui.menuAnnotation.addAction(action) self.annotationMenu[a] = action ### ### GUI/Application setup ###___________________________________________________________________________________________ def setupGui(self): self.ui = uic.loadUi(os.path.join(GUIDIR, "labeltool.ui"), self) # get inserters and items from labels # FIXME for handling the new-style config correctly inserters = dict([ (label['attributes']['class'], label['inserter']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'inserter' in label ]) items = dict([ (label['attributes']['class'], label['item']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'item' in label ]) # Property Editor self.property_editor = PropertyEditor(config.LABELS) self.ui.dockProperties.setWidget(self.property_editor) # Scene self.scene = AnnotationScene(self.labeltool, items=items, inserters=inserters) self.property_editor.insertionModeStarted.connect( self.scene.onInsertionModeStarted) self.property_editor.insertionModeEnded.connect( self.scene.onInsertionModeEnded) # SceneView self.view = GraphicsView(self) self.view.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.view.setScene(self.scene) self.central_widget = QWidget() self.central_layout = QVBoxLayout() self.controls = ControlButtonWidget() self.controls.back_button.clicked.connect(self.labeltool.gotoPrevious) self.controls.forward_button.clicked.connect(self.labeltool.gotoNext) self.central_layout.addWidget(self.controls) self.central_layout.addWidget(self.view) self.central_widget.setLayout(self.central_layout) self.setCentralWidget(self.central_widget) self.initShortcuts(config.HOTKEYS) self.initOptions() self.initAnnotationMenu() self.treeview = AnnotationTreeView() self.treeview.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) self.ui.dockAnnotations.setWidget(self.treeview) self.scene.selectionChanged.connect(self.scene.onSelectionChanged) self.treeview.selectedItemsChanged.connect( self.scene.onSelectionChangedInTreeView) self.posinfo = QLabel("-1, -1") self.posinfo.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.posinfo) self.scene.mousePositionChanged.connect(self.onMousePositionChanged) self.image_resolution = QLabel("[no image]") self.image_resolution.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.image_resolution) self.zoominfo = QLabel() self.zoominfo.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.zoominfo) self.view.scaleChanged.connect(self.onScaleChanged) self.onScaleChanged(self.view.getScale()) self.sb_progress = QProgressBar() # View menu self.ui.menu_Views.addAction(self.ui.dockProperties.toggleViewAction()) self.ui.menu_Views.addAction( self.ui.dockAnnotations.toggleViewAction()) # Annotation menu self.copyAnnotations = CopyAnnotations(self.labeltool) self.interpolateRange = InterpolateRange(self.labeltool) # Show the UI. It is important that this comes *after* the above # adding of custom widgets, especially the central widget. Otherwise the # dock widgets would be far to large. self.ui.show() ## connect action signals self.connectActions() def connectActions(self): ## File menu self.ui.actionNew.triggered.connect(self.fileNew) self.ui.actionOpen.triggered.connect(self.fileOpen) self.ui.actionSave.triggered.connect(self.fileSave) self.ui.actionSave_As.triggered.connect(self.fileSaveAs) self.ui.actionExit.triggered.connect(self.close) ## View menu self.ui.actionLocked.toggled.connect(self.onViewsLockedChanged) ## Help menu self.ui.action_About.triggered.connect(self.about) ## Navigation self.ui.action_Add_Image.triggered.connect(self.addMediaFile) self.ui.actionNext.triggered.connect(self.labeltool.gotoNext) self.ui.actionPrevious.triggered.connect(self.labeltool.gotoPrevious) self.ui.actionZoom_In.triggered.connect( functools.partial(self.view.setScaleRelative, 1.2)) self.ui.actionZoom_Out.triggered.connect( functools.partial(self.view.setScaleRelative, 1 / 1.2)) ## Connections to LabelTool self.labeltool.pluginLoaded.connect(self.onPluginLoaded) self.labeltool.statusMessage.connect(self.onStatusMessage) self.labeltool.annotationsLoaded.connect(self.onAnnotationsLoaded) self.labeltool.currentImageChanged.connect(self.onCurrentImageChanged) ## options menu self.options["Fit-to-window mode"].changed.connect( self.onFitToWindowModeChanged) self.options["Enumerate-corners mode"].changed.connect( self.onEnumerateCornersModeChanged) ## annotation menu self.annotationMenu["Copy from previous"].changed.connect( self.onCopyAnnotationsModeChanged) self.annotationMenu["Interpolate range"].changed.connect( self.onInterpolateRangeModeChanged) def loadApplicationSettings(self): settings = QSettings() size = settings.value("MainWindow/Size", QSize(800, 600)) pos = settings.value("MainWindow/Position", QPoint(10, 10)) state = settings.value("MainWindow/State") locked = settings.value("MainWindow/ViewsLocked", False) if isinstance(size, QVariant): size = size.toSize() if isinstance(pos, QVariant): pos = pos.toPoint() if isinstance(state, QVariant): state = state.toByteArray() if isinstance(locked, QVariant): locked = locked.toBool() self.resize(size) self.move(pos) if state is not None: self.restoreState(state) self.ui.actionLocked.setChecked(bool(locked)) def saveApplicationSettings(self): settings = QSettings() settings.setValue("MainWindow/Size", self.size()) settings.setValue("MainWindow/Position", self.pos()) settings.setValue("MainWindow/State", self.saveState()) settings.setValue("MainWindow/ViewsLocked", self.ui.actionLocked.isChecked()) if self.labeltool.getCurrentFilename() is not None: filename = self.labeltool.getCurrentFilename() else: filename = None settings.setValue("LastFile", filename) def okToContinue(self): if self.labeltool.model().dirty(): reply = QMessageBox.question( self, "%s - Unsaved Changes" % (APP_NAME), "Save unsaved changes?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if reply == QMessageBox.Cancel: return False elif reply == QMessageBox.Yes: return self.fileSave() return True def fileNew(self): if self.okToContinue(): self.labeltool.clearAnnotations() def fileOpen(self): if not self.okToContinue(): return path = '.' filename = self.labeltool.getCurrentFilename() if (filename is not None) and (len(filename) > 0): path = QFileInfo(filename).path() format_str = ' '.join(self.labeltool.getAnnotationFilePatterns()) fname = QFileDialog.getOpenFileName( self, "%s - Load Annotations" % APP_NAME, path, "%s annotation files (%s)" % (APP_NAME, format_str)) if len(str(fname)) > 0: self.labeltool.loadAnnotations(fname) def fileSave(self): filename = self.labeltool.getCurrentFilename() if filename is None: return self.fileSaveAs() return self.labeltool.saveAnnotations(filename) def fileSaveAs(self): fname = '.' # self.annotations.filename() or '.' format_str = ' '.join(self.labeltool.getAnnotationFilePatterns()) fname = QFileDialog.getSaveFileName( self, "%s - Save Annotations" % APP_NAME, fname, "%s annotation files (%s)" % (APP_NAME, format_str)) if len(str(fname)) > 0: return self.labeltool.saveAnnotations(str(fname)) return False def addMediaFile(self): filename = self.labeltool.getCurrentFilename() image_types = [ '*.jpg', '*.bmp', '*.png', '*.pgm', '*.ppm', '*.ppm', '*.tif', '*.gif' ] video_types = [ '*.mp4', '*.mpg', '*.mpeg', '*.avi', '*.mov', '*.vob', '*.json' ] format_str = ' '.join(image_types + video_types) fnames = QFileDialog.getOpenFileNames( self, "%s - Add Media File" % APP_NAME, path, "Media files (%s)" % (format_str, )) item = None numFiles = len(fnames) progress_bar = QProgressDialog('Importing files...', 'Cancel import', 0, numFiles, self) for fname, c in zip(fnames, range(numFiles)): if len(str(fname)) == 0: continue fname = str(fname) if os.path.isabs(fname): fname = os.path.relpath(fname) for pattern in image_types: if fnmatch.fnmatch(fname, pattern): item = self.labeltool.addImageFile(fname) progress_bar.setValue(c) if item is None: item = self.labeltool.addVideoFile(fname, progress_bar) progress_bar.close() return item def onViewsLockedChanged(self, checked): features = QDockWidget.AllDockWidgetFeatures if checked: features = QDockWidget.NoDockWidgetFeatures self.ui.dockProperties.setFeatures(features) self.ui.dockAnnotations.setFeatures(features) ### ### global event handling ###______________________________________________________________________________ def closeEvent(self, event): if self.okToContinue(): self.saveApplicationSettings() else: event.ignore() def about(self): QMessageBox.about( self, "About %s" % APP_NAME, """<b>%s</b> version %s <p>This labeling application for computer vision research was developed at the CVHCI research group at KIT. <p>For more details, visit our homepage: <a href="%s">%s</a>""" % (APP_NAME, __version__, ORGANIZATION_DOMAIN, ORGANIZATION_DOMAIN))
class MainWindow(QMainWindow): def __init__(self, labeltool, parent=None): QMainWindow.__init__(self, parent) self.network = Network() self.tool = AnnotationTool(self, self.network) self.logindl = LoginDialog(self.network, self) self.caseList = CaseListDialog(self.network, self.tool) self.setupGui() self.connectActions() self.onAnnotationsLoaded() self.loadApplicationSettings() self.network.caseChanged.connect(self.inftable.onCaseChanged) def setupGui(self): inserters = dict([ (label['attributes']['class'], label['inserter']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'inserter' in label ]) items = dict([ (label['attributes']['class'], label['item']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'item' in label ]) self.ui = uic.loadUi(os.path.join(GUIDIR, "labeltool.ui"), self) self.view = GraphicsView() self.scene = AnnotationScene(self.tool, inserters=inserters, items=items) self.view.setScene(self.scene) self.central_widget = QWidget() self.central_layout = QVBoxLayout() self.controls = ControlButtonWidget() self.controls.back_button.clicked.connect(self.tool.gotoPrevious) self.controls.forward_button.clicked.connect(self.tool.gotoNext) self.central_layout.addWidget(self.controls) self.central_layout.addWidget(self.view) self.central_widget.setLayout(self.central_layout) self.setCentralWidget(self.central_widget) self.initShortcuts(config.HOTKEYS) self.treeview = AnnotationTreeView() self.treeview.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) self.ui.dockAnnotations.setWidget(self.treeview) self.propertyEditor = PropertyEditor(config, self.tool) self.ui.dockProperties.setWidget(self.propertyEditor) self.propertyEditor.insertionModeStarted.connect( self.scene.onInsertionModeStarted) self.propertyEditor.insertionModeEnded.connect( self.scene.onInsertionModeEnded) self.inftable = CaseInformationWidget(self.tool) self.ui.dockInformation.setWidget(self.inftable) self.statusBar = QStatusBar() self.posinfo = QLabel("-1, -1") self.posinfo.setFrameStyle(QFrame.StyledPanel) self.statusBar.addPermanentWidget(self.posinfo) self.image_resolution = QLabel('[No Image]') self.image_resolution.setFrameStyle(QFrame.StyledPanel) self.statusBar.addPermanentWidget(self.image_resolution) self.image_download = QLabel('[Image Download]') self.image_download.setFrameStyle(QFrame.StyledPanel) self.statusBar.addPermanentWidget(self.image_download) self.zoominfo = QLabel() self.zoominfo.setFrameStyle(QFrame.StyledPanel) self.statusBar.addPermanentWidget(self.zoominfo) self.setStatusBar(self.statusBar) def connectActions(self): self.ui.open.triggered.connect(self.onOpen) self.ui.save.triggered.connect(self.onSave) self.ui.submit.triggered.connect(self.onSubmit) self.ui.actionExit.triggered.connect(self.close) self.ui.actionNext.triggered.connect(self.tool.gotoNext) self.ui.actionPrevious.triggered.connect(self.tool.gotoPrevious) # self.ui.actionZoom_In.triggered.connect() # self.ui.actionZoom_Out.triggered.connect() self.ui.actionLogin.triggered.connect(self.login) self.ui.actionLogoff.triggered.connect(self.logoff) self.tool.annotationsLoaded.connect(self.onAnnotationsLoaded) self.tool.currentImageChanged.connect(self.onCurrentImageChanged) def initShortcuts(self, HOTKEYS): self.shortcuts = [] for hotkey in HOTKEYS: assert len(hotkey) >= 2 key = hotkey[0] fun = hotkey[1] desc = "" if len(hotkey) > 2: desc = hotkey[2] if type(fun) == str: fun = import_callable(fun) hk = QAction(desc, self) hk.setShortcut(QKeySequence(key)) hk.setEnabled(True) if hasattr(fun, '__call__'): hk.triggered.connect(bind(fun, self.tool)) else: hk.triggered.connect( compose_noargs([bind(f, self.tool) for f in fun])) self.ui.menuShortcuts.addAction(hk) self.shortcuts.append(hk) def onAnnotationsLoaded(self): self.tool.model().dirtyChanged.connect(self.onModelDirtyChanged) self.onModelDirtyChanged(self.tool.model().dirty()) self.treeview.setModel(self.tool.model()) self.scene.setModel(self.tool.model()) self.selectionmodel = QItemSelectionModel(self.tool.model()) self.treeview.setSelectionModel(self.selectionmodel) self.treeview.selectionModel().currentChanged.connect( self.tool.setCurrentImage) def clear(self): self.tool.clearAnnotations() def login(self): if self.network.isLogin(): if not self.okToContinue(): return else: reply = QMessageBox.question(self, u"提示", u'是否退出当前登陆?', QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.No: return else: self.logoff() self.logindl.showDialog() def logoff(self): if not self.network.isLogin(): return if not self.okToContinue(): return self.clear() self.network.logOff() self.onModelDirtyChanged(False) def onSave(self): print(self.tool.annotations()) out = self.network.labelUpload(self.tool.annotations(), isSubmit=False) if out: self.tool.model().setDirty(False) return out def onSubmit(self): print(self.tool.annotations()) out = self.network.labelUpload(self.tool.annotations(), isSubmit=True) if out: self.tool.clearAnnotations() def onOpen(self): if not self.network.isLogin(): QMessageBox.question(self, 'message', 'Please login first.', QMessageBox.Ok) return if self.okToContinue(): self.tool.clearAnnotations() self.inftable.onCaseChanged(None) self.inftable.onImageChanged(None) self.caseList.show() self.caseList.getData() def onCurrentImageChanged(self): new_image = self.tool.currentImage() self.scene.setCurrentImage(new_image) self.treeview.scrollTo(new_image.index()) self.inftable.onImageChanged(new_image) def okToContinue(self): if self.tool.model().dirty(): reply = QMessageBox.question( self, u"保存标注提示", u'是否保存更改?', QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if reply == QMessageBox.Cancel: return False elif reply == QMessageBox.Yes: return self.onSave() return True def onModelDirtyChanged(self, dirty): postfix = '[+]' if dirty else '' name = self.network.name self.setWindowTitle('%s%s - username:%s' % (APP_NAME, postfix, name)) def loadApplicationSettings(self): settings = QSettings() size = settings.value("MainWindow/Size", QSize(1000, 800)) pos = settings.value("MainWindow/Position", QPoint(10, 10)) state = settings.value("MainWindow/State") locked = settings.value("MainWindow/ViewsLocked", False) if isinstance(size, QVariant): size = size.toSize() if isinstance(pos, QVariant): pos = pos.toPoint() if isinstance(state, QVariant): state = state.toByteArray() if isinstance(locked, QVariant): locked = locked.toBool() self.resize(size) self.move(pos) if state is not None: self.restoreState(state) self.ui.actionLocked.setChecked(bool(locked)) def saveApplicationSettings(self): settings = QSettings() settings.setValue("MainWindow/Size", self.size()) settings.setValue("MainWindow/Position", self.pos()) settings.setValue("MainWindow/State", self.saveState()) settings.setValue("MainWindow/ViewsLocked", self.ui.actionLocked.isChecked()) def closeEvent(self, event): if self.okToContinue(): self.saveApplicationSettings() else: event.ignore() def about(self): QMessageBox.about( self, "About %s" % APP_NAME, """<b>%s</b> version %s <p>This labeling application for computer vision research was developed at the CVHCI research group at KIT. <p>For more details, visit our homepage: <a href="%s">%s</a>""" % (APP_NAME, __version__, ORGANIZATION_DOMAIN, ORGANIZATION_DOMAIN))