PyQt4 - QGraphicsItem 位置在拖动后未正确映射到场景中

PyQt4 - QGraphicsItem position doesn't map into scene properly after drag

我基于 QWidget 创建了一个 ImageView 小部件,其中包含一个 QGraphicsView。此小部件显示图像并让您通过 QGraphicsRectItem 使用鼠标绘制 ROI(感兴趣区域)。 ImageView 小部件运行良好,但是如果拖动 ROI 矩形并且您想在另一个地方重新绘制它,则鼠标事件捕获的位置不会正确映射到场景。

这里有几张图片来解释我的意思。

包含 ImageView 小部件的对话框,其操作控制小部件本身:

如果启用了选择,您可以绘制一个矩形:

注意矩形右下角的指针位置。 ROI刚好可以画在图像里面。

如果禁用选择,您可以拖动之前绘制的矩形:

在此之后,如果启用了选择并且您想重绘矩形:

正如您在对话框状态栏中看到的那样,指针位置被很好地捕获(这是事实!),但是这个位置(用于设置矩形几何形状)不再对应于矩形位置。

我是 Qt 的新手,这里是代码:

代码

class ImageView(QtGui.QWidget):
    scaleChanged = QtCore.pyqtSignal()
    statusChanged = QtCore.pyqtSignal(str)

    def __init__(self, parent=None):
        super(ImageView, self).__init__(parent)
        self.scale_factor = 0.0

        # Imagen
        self.image_item = QtGui.QGraphicsPixmapItem()

        # ROI
        self.ROI_item = FancyQGraphicsRectItem(self.image_item)
        self.ROI_item.setFlag(self.ROI_item.ItemIsMovable)
        self.ROI_item.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
        self.ROI_item.setPen(QtGui.QPen(QtCore.Qt.white, 0, QtCore.Qt.DashDotLine))
        self.ROI_item.setCursor(QtCore.Qt.OpenHandCursor)
        self.ROI_added = False

        # Escena
        self.scene = QtGui.QGraphicsScene()

        # Vista
        self.view = FancyQGraphicsView()
        self.view.ROI_item = self.ROI_item
        self.view.statusChanged.connect(self.change_status)
        self.view.setScene(self.scene)
        self.view.setBackgroundRole(QtGui.QPalette.Dark)
        self.view.setAlignment(QtCore.Qt.AlignCenter)
        self.view.setFrameShape(QtGui.QFrame.NoFrame)
        self.view.setRenderHint(QtGui.QPainter.Antialiasing, False)
        self.view.setMouseTracking(True)

        # Disposición
        layout = QtGui.QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.view)

        self.setLayout(layout)

    def setImage(self, pixmap):
        self.image_item.setPixmap(pixmap)
        self.scene.addItem(self.image_item)
        self.scene.setSceneRect(0, 0, self.image_item.boundingRect().right(), self.image_item.boundingRect().bottom())
        self.view.setSceneSize()

    def selectionEnable(self, value):
        self.view.selectionEnable(value)
        if value:
            self.ROI_item.setCursor(QtCore.Qt.CrossCursor)
            self.view.setInteractive(False)
            self.view.viewport().setCursor(QtCore.Qt.CrossCursor)
            if not self.ROI_added:
                self.ROI_added = True
        else:
            self.view.viewport().setCursor(QtCore.Qt.ArrowCursor)
            self.ROI_item.setCursor(QtCore.Qt.OpenHandCursor)
            self.view.setInteractive(True)

    def setupDrag(self, value):
        if value:
            self.view.setInteractive(False)
            self.view.setDragMode(self.view.ScrollHandDrag)
        else:
            self.view.setDragMode(self.view.NoDrag)
            self.view.setInteractive(True)

    def normal_size(self):
        if self.scale_factor != 1.0:
            self.view.resetMatrix()
            self.scale_factor = 1.0
            self.scaleChanged.emit()

    def scale_image(self, factor):
        self.scale_factor *= factor
        self.view.scale(factor, factor)
        self.scaleChanged.emit()

    def delete_roi(self):
        self.ROI_item.setRect(0, 0, 0, 0)

    @QtCore.pyqtSlot(str)
    def change_status(self, message):
        self.statusChanged.emit(message)


