Пример #1
0
class GMPage:

    @property
    def document(self):
        return self._doc

    @property
    def index(self):
        return self._index

    @property
    def page_image(self):
        return self._page_image

    @property
    def drawing_image(self):
        return self._drawing_image

    @property
    def command_count(self):
        return self._undo_stack.count()

    @property
    def can_undo(self):
        return self._undo_stack.canUndo()

    @property
    def can_redo(self):
        return self._undo_stack.canRedo()

    def __init__(self, document, mu_page, index):
        assert isinstance(document, GMDoc)
        assert isinstance(mu_page, fitz.Page)
        assert type(index) is int

        self._doc = document
        self._index = index

        self._mu_page = mu_page

        # zoom in to get higher resolution pixmap
        # todo: add this param to preferences dialog
        zoom = 2
        matrix = fitz.Matrix(zoom, zoom)

        page_data = self._mu_page.getPixmap(matrix=matrix, alpha=False).getImageData()

        self._undo_stack = QUndoStack()

        self._page_image = QPixmap()
        self._page_image.loadFromData(page_data)
        self._drawing_image = generateDrawingPixmap(self._page_image)
        self._last_drawing_hash = None

        self.captureState()

    def getData(self, alpha=False):
        return self._mu_page.getPixmap(alpha=alpha).getImageData()

    def getSize(self):
        return self._page_image.size()

    def setDrawingPixmap(self, pixmap):
        self._drawing_image = pixmap

    def getMergedPixmap(self):
        result = QPixmap(self.getSize())
        result.fill(QColor(255, 255, 255))

        painter = QPainter(result)
        painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
        painter.drawPixmap(QPoint(0, 0), self._page_image)
        painter.drawPixmap(QPoint(0, 0), self._drawing_image)
        painter.end()

        return result

    def getUndoAction(self, parent):
        return self._undo_stack.createUndoAction(parent)

    def getRedoAction(self, parent):
        return self._undo_stack.createRedoAction(parent)

    def pushCommand(self, command):
        assert isinstance(command, QUndoCommand)
        self._undo_stack.push(command)

    def captureState(self):
        self._last_drawing_hash = self._drawing_image.cacheKey()
        self._undo_stack.setClean()

    def changed(self):
        if self._undo_stack.isClean():
            return False

        return self._undo_stack.count() > 0

    def undo(self):
        self._undo_stack.undo()

    def redo(self):
        self._undo_stack.redo()
