Synaps
Synaps

Reputation: 323

PyQtgraph - Draw ROI by mouse click & drag

I would like to draw ROI's by click and drag events in the PlotWidget. The issue is that several click interactions are already reserved for the PlotWidget, second it is hard to tell the right position of the mouse in the PlotWidget - especially when the image scale has been changed or if the window scale has been changed.

Current GUI status

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_()

I would like to start drawing the ROI by mouse click and stop drawing by mouse release... every hint would be helpful. Please consider that the content of the PlotWidget is drag-able and that it might need to be frozen while drawing a ROI.


EDIT:

I have tried to overwrite temporary the click events with the following lines, but somehow clickevents seem to be triggered somewhere else, since my functions do not get called...



    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

Upvotes: 4

Views: 3987

Answers (1)

i8pineapples
i8pineapples

Reputation: 26

I know this post is old, but in case someone else finds it someday I thought I'd post my solution. It's inelegant, but it works for me. In my application, I have a button labeled "draw" that calls a function that overwrites the mouse drag event temporarily to simply draw a box instead. The overwritten mouse drag event restores the native mouse drag event with the finish signal. This code also overwrites the escape key to cancel the draw if pressed after pushing my draw button but before drawing. In my code, imageArrayItem is an existing imageItem in a plot widget, and Dialog is a QDialog containing the plot widget.

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

Upvotes: 1

Related Questions