class FancyQGraphicsView(QtGui.QGraphicsView):
    statusChanged = QtCore.pyqtSignal(str)
    scene_size = (0, 0)
    ROI_item = None
    event_origin = None
    selection = False
    click = False

    def mousePressEvent(self, event):
        if self.selection:
            event_pos = self.mapToScene(event.pos())
            pos = (int(event_pos.x()), int(event_pos.y()))
            if 0 <= pos[0] < self.scene_size[0] and 0 <= pos[1] < self.scene_size[1]:
                self.event_origin = event_pos
            else:
                self.event_origin = None
            self.click = True
        else:
            QtGui.QGraphicsView.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        event_pos = self.mapToScene(event.pos())
        if self.selection and self.click:
            if self.event_origin:
                self.statusChanged.emit("x1: {0:>5d}    y1: {1:>5d}    "
                                        "x2: {2:>5d}    y2: {3:>5d}".format(int(self.event_origin.x()),
                                                                            int(self.event_origin.y()),
                                                                            int(event_pos.x()),
                                                                            int(event_pos.y())))
                if event_pos.x() < 0:
                    event_pos.setX(0)
                elif event_pos.x() > self.scene_size[0] - 1:
                    event_pos.setX(self.scene_size[0] - 1)
                if event_pos.y() < 0:
                    event_pos.setY(0)
                elif event_pos.y() > self.scene_size[1] - 1:
                    event_pos.setY(self.scene_size[1] - 1)
                self.ROI_item.setRect(QtCore.QRectF(self.event_origin, event_pos).normalized())
                print self.ROI_item.rect(), self.event_origin, event_pos
            else:
                self.statusChanged.emit("x: {0:>5d}    y: {1:>5d}".format(int(event_pos.x()), int(event_pos.y())))
        else:
            self.statusChanged.emit("x: {0:>5d}    y: {1:>5d}".format(int(event_pos.x()), int(event_pos.y())))
            QtGui.QGraphicsView.mouseMoveEvent(self, event)

    def mouseReleaseEvent(self, event):
        if self.selection:
            self.click = False
            if self.event_origin:
                self.event_origin = None
        else:
            QtGui.QGraphicsView.mouseReleaseEvent(self, event)

    def selectionEnable(self, value):
        self.selection = value

    def setSceneSize(self):
        rect = self.scene().sceneRect()
        self.scene_size = (rect.width(), rect.height())


class FancyQGraphicsRectItem(QtGui.QGraphicsRectItem):

    def mousePressEvent(self, event):
        self.setCursor(QtCore.Qt.ClosedHandCursor)
        QtGui.QGraphicsRectItem.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        QtGui.QGraphicsRectItem.mouseMoveEvent(self, event)
        # Maybe this could be modified

    def mouseReleaseEvent(self, event):
        self.setCursor(QtCore.Qt.OpenHandCursor)
        QtGui.QGraphicsRectItem.mouseReleaseEvent(self, event)

为什么会这样?以前我曾尝试在矩形的图像中实现一个受限的可移动区域,但该项目在拖动时无法正确识别其在场景中的位置。

您的问题是由于您使用 QGraphicsRectItem 的方式造成的。当您最初正确设置矩形时,该项目最初放置在场景的坐标 (0,0) 处。因此,QGraphicsRectItem(0,0) 延伸到矩形的右下坐标(在场景坐标中)。

移动 ROI 时,您将翻译整个项目,而不仅仅是项目内的矩形。这意味着该项目不再位于 (0,0),因此您提供给它的坐标是偏移的,因为您使用的是场景坐标而不是项目坐标。

有多种方法(例如 QGraphicsItem.mapFromScene())可以将坐标转换为正确的参考点(请注意,这应考虑到您的 ROI 是 self.image_item 的子项这一事实以及如果它曾经离开 (0,0))。

另一种方法是,您可以将ROI重新定位到初始点击坐标,然后根据初始点击坐标与当前点击坐标之间的差异调整大小。所以在 mouseMoveEvent 你可以这样做:

self.ROI_item.setPos(self.event_origin)
self.ROI_item.setRect(QtCore.QRectF(QtCore.QPointF(0,0), event_pos-self.event_origin).normalized())

但是,我怀疑如果移动了父项,或者如果对 QGraphicsView 应用了缩放,这可能会中断。在这种情况下,您可能需要使用 QGraphicsItem.mapFromScene() 方法进行调查(尽管始终将项目重新定位到初始点击位置可能很有用,如果只是为了减少项目的边界框的话)