Пример #2
0
class mylabel(QLabel):
    print("into mylabel")
    def __init__(self, parent):
        super(mylabel, self).__init__(parent)
        self.image = QImage()
        self.drawing = True
        self.lastPoint = QPoint()
        self.modified = False
        self.scribbling = False
        self.eraserSize = 5  #橡皮擦初始值
        self.fontSize = 12   #字形初始值
        self.setAcceptDrops(True)
        self.savetextedit = []
        self.text = [] #紀錄文字
        self.eraserPos = []
        self.temp_img = 1   #紀錄圖片編號
        self.numstack = []

        self.i = 0  # 紀錄textedit

        self.eraserClicked = False
        self.textClicked = False
        self.clearClicked = False

        self.undoStack = QUndoStack()
        self.m_undoaction = self.undoStack.createUndoAction(self, self.tr("&Undo"))
        # self.m_undoaction.setShortcut('Ctrl+Z')
        self.addAction(self.m_undoaction)
        self.m_redoaction = self.undoStack.createRedoAction(self, self.tr("&Redo"))
        # self.m_redoaction.setShortcut('Ctrl+Y')
        self.addAction(self.m_redoaction)

    #開啟圖片
    def openimage(self, filename):
        self.image = filename
        self.scaredPixmap = self.image.scaled(self.width(), self.height(), aspectRatioMode=Qt.KeepAspectRatio)
        self.image = self.scaredPixmap
        self.setAlignment(Qt.AlignLeft)
        self.update()

    #獲得橡皮擦大小
    def getErasersize(self, esize):
        self.eraserSize = int(esize)
        print(self.eraserSize)
    #獲得字體大小
    def getFontsize(self, fsize):
        self.fontSize = int(fsize)
        print(self.fontSize)

    def mousePressEvent(self, event):
        print("left press")
        super().mousePressEvent(event)

        if event.button() == Qt.LeftButton and self.eraserClicked:
            print("start erase action")
            self.drawing = True
            self.eraserPos.clear()
            self.lastPoint = event.pos()
            self.eraserPos.append(self.lastPoint)


        elif event.button() == Qt.LeftButton and self.textClicked:
            print("add textedit")
            self.item = self.childAt(event.x(), event.y())
            if not self.childAt(event.x(), event.y()):  # 如果不是點在文字框上的話
                # 文字框
                self.textedit = textedit1(self)
                self.textedit.setObjectName("textedit{}".format(self.i))
                self.textedit.setStyleSheet("background-color:rgb(255,255,255,0);\n"  # 設定透明度
                                            "border: 6px solid black;\n");  # 設定邊框
                self.textedit.move(event.pos().x(), event.pos().y())

                self.savetextedit.append(self.textedit)
                print("record textedit", self.savetextedit)

                # 設定文字框格式
                self.textedit.setFont(QFont("Roman times", self.fontSize))
                self.textedit.setLineWrapMode(QTextEdit.FixedColumnWidth)  # 自動斷行
                self.textedit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)  # 隱藏滾動軸
                self.textedit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)  # 隱藏滾動軸

                self.textedit.show()
                print(self.textedit)

                self.item = self.childAt(event.x(), event.y())
                print("choose item", self.item)

                command = storeCommand(self.textedit)
                self.undoStack.push(command)
                print("undostack count", self.undoStack.count())
                print("into undostack", self.textedit)

                if self.textedit:  # 將文字框設為不能寫
                    x = 0
                    for self.savetextedit[x] in self.savetextedit:
                        self.savetextedit[x].setReadOnly(True)
                        self.savetextedit[x].setStyleSheet("background-color:rgb(255,255,255,0);\n"  # 設定透明度
                                                           "border: 6px solid black;\n");  # 設定邊框
                        x = x + 1

            else:
                self.item.setReadOnly(False)
                self.doc = self.item.document()
                self.doc.contentsChanged.connect(self.textAreaChanged)  # 隨著輸入的文字改變文字框的大小

        elif event.button() == Qt.RightButton:
            self.item = self.childAt(event.x(), event.y())
            print("choose textedit", self.item)

            # 有存到stack裡,可是沒辦法redo,沒辦法傳進去移動過後的位置
            try:
                if self.item:
                    self.item.setReadOnly(True)
                    print("moveCmd", self.item, self.item.pos())
                    # self.undoStack.push(moveCommand(self.item, self.item.pos()))
                    self.undoStack.push(moveCommand(self.item, self.item.pos()))
                    print("moveundostack count", self.undoStack.count())
            except Exception as e:
                print(e)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Delete:
            print("delete textedit", self.item)
            self.item.deleteLater()
            self.savetextedit.remove(self.item)

    # 隨著輸入的文字改變文字框大小
    def textAreaChanged(self):
        self.doc.adjustSize()
        newWidth = self.doc.size().width() + 20
        newHeight = self.doc.size().height() + 20
        if newWidth != self.item.width():
            self.item.setFixedWidth(newWidth)
        if newHeight != self.item.height():
            self.item.setFixedHeight(newHeight)

    def dragEnterEvent(self, e):
        print("drag")
        e.accept()

    def dropEvent(self, e):
        print("drop item")
        mime = e.mimeData().text()
        x, y = map(int, mime.split(','))
        print(x,y)
        position = e.pos()
        print(position)

        e.source().setReadOnly(True)
        self.undoStack.push(moveCommand(e.source(), e.source().pos()))

        e.source().move(position - QPoint(x,y))
        print("move item to", self.item.pos())
        e.setDropAction(Qt.MoveAction)
        e.accept()

    def mouseMoveEvent(self, event):
        if (event.buttons() and Qt.LeftButton) and self.drawing and self.eraserClicked:
            print("start painting")
            self.path = QPainterPath()
            self.path.moveTo(self.lastPoint)
            self.path.lineTo(event.pos())

            painter = QPainter(self.image)
            x = self.width() - self.image.width()
            y = self.height() - self.image.height()
            painter.translate(x / 2 - x, y / 2 - y)  #因為將圖片置中,所以要重設置畫筆原點
            painter.setPen(QPen(Qt.white, self.eraserSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
            painter.drawPath(self.path)
            print(self.path)

            self.lastPoint = event.pos()
            self.eraserPos.append(self.lastPoint)
            self.update()

    def mouseReleaseEvent(self, event):
        print("release mouse")
        if event.button() == Qt.LeftButton:
            self.drawing = False

            #儲存圖片佔存,供undo redo使用
            if self.eraserClicked == True:
                self.imagename = "img{}.PNG".format(self.temp_img)
                print(self.imagename)
                self.image.save("./saveload/{}".format(self.imagename))
                self.temp_img = self.temp_img + 1
                self.numstack.append(self.imagename)

    def paintEvent(self, event):
        canvasPainter = QPainter(self)
        self.x = (self.width() - self.image.width())/2
        self.y = (self.height() - self.image.height())/2
        canvasPainter.translate(self.x, self.y)
        canvasPainter.drawImage(event.rect(), self.image, event.rect())

        if not self.textClicked:
            x = 0
            self.undoStack.clear()
            for self.savetextedit[x] in self.savetextedit:
                self.savetextedit[x].setStyleSheet("background-color:rgb(255,255,255,0);\n"  # 設定透明度
                                        "border: 6px solid transparent;\n");  # 設定邊框
                if self.savetextedit[x].toPlainText() == "":
                    self.savetextedit[x].deleteLater()
                    self.savetextedit.remove(self.savetextedit[x])
                x = x + 1

        self.update()
Пример #3
0
class VolumeEditor(QObject):
    newImageView2DFocus = pyqtSignal()
    shapeChanged = pyqtSignal()

    @property
    def showDebugPatches(self):
        return self._showDebugPatches

    @showDebugPatches.setter
    def showDebugPatches(self, show):
        for s in self.imageScenes:
            s.showTileOutlines = show
        self._showDebugPatches = show

    @property
    def showTileProgress(self):
        return self._showTileProgress

    @showDebugPatches.setter
    def showTileProgress(self, show):
        for s in self.imageScenes:
            s.showTileProgress = show
        self._showTileProgress = show

    @property
    def cacheSize(self):
        return self._cacheSize

    @cacheSize.setter
    def cacheSize(self, cache_size):
        self._cacheSize = cache_size
        for s in self.imageScenes:
            s.setCacheSize(cache_size)

    @property
    def navigationInterpreterType(self):
        return type(self.navInterpret)

    @navigationInterpreterType.setter
    def navigationInterpreterType(self, navInt):
        self.navInterpret = navInt(self.navCtrl)
        self.eventSwitch.interpreter = self.navInterpret

    def setNavigationInterpreter(self, navInterpret):
        self.navInterpret = navInterpret
        self.eventSwitch.interpreter = self.navInterpret

    @property
    def syncAlongAxes(self):
        """Axes orthogonal to slices, whose values are synced between layers.

        Returns: a tuple of up to three values, encoding:
                 0 - time
                 1 - space
                 2 - channel

                 for example the meaning of (0,1) is: time and orthogonal space axes
                 are synced for all layers, channel is not. (For the x-y slice, the space
                 axis would be z and so on.)

        """
        return tuple(self._sync_along)

    @property
    def dataShape(self):
        return self.posModel.shape5D

    @dataShape.setter
    def dataShape(self, s):
        self.cropModel.set_volume_shape_3d_cropped([0, 0, 0], s[1:4])
        self.cropModel.set_time_shape_cropped(0, s[0])

        self.posModel.shape5D = s
        # for 2D images, disable the slice intersection marker
        is_2D = (numpy.asarray(s[1:4]) == 1).any()
        if is_2D:
            self.navCtrl.indicateSliceIntersection = False
        else:
            for i in range(3):
                self.parent.volumeEditorWidget.quadview.ensureMinimized(i)

        self.shapeChanged.emit()

        for i, v in enumerate(self.imageViews):
            v.sliceShape = self.posModel.sliceShape(axis=i)
        self.view3d.set_shape(s[1:4])

    def lastImageViewFocus(self, axis):
        self._lastImageViewFocus = axis
        self.newImageView2DFocus.emit()

    def __init__(
        self, layerStackModel, parent, labelsink=None, crosshair=True, is_3d_widget_visible=False, syncAlongAxes=(0, 1)
    ):
        super(VolumeEditor, self).__init__(parent=parent)
        self._sync_along = tuple(syncAlongAxes)

        ##
        ## properties
        ##
        self._showDebugPatches = False
        self._showTileProgress = True

        ##
        ## base components
        ##
        self._undoStack = QUndoStack()
        self.layerStack = layerStackModel
        self.posModel = PositionModel(self)
        self.brushingModel = BrushingModel()
        self.cropModel = CropExtentsModel(self)

        self.imageScenes = [
            ImageScene2D(self.posModel, (0, 1, 4), swapped_default=True),
            ImageScene2D(self.posModel, (0, 2, 4)),
            ImageScene2D(self.posModel, (0, 3, 4)),
        ]
        self.imageViews = [ImageView2D(parent, self.cropModel, self.imageScenes[i]) for i in [0, 1, 2]]
        self.imageViews[0].focusChanged.connect(lambda arg=0: self.lastImageViewFocus(arg))
        self.imageViews[1].focusChanged.connect(lambda arg=1: self.lastImageViewFocus(arg))
        self.imageViews[2].focusChanged.connect(lambda arg=2: self.lastImageViewFocus(arg))
        self._lastImageViewFocus = 0

        if not crosshair:
            for view in self.imageViews:
                view._crossHairCursor.enabled = False

        self.imagepumps = self._initImagePumps()

        self.view3d = self._initView3d(is_3d_widget_visible)

        names = ["x", "y", "z"]
        for scene, name, pump in zip(self.imageScenes, names, self.imagepumps):
            scene.setObjectName(name)
            scene.stackedImageSources = pump.stackedImageSources

        self.cacheSize = 50

        ##
        ## interaction
        ##

        # navigation control
        self.navCtrl = NavigationController(self.imageViews, self.imagepumps, self.posModel, view3d=self.view3d)
        self.navInterpret = NavigationInterpreter(self.navCtrl)

        # event switch
        self.eventSwitch = EventSwitch(self.imageViews, self.navInterpret)

        # brushing control
        if crosshair:
            self.crosshairController = CrosshairController(self.brushingModel, self.imageViews)
        self.brushingController = BrushingController(
            self.brushingModel, self.posModel, labelsink, undoStack=self._undoStack
        )
        self.brushingInterpreter = BrushingInterpreter(self.navCtrl, self.brushingController)

        for v in self.imageViews:
            self.brushingController._brushingModel.brushSizeChanged.connect(v._sliceIntersectionMarker._set_diameter)

        # thresholding control
        self.thresInterpreter = ThresholdingInterpreter(self.navCtrl, self.layerStack, self.posModel)

        # By default, don't show cropping controls
        self.showCropLines(False)

        ##
        ## connect
        ##
        self.posModel.timeChanged.connect(self.navCtrl.changeTime)
        self.posModel.slicingPositionChanged.connect(self.navCtrl.moveSlicingPosition)
        if crosshair:
            self.posModel.cursorPositionChanged.connect(self.navCtrl.moveCrosshair)
        self.posModel.slicingPositionSettled.connect(self.navCtrl.settleSlicingPosition)

        self.layerStack.layerAdded.connect(self._onLayerAdded)
        self.parent = parent
        self._setUpShortcuts()

    def _reset(self):
        for s in self.imageScenes:
            s.reset()

    def scheduleSlicesRedraw(self):
        for s in self.imageScenes:
            s._invalidateRect()

    def setInteractionMode(self, name):
        modes = {
            "navigation": self.navInterpret,
            "brushing": self.brushingInterpreter,
            "thresholding": self.thresInterpreter,
        }
        self.eventSwitch.interpreter = modes[name]

    def showCropLines(self, visible):
        for view in self.imageViews:
            view.showCropLines(visible)

    def setTileWidth(self, tileWidth):
        for i in self.imageScenes:
            i.setTileWidth(tileWidth)

    def cleanUp(self):
        QApplication.processEvents()
        for scene in self._imageViews:
            scene.close()
            scene.deleteLater()
        self._imageViews = []
        QApplication.processEvents()

    def closeEvent(self, event):
        event.accept()

    def setLabelSink(self, labelsink):
        self.brushingController.setDataSink(labelsink)

    ##
    ## private
    ##
    def _setUpShortcuts(self):
        mgr = ShortcutManager()
        ActionInfo = ShortcutManager.ActionInfo

        def _undo():
            idx = self._undoStack.index()
            self._undoStack.setIndex(idx - 1)

        def _redo():
            cap = self._undoStack.count()
            idx = self._undoStack.index()
            self._undoStack.setIndex(min(cap, idx + 1))

        mgr.register("Ctrl+Z", ActionInfo("Labeling", "Undo", "Undo last action", _undo, self.parent, None))
        mgr.register("Ctrl+Y", ActionInfo("Labeling", "Redo", "Redo last action", _redo, self.parent, None))

    def _initImagePumps(self):
        alongTXC = SliceProjection(abscissa=2, ordinate=3, along=[0, 1, 4])
        alongTYC = SliceProjection(abscissa=1, ordinate=3, along=[0, 2, 4])
        alongTZC = SliceProjection(abscissa=1, ordinate=2, along=[0, 3, 4])

        imagepumps = []
        imagepumps.append(volumina.pixelpipeline.imagepump.ImagePump(self.layerStack, alongTXC, self._sync_along))
        imagepumps.append(volumina.pixelpipeline.imagepump.ImagePump(self.layerStack, alongTYC, self._sync_along))
        imagepumps.append(volumina.pixelpipeline.imagepump.ImagePump(self.layerStack, alongTZC, self._sync_along))

        return imagepumps

    def _initView3d(self, is_3d_widget_visible):
        from .view3d.overview3d import Overview3D

        view3d = Overview3D(is_3d_widget_visible=is_3d_widget_visible)

        def onSliceDragged():
            self.posModel.slicingPos = view3d.get_slice()

        view3d.slice_changed.connect(onSliceDragged)
        return view3d

    def _onLayerAdded(self, layer, row):
        self.navCtrl.layerChangeChannel(layer)
        layer.channelChanged.connect(partial(self.navCtrl.layerChangeChannel, layer=layer))