mousePressEvent 禁用 QGraphicsItem ItemIsMovable

mousePressEvent disables QGraphicsItem ItemIsMovable

我有一种情况想在 QGraphicsViewQGraphicsScene 中显示的图像上画圆圈(参见屏幕截图)。

我目前通过覆盖自定义 QGraphicsScene 对象的 mousePressEvent 来放置红色圆圈,但这似乎阻止了鼠标事件传播到 QGraphicsEllipseItems形成圆圈。这是一种耻辱,因为我希望这些保持可移动和可选择。我目前的技巧是维护生成的 QGraphicsItem 的列表并简单地遍历它们,调用它们的事件处理程序,例如:

def mouseMoveEvent(self, event):
    for circ in self.circs:
        if circ.rect().contains(event.scenePos()) or circ.isSelected():
            circ.mouseMoveEvent(event)

这一系列的作品,但似乎有点错误(有时省略号变得无法选择)。我应该提到 this post 的解决方案对我不起作用。如果我尝试使用 QtGui.QGraphicsItem.mouseMoveEvent(self, event) 之类的方式传播事件,我会收到错误消息:

Traceback (most recent call last):
  File "main.py", line 58, in mouseMoveEvent
    QtGui.QGraphicsItem.mouseMoveEvent(self, event)
AttributeError: module 'PySide2.QtGui' has no attribute 'QGraphicsItem'

那么正确和正确的方法是什么?下面是一个代码示例。

main.py:

from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from PySide2 import QtWidgets, QtGui

import ui.main_window as main_window
import sys
import data_loader.data_loaders as module_data

import cv2 as cv

img_default_size = (512, 512)


class WingSceneWidget(QtWidgets.QGraphicsScene):
    def __init__(self, parent):
        super(self.__class__, self).__init__(parent)
        self.keypoints = []
        self.annotating = False
        self.p_item = None
        self.circs = []

    def mouseDoubleClickEvent(self, event):
        print("New Annotation")
        self.annotating = True
        self.keypoints = []
        for circ in self.circs:
            self.removeItem(circ)

    def mousePressEvent(self, event):
        circ_radius = 5
        if self.annotating:
            x = event.scenePos().x()
            y = event.scenePos().y()
            self.keypoints.append([x, y])
            circ = QGraphicsEllipseItem(x-circ_radius, y-circ_radius, circ_radius*2, circ_radius*2, self.p_item)
            circ.setPen(QPen(Qt.red, 2))
            circ.setFlag(QGraphicsItem.ItemIsMovable, True)
            circ.setFlag(QGraphicsItem.ItemIsSelectable, True)
            self.circs.append(circ)

            print(self.keypoints)
            if len(self.keypoints) >= 8:
                self.annotating = False

        for circ in self.circs:
            if circ.rect().contains(event.scenePos()):
                circ.mousePressEvent(event)

    def mouseMoveEvent(self, event):
        for circ in self.circs:
            if circ.rect().contains(event.scenePos()) or circ.isSelected():
                circ.mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        for circ in self.circs:
            if circ.rect().contains(event.scenePos()) or circ.isSelected():
                circ.mouseReleaseEvent(event)

    def give_pitem(self, p_item):
        self.p_item = p_item


class WingNet(QtWidgets.QMainWindow, main_window.Ui_MainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self)
        self.folder_list = []
        self.scene = WingSceneWidget(self.gv_wing_image)
        self.gv_wing_image.setScene(self.scene)
        self.gv_wing_image.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)

        self.btn_label_wings.clicked.connect(self.browse_folders)
        self.listWidget.currentItemChanged.connect(self.selection_changed)

    def browse_folders(self):
        self.listWidget.clear()  # In case there are any existing elements in the list

        file_dialog = QtWidgets.QFileDialog()
        file_dialog.setFileMode(QtWidgets.QFileDialog.DirectoryOnly)
        file_dialog.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True)
        file_view = file_dialog.findChild(QtWidgets.QListView, 'listView')

        # to make it possible to select multiple directories:
        if file_view:
            file_view.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
        f_tree_view = file_dialog.findChild(QtWidgets.QTreeView)
        if f_tree_view:
            f_tree_view.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)

        if file_dialog.exec():
            self.folder_list = file_dialog.selectedFiles()

        if self.folder_list:
            image_paths = module_data.get_image_paths(self.folder_list)
            for image_path in image_paths:
                print(image_path)
                self.listWidget.addItem(image_path)

        self.btn_edit_tps.setEnabled(False)
        self.btn_label_wings.setText("Start")
        self.btn_label_wings.clicked.disconnect()

    def selection_changed(self):
        selected = self.listWidget.currentItem().text()
        print(selected)
        image = cv.imread(selected)
        image = cv.resize(image, img_default_size)
        height, width, channel = image.shape
        bytes_per_line = 3 * width
        q_img = QtGui.QImage(image.data, width, height, bytes_per_line, QtGui.QImage.Format_RGB888).rgbSwapped()
        pixmap = QtGui.QPixmap.fromImage(q_img)

        p_item = self.scene.addPixmap(pixmap)
        self.scene.give_pitem(p_item)


