PyQtgraph - 通过鼠标单击和拖动绘制 ROI

Posted

技术标签:

【中文标题】PyQtgraph - 通过鼠标单击和拖动绘制 ROI【英文标题】:PyQtgraph - Draw ROI by mouse click & drag 【发布时间】:2020-01-30 22:01:18 【问题描述】:

我想通过 PlotWidget 中的单击和拖动事件来绘制 ROI。问题是已经为 PlotWidget 保留了几个点击交互,其次很难判断鼠标在 PlotWidget 中的正确位置 - 特别是当图像比例已更改或窗口比例已更改时。

import pyqtgraph as pg
import pyqtgraph.opengl as gl
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
from PyQt5 import Qt
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
import vtk, sys
import numpy as np
from PIL import Image
class GUI:
    def __init__(self):
        self.init_gui() 

    def proxyWidget(self, item, width=None, height=None):
        proxy = QtGui.QGraphicsProxyWidget()
        if(height != None):
            height = item.sizeHint().height() if height==None else height
            item.setMaximumHeight(height)
        if(width!=None):
            width = item.sizeHint().width() if width==None else width
            item.setMaximumWidth(width)
        proxy.setWidget(item)
        return proxy

    def init_gui(self, win_height=800, win_width=1800):
        pg.setConfigOptions(imageAxisOrder='row-major')
        pg.setConfigOption('background', 'w')
        pg.setConfigOption('foreground', 'k')
        self.w = pg.GraphicsWindow(size=(win_width,win_height), border=True)
        self.img = pg.ImageItem()
        self.list_imgs       = QtGui.QListWidget()
        self.btn_Del_Mark    = QtGui.QPushButton('Del Mark')
        self.btn_MarkPed     = QtGui.QPushButton('Mark ped')
        self.lbl_list1       = QtGui.QLabel("List Images")
        self.lbl_list2       = QtGui.QLabel("List Markings")
        self.list_imgs       = QtGui.QListWidget()
        self.list_marks      = QtGui.QListWidget()
        self.layout = QtGui.QGridLayout()
        self.w.setLayout(self.layout)
        #self.w_3d = pg.GraphicsWindow()

        self.vtkWidget = QVTKRenderWindowInteractor()
        #self.w_3d.addItem(self.proxyWidget(self.vtkWidget))

        self.vtkWidget.Initialize()
        self.vtkWidget.Start()
        self.ren = vtk.vtkRenderer()
        self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
        self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()

        # Create source
        source = vtk.vtkSphereSource()
        source.SetCenter(0, 0, 0)
        source.SetRadius(5.0)

        # Create a mapper
        mapper = vtk.vtkPolyDataMapper()
        mapper.SetInputConnection(source.GetOutputPort())

        # Create an actor
        actor = vtk.vtkActor()
        actor.SetMapper(mapper)

        self.ren.AddActor(actor)

        self.ren.ResetCamera()
        self.iren.Initialize()
        self.iren.Start()
        path = "/home/brain/uni/frustum-pointnets/dataset/KITTI/object/testing/image_2/000000.png"
        imgdata = Image.open(path)
        self.imgArr = np.array(imgdata)
        #ToDo: undistort Image if neccessary

        self.img.setImage(self.imgArr)
        #self.vbLayout = self.w.addLayout(row=0,  col=3, rowspan=10,  colspan=20)
        imageGraph = pg.PlotWidget(name='Signalgraph')
        self.vb = imageGraph.plotItem.vb
        self.lbl_list1.setAlignment(QtCore.Qt.AlignCenter)
        self.lbl_list2.setAlignment(QtCore.Qt.AlignCenter)
        self.vb.setAspectLocked()
        self.vb.addItem(self.img)
        self.vb.invertY(True)
        self.vb.setMaximumSize(int(7/10.*win_width), int(9/20.*win_height))
        self.layout.addWidget(imageGraph,                           1 , 3, 10,  20)
        self.layout.addWidget(self.vtkWidget                      , 11, 3, 10,  20)
        self.layout.addWidget(self.lbl_list1 ,                                  0, 1, 1, 1)   
        self.layout.addWidget(self.lbl_list2 ,                                  0, 2, 1, 1)  
        self.layout.addWidget(self.list_imgs ,                                  1, 1, 20,1)   
        self.layout.addWidget(self.list_marks,                                  1, 2, 20,1)   
        sizeHint =  lambda: pg.QtCore.QSize(int(1./10.*win_width), int(0.9/20.*win_height))
        self.lbl_list1.sizeHint = lambda: pg.QtCore.QSize(int(1./10.*win_width), int(0.9/20.*win_height))
        self.lbl_list2.sizeHint = lambda: pg.QtCore.QSize(int(1./10.*win_width), int(0.9/20.*win_height))
        self.list_imgs.sizeHint  = lambda: pg.QtCore.QSize(int(1./10.*win_width), int(18/20.*win_height))
        self.list_marks.sizeHint = lambda: pg.QtCore.QSize(int(1./10.*win_width), int(18/20.*win_height))
        self.list_imgs.setMaximumWidth(int(1./10.*win_width))
        self.list_marks.setMaximumWidth(int(1./10.*win_width))

        self.vtkWidget.show()


if __name__ == "__main__":
    app = QtGui.QApplication([])
    guiobj = GUI()

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

