Esempio n. 1
0
class UndoDock(DockWidget):

    def __init__(self, parent=None):
        DockWidget.__init__(self, "历史", parent)
        self.setObjectName("undoViewDock")
        self._undo_view = QUndoView(self)
        self._clear_icon = QIcon(":/drive-harddisk.png")

        self._undo_view.setCleanIcon(self._clear_icon)
        self._undo_view.setUniformItemSizes(True)
        self._widget = QWidget(self)
        self._layout = QVBoxLayout(self._widget)
        self._layout.setStretch(0, 0)
        self._layout.setContentsMargins(0, 6, 6, 6)
        self._layout.addWidget(self._undo_view)
        self.setWidget(self._widget)
        self.retranslateUi()

    def set_stack(self, stack: QUndoStack):
        self._undo_view.setStack(stack)

    def set_group(self, group: QUndoGroup):
        self._undo_view.setGroup(group)

    def changeEvent(self, event: QEvent) -> None:
        pass
        # if e.type():
        # self.LanguageChange
        # self.retranslateUi()

    def retranslateUi(self):
        self.setWindowTitle("历史")
        self._undo_view.setEmptyLabel("<空>")
Esempio n. 2
0
class UndoList(QDialog):
    __instance = None

    @classmethod
    def __getInstance(cls):
        return cls.__instance

    @classmethod
    def getInstance(cls):
        cls.__instance = cls()
        cls.getInstance = cls.__getInstance
        return cls.__instance

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Command List")
        self.stack = UndoAction()

        self.view = QUndoView()
        self.view.setStack(self.stack)

        layout = QGridLayout()
        layout.addWidget(self.view)

        self.setLayout(layout)
