PyQt4 - QGraphicsItem 位置在拖动后无​​法正确映射到场景中 [英] PyQt4 - QGraphicsItem position doesn't map into scene properly after drag

查看:95
本文介绍了PyQt4 - QGraphicsItem 位置在拖动后无​​法正确映射到场景中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经基于包含 QGraphicsViewQWidget 创建了一个 ImageView 小部件.这个小部件显示一个图像,并允许您通过 QGraphicsRectItem 用鼠标绘制一个 ROI(感兴趣区域).ImageView 小部件运行良好,但如果拖动 ROI 矩形并且您想在其他位置重新绘制它,则鼠标事件捕获的位置无法正确映射到场景.

I've created a ImageView widget based on QWidget that contains a QGraphicsView. This widget shows an image and lets you draw a ROI (region of interest) with the mouse through a QGraphicsRectItem. The ImageView widget works well, but if the ROI rectangle is dragged and you want to redraw it in another place, the position captured by the mouse event doesn't maps to scene correctly.

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

Here are a few images to explain what I mean.

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

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

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

Note the pointer position at the bottom right corner of the rectangle. The ROI just can be drawn inside the image.

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

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

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

The pointer position is captured fine (is a fact!), as you can see at the dialog status bar, but this position (used to set the rectangle geometry) doesn't corresponds the rectangle position anymore.

我对 Qt 很陌生,这是代码:

I'm pretty new with Qt, here is the code:

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)

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

Why is this happening? Previously I've tried to implement a restricted movable area into the image for the rectangle, but that item doesn't recognize its position properly in the scene when is dragged.

推荐答案

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

Your issue occurs because of the way you are using the QGraphicsRectItem. While you are setting the rectangle correctly initially, the item is originally placed at coordinates (0,0) of the scene. As such, the QGraphicsRectItem extends from (0,0) to the bottom right coordinate of your rectangle (in scene coordinates).

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

When you move the ROI, you are translating the entire item, not just the rectangle within the item. Which means the item is no longer located at (0,0), and so the coordinates you are feeding it are offset because you are using scene coordinates rather than item coordinates.

有多种方法(例如 QGraphicsItem.mapFromScene())可以将坐标转换为正确的参考点(请注意,这应该考虑到您的 ROI 是self.image_item 以及如果它从 (0,0) 移开.

There are various methods (such as QGraphicsItem.mapFromScene()) which can translate the coordinates to the correct reference points (note that this should take into account the fact that your ROI is a child of self.image_item as well if that ever gets moved away from (0,0)).

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

Another alternative is that you could relocate the ROI to the initial click coordinate, and then size it according to the difference between the initial click coordinate and the current click coordinate. So in the mouseMoveEvent you could do:

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() 方法进行调查(尽管始终将项目重新定位到初始单击位置可能很有用,如果只是为了减少边界框项)

However, I suspect this may break if the parent item is moved, or if there is scaling applied to the QGraphicsView. In such cases you would probably need to investigate using the QGraphicsItem.mapFromScene() method (although it is likely to be useful to always relocate the item to the initial click position, if only to reduce the bounding box of the item)

这篇关于PyQt4 - QGraphicsItem 位置在拖动后无​​法正确映射到场景中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