我想通过鼠标单击开始绘制 ROI 并通过鼠标释放停止绘制...每个提示都会有所帮助。请注意 PlotWidget 的内容是可拖动的,并且在绘制 ROI 时可能需要将其冻结。


编辑:

我试图用以下几行临时覆盖点击事件,但不知何故,点击事件似乎在其他地方被触发,因为我的函数没有被调用......



    def on_btn_MarkPed(self):
        #self.vb.setMouseEnabled(x=False, y=False)
        self.creatRoiByMouse("Pedestrian")

    def on_btn_MarkCycl(self):
        self.creatRoiByMouse("Cyclist")

    def on_btn_MarkVehicle(self):
        self.creatRoiByMouse("Vehicle")

    def creatRoiByMouse(self, class2Mark):
        self.img.mousePressEvent   = self.ImgMousePressEvent
        self.img.mouseReleaseEvent = self.ImgMouseReleaseEvent

    def ImgMousePressEvent(self, event):
        print(event)
        pass
#
#
    def ImgMouseReleaseEvent(self, event):
        print(event)
        pass

【问题讨论】:

【参考方案1】:

我知道这篇文章很旧,但万一其他人有一天发现它,我想我会发布我的解决方案。这很不优雅,但它对我有用。在我的应用程序中,我有一个标记为“draw”的按钮,它调用一个临时覆盖鼠标拖动事件的函数来简单地绘制一个框。覆盖的鼠标拖动事件使用完成信号恢复本机鼠标拖动事件。如果在按下我的绘制按钮之后但在绘制之前按下,此代码还会覆盖退出键以取消绘制。在我的代码中,imageArrayItem 是绘图小部件中现有的 imageItem,而 Dialog 是包含绘图小部件的 QDialog。

def clickDraw(self):
    app.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))
    imageArrayItem.getViewBox().setMouseMode(ViewBox.RectMode)
    imageArrayItem.getViewBox().rbScaleBox.setPen(fn.mkPen((255, 255, 255), width = 1))
    imageArrayItem.getViewBox().rbScaleBox.setBrush(fn.mkBrush(255, 255, 255, 100))
    
    def mouseDragEvent(ev, axis = None): # This is a modified version of the original mouseDragEvent function in pyqtgraph.ViewBox
        ev.accept()  # accept all buttons
        dif = (ev.pos() - ev.lastPos()) * -1
        mouseEnabled = np.array(imageArrayItem.getViewBox().state['mouseEnabled'], dtype = np.float)
        mask = mouseEnabled.copy()
        if ev.button() & QtCore.Qt.LeftButton:
            if imageArrayItem.getViewBox().state['mouseMode'] == ViewBox.RectMode:
                if ev.isFinish():
                    QtCore.QTimer.singleShot(0, self.restoreCursor)
                    imageArrayItem.getViewBox().rbScaleBox.hide()
                    ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(ev.pos()))
                    ax = imageArrayItem.getViewBox().childGroup.mapRectFromParent(ax)
                    imageArrayItem.getViewBox().mouseDragEvent = temp # reset to original mouseDragEvent
                    imageArrayItem.getViewBox().setMouseMode(ViewBox.PanMode)
                else:
                    imageArrayItem.getViewBox().updateScaleBox(ev.buttonDownPos(), ev.pos()) # update shape of scale box
        elif ev.button() & QtCore.Qt.MidButton: # allow for panning with middle mouse button
            tr = dif*mask
            tr = imageArrayItem.getViewBox().mapToView(tr) - imageArrayItem.getViewBox().mapToView(Point(0,0))
            x = tr.x() if mask[0] == 1 else None
            y = tr.y() if mask[1] == 1 else None
            imageArrayItem.getViewBox()._resetTarget()
            if x is not None or y is not None:
                imageArrayItem.getViewBox().translateBy(x=x, y=y)
            imageArrayItem.getViewBox().sigRangeChangedManually.emit(imageArrayItem.getViewBox().state['mouseEnabled'])
            
    def keyPressE_mouseDrag(event): # Override "esc" key to cancel draw
            if event.key() == QtCore.Qt.Key_Escape:
                QtCore.QTimer.singleShot(0, self.restoreCursor)
                imageArrayItem.getViewBox().rbScaleBox.hide()
                imageArrayItem.getViewBox().mouseDragEvent = temp # reset to original mouseDragEvent
                imageArrayItem.getViewBox().setMouseMode(ViewBox.PanMode)
            else:
                QtWidgets.QDialog.keyPressEvent(Dialog, event)
        Dialog.keyPressEvent = keyPressE_mouseDrag
        temp = imageArrayItem.getViewBox().mouseDragEvent # save original mouseDragEvent for later
        imageArrayItem.getViewBox().mouseDragEvent = mouseDragEvent # set to modified mouseDragEvent

【讨论】:

以上是关于PyQtgraph - 通过鼠标单击和拖动绘制 ROI的主要内容,如果未能解决你的问题,请参考以下文章

如何在pyqtgraph中绘制十字准线并绘制鼠标位置?

OpenGL用鼠标拖动绘制矩形?

如何通过鼠标拖动选择铁选择器中的多个元素

在opencv Python中拖动鼠标绘制一条线并获取线的端点坐标

在线程内更新 pyqtgraph BarGraphItem

在 MFC 中通过鼠标单击而不是按下拖动动作进行选择