def main():
    app = QtWidgets.QApplication(sys.argv)
    form = WingNet()
    form.show()
    app.exec_()


if __name__ == '__main__':
    main()

main_window.py:

from PySide2 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(803, 616)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.splitter_image_list = QtWidgets.QSplitter(self.centralwidget)
        self.splitter_image_list.setOrientation(QtCore.Qt.Horizontal)
        self.splitter_image_list.setObjectName("splitter_image_list")
        self.gv_wing_image = QtWidgets.QGraphicsView(self.splitter_image_list)
        self.gv_wing_image.setObjectName("gv_wing_image")
        self.listWidget = QtWidgets.QListWidget(self.splitter_image_list)
        self.listWidget.setObjectName("listWidget")
        self.verticalLayout.addWidget(self.splitter_image_list)
        self.h_layout_buttons = QtWidgets.QHBoxLayout()
        self.h_layout_buttons.setObjectName("h_layout_buttons")
        self.btn_edit_tps = QtWidgets.QPushButton(self.centralwidget)
        self.btn_edit_tps.setObjectName("btn_edit_tps")
        self.h_layout_buttons.addWidget(self.btn_edit_tps)
        self.btn_label_wings = QtWidgets.QPushButton(self.centralwidget)
        self.btn_label_wings.setObjectName("btn_label_wings")
        self.h_layout_buttons.addWidget(self.btn_label_wings)
        self.verticalLayout.addLayout(self.h_layout_buttons)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.menuBar = QtWidgets.QMenuBar(MainWindow)
        self.menuBar.setGeometry(QtCore.QRect(0, 0, 803, 25))
        self.menuBar.setObjectName("menuBar")
        MainWindow.setMenuBar(self.menuBar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "MainWindow", None, -1))
        self.btn_edit_tps.setText(QtWidgets.QApplication.translate("MainWindow", "Edit Existing TPS", None, -1))
        self.btn_label_wings.setText(QtWidgets.QApplication.translate("MainWindow", "Label New Wings", None, -1))

由于是重写场景mousePressEvent,所以需要自己查看鼠标坐标处有什么item;这可以通过使用 QGraphicsScene.items() 来实现,其中 returns 这些坐标处的项目列表,按 z-index 顺序(从上到下)。

在您的情况下,只需确保某些项目存在,并且最上面的是 QGraphicsEllipseItem;如果是这种情况,只需继续基本 class 实现,否则添加点。这也意味着您不必覆盖 mouseMoveEvent 或 mouseReleaseEvent。

    def mousePressEvent(self, event):
        items = self.items(event.scenePos())
        if items and isinstance(items[0], QGraphicsEllipseItem):
            QtWidgets.QGraphicsScene.mousePressEvent(self, event)
            return
        if self.annotating:
            circ_radius = 5
            x = event.scenePos().x()
            y = event.scenePos().y()
            self.keypoints.append([x, y])
            circ = QGraphicsEllipseItem(x-circ_radius, y-circ_radius, circ_radius*2, circ_radius*2, self.p_item)
            circ.setPen(QPen(Qt.red, 2))
            circ.setFlags(circ.flags() | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)

            if len(self.keypoints) >= 8:
                self.annotating = False

为了将来参考,请尽量提供尽可能少且可重现的示例:不需要整个加载机制,也不需要 cv 模块依赖项。