Esempio n. 3
0
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Pangolin")
        self.setUnifiedTitleAndToolBarOnMac(True)
        self.setGeometry(50, 50, 1200, 675)

        # Models and Views
        self.interface = PangoModelSceneInterface()

        self.tree_view = QTreeView()
        self.tree_view.setUniformRowHeights(True)
        self.tree_view.setModel(self.interface.model)
        self.interface.set_tree(self.tree_view)

        self.graphics_view = PangoGraphicsView()
        self.graphics_view.setScene(self.interface.scene)

        self.undo_view = QUndoView()

        # Dock widgets
        self.label_widget = PangoLabelWidget("Labels", self.tree_view)
        self.undo_widget = PangoUndoWidget("History", self.undo_view)
        self.file_widget = PangoFileWidget("Files")

        # Menu and toolbars
        self.menu_bar = PangoMenuBarWidget()
        self.tool_bar = PangoToolBarWidget()
        self.tool_bar.set_scene(self.interface.scene)
        self.tool_bar.label_select.setModel(self.interface.model)

        # Signals and Slots
        self.menu_bar.open_images_action.triggered.connect(self.load_images)
        self.menu_bar.export_action.triggered.connect(self.export_project)
        self.menu_bar.import_action.triggered.connect(self.import_project)
        self.menu_bar.save_project_action.triggered.connect(self.save_project)

        self.file_widget.file_view.selectionModel().currentChanged.connect(
            self.switch_image)
        self.file_widget.file_model.directoryLoaded.connect(
            self.after_loaded_images)
        self.tool_bar.label_select.currentIndexChanged.connect(
            self.interface.switch_label)
        self.tool_bar.del_labels_signal.connect(self.interface.del_labels)

        # Layouts
        self.bg = QWidget()
        self.setCentralWidget(self.bg)

        self.bg_layout = QVBoxLayout(self.bg)
        self.bg_layout.setContentsMargins(0, 0, 0, 0)
        self.bg_layout.setSpacing(0)
        self.bg_layout.addWidget(self.tool_bar)
        self.bg_layout.addWidget(self.graphics_view)

        self.addDockWidget(Qt.RightDockWidgetArea, self.label_widget)
        self.addDockWidget(Qt.RightDockWidgetArea, self.undo_widget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.file_widget)

        self.addToolBar(Qt.TopToolBarArea, self.menu_bar)

        # Shortcuts
        self.sh_reset_tool = QShortcut(QKeySequence('Esc'), self)
        self.sh_reset_tool.activated.connect(self.tool_bar.reset_tool)

        self.sh_inc_tool_size = QShortcut(
            QKeySequence(Qt.SHIFT + Qt.Key_Underscore), self)
        self.sh_inc_tool_size.activated.connect(
            lambda: self.tool_bar.set_tool_size(1, additive=True))

        self.sh_dec_tool_size = QShortcut(
            QKeySequence(Qt.SHIFT + Qt.Key_Equal), self)
        self.sh_dec_tool_size.activated.connect(
            lambda: self.tool_bar.set_tool_size(-1, additive=True))

        self.sh_select_next_image = QShortcut(
            QKeySequence(Qt.CTRL + Qt.Key_Down), self)
        self.sh_select_next_image.activated.connect(
            self.file_widget.select_next_image)

        self.sh_select_prev_image = QShortcut(
            QKeySequence(Qt.CTRL + Qt.Key_Up), self)
        self.sh_select_prev_image.activated.connect(
            self.file_widget.select_prev_image)

        self.sh_select_next_label = QShortcut(
            QKeySequence(Qt.CTRL + Qt.Key_Right), self)
        self.sh_select_next_label.activated.connect(
            self.tool_bar.label_select.select_next_label)

        self.sh_select_prev_label = QShortcut(
            QKeySequence(Qt.CTRL + Qt.Key_Left), self)
        self.sh_select_prev_label.activated.connect(
            self.tool_bar.label_select.select_prev_label)

        self.sh_undo = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Z), self)
        self.sh_undo.activated.connect(self.undo_widget.undo)

        self.sh_redo = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Y), self)
        self.sh_redo.activated.connect(self.undo_widget.redo)

    def switch_image(self, c_idx, p_idx):
        c_fpath = self.file_widget.file_model.filePath(c_idx)
        p_fpath = self.file_widget.file_model.filePath(p_idx)

        self.interface.scene.set_fpath(c_fpath)
        self.interface.scene.reset_com()
        self.graphics_view.fitInView(self.interface.scene.sceneRect(),
                                     Qt.KeepAspectRatio)

        # Handling unsaved changes
        if c_fpath not in self.interface.scene.change_stacks.keys():
            self.interface.scene.change_stacks[c_fpath] = QUndoStack()
        self.undo_view.setStack(self.interface.scene.change_stacks[c_fpath])
        self.interface.scene.stack = self.interface.scene.change_stacks[
            c_fpath]
        self.interface.filter_tree(c_fpath, p_fpath)

    def load_images(self, action=None, fpath=None):
        if fpath is None:
            dialog = QFileDialog()
            dialog.setFileMode(QFileDialog.DirectoryOnly)
            fpath = QFileDialog.getExistingDirectory()

        if fpath != "":
            self.file_widget.file_model.setRootPath(fpath)
            root_idx = self.file_widget.file_model.index(fpath)
            self.file_widget.file_view.setRootIndex(root_idx)
            self.images_are_new = True

    def after_loaded_images(self):
        if self.images_are_new is True:
            folder_path = self.file_widget.file_model.rootPath()
            for f in sorted(os.listdir(folder_path)):
                if f.endswith(".jpg") or f.endswith(".png"):
                    idx = self.file_widget.file_model.index(
                        os.path.join(folder_path, f))
                    self.file_widget.file_view.setCurrentIndex(idx)
                    break

            self.file_widget.file_view.scrollToTop()
            self.load_project()
            self.images_are_new = False

    def save_project(self, action=None, project_path=None):
        if project_path is None:
            project_path = os.path.join(self.file_widget.file_model.rootPath(),
                                        "pangolin_project.p")

        pickle_items = []
        for k in self.interface.map.keys():
            pickle_items.append(
                self.interface.model.itemFromIndex(QModelIndex(k)))
        pickle.dump(pickle_items, open(project_path, "wb"))

    def load_project(self, action=None, project_path=None):
        if project_path is None:
            project_path = os.path.join(self.file_widget.file_model.rootPath(),
                                        "pangolin_project.p")

        if os.path.exists(project_path):
            self.clear_project()

            unpickled_items = pickle.load(open(project_path, "rb"))
            for item in unpickled_items:
                if item.parent() is None:
                    self.interface.model.appendRow(item)
                item.force_update()

            self.interface.filter_tree(self.interface.scene.fpath, None)

    def clear_project(self, action=None, show_dialog=True):
        self.interface.map.clear()
        self.interface.model.clear()
        self.interface.scene.full_clear()

    def export_project(self, action=None):
        folder_path = self.file_widget.file_model.rootPath()
        fpaths = self.interface.scene.change_stacks.keys()

        dialog = ExportSettingsDialog(self, fpaths)
        if dialog.exec():
            s_fpaths = dialog.selected_fnames()
            file_format = dialog.file_format()

            if file_format == "PascalVOC (XML)":
                for fpath in s_fpaths:
                    pascal_voc_write(self.interface, fpath)

            elif file_format == "COCO (JSON)":
                #export_fpath, _ = QFileDialog().getSaveFileName(
                #    self, "Save Project", os.path.join(default, "annotations.xml"),
                #    "XML files (*.xml)")
                pass

            elif file_format == "YOLOv3 (TXT)":
                for fpath in s_fpaths:
                    yolo_write(self.interface, fpath)

            elif file_format == "Image Mask (PNG)":
                mask_folder = os.path.join(folder_path, "Masks")
                if not os.path.exists(mask_folder):
                    os.mkdir(mask_folder)
                for fpath in s_fpaths:
                    idx = self.file_widget.file_model.index(fpath)
                    self.file_widget.file_view.setCurrentIndex(idx)
                    image_mask_write(self.interface, fpath, mask_folder)

        self.interface.filter_tree(self.interface.scene.fpath, None)

    def import_project(self, action=None, folder=None):
        if folder is None:
            folder = self.file_widget.file_model.rootPath()
        fpaths = []
        for fname in os.listdir(folder):
            pre, ext = os.path.splitext(fname)
            if ext in (".xml", ".txt"):
                fpaths.append(os.path.join(folder, fname))
        if fpaths == []:
            return

        dialog = ImportSettingsDialog(self, fpaths)
        if dialog.exec():
            s_fpaths = dialog.selected_fnames()

            for fpath in s_fpaths:
                pre, ext = os.path.splitext(fpath)
                if ext == ".xml":  # PascalVOC (XML)
                    pascal_voc_read(self.interface, fpath)
                elif ext == ".txt":
                    yolo_read(self.interface, fpath)

    def unsaved_files_dialog(self):
        #if pfile != "":
        #    # Save changes?
        #    if self.interface.map:
        #        result = self.unsaved_files_dialog()
        #        if result == QMessageBox.Cancel:
        #            return
        #        elif result == QMessageBox.Save:
        #            self.save_project()

        dialog = QMessageBox()
        dialog.setText("The project has been modified.")
        dialog.setInformativeText("Do you want to save your changes?")
        dialog.setStandardButtons(QMessageBox.Save | QMessageBox.Discard
                                  | QMessageBox.Cancel)
        dialog.setDefaultButton(QMessageBox.Save)
        return dialog.exec()

    def unsaved_commands_dialog(self):
        res = QMessageBox().question(
            self.parent(), "Unsaved shape commands",
            "Command history will be cleaned, are you sure you want to continue?"
        )
        if res == QMessageBox.Yes:
            for stack in self.interface.scene.change_stacks.values():
                stack.clear()
        return res

    def export_warning_dialog(self):
        pass