PyQt4 - 拖动后 QGraphicsItem 位置未正确映射到场景中
Posted
技术标签:
【中文标题】PyQt4 - 拖动后 QGraphicsItem 位置未正确映射到场景中【英文标题】:PyQt4 - QGraphicsItem position doesn't map into scene properly after drag 【发布时间】:2015-08-14 20:32:52 【问题描述】:我创建了一个基于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)
为什么会这样?以前我尝试在矩形的图像中实现一个受限的可移动区域,但是当被拖动时,该项目无法正确识别其在场景中的位置。
【问题讨论】:
我知道这并不能解决您的问题,但是您是否有理由从头开始构建 ROI 功能?如果我没记错的话,Pyqtgraph 有可移动的投资回报率。 @three_pineapples 因为我想学习。 ;) 我知道 PyQtGraph,但因为我只需要这个基本功能,所以我想自己构建它。 酷!我只是想检查一下你没有浪费时间:) 【参考方案1】:您的问题是由于您使用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()
方法进行调查(尽管始终将项目重新定位到初始点击位置可能很有用,如果只是为了减少项目的边界框)
【讨论】:
以上是关于PyQt4 - 拖动后 QGraphicsItem 位置未正确映射到场景中的主要内容,如果未能解决你的问题,请参考以下文章
Python / PyQt4:如何使 QComboBox 项可拖动