pythonpython-3.xopencvpyqt5pyqtgraph

PyQtGraph ROI RemoveHandle Does Not Remove the Circular Handle for the Ellipse ROI


My objective is code out an ellipse ROI that cannot be rotated. Consider the following piece of code modified from one of the PyQtGraph examples :


import numpy as np
import cv2
import pyqtgraph as pg
from PyQt5 import QtGui
import numpy.ma as ma
from PyQt5.QtWidgets import QMessageBox

pg.setConfigOptions(imageAxisOrder='row-major')

## Create image to display
image = cv2.imread('panda.jpg')


def picturetranspose(picture):
    shape = picture.shape
    result = np.empty((shape[1],shape[0],shape[2]),dtype= np.uint8)
    for i in range(0,3):
        result[:,:,i] = np.transpose(picture[:,:,i])
    return result
    
arr = np.rot90(picturetranspose(image))
app = pg.mkQApp("ROI Examples")
w = pg.GraphicsLayoutWidget(show=True, size=(1000,800), border=True)
w.setWindowTitle('pyqtgraph example: ROI Examples')
w1 = w.addLayout(row=0, col=0)
v1a = w1.addViewBox(row=1, col=0, lockAspect = True)
v1a.setLimits(minXRange = arr.shape[0]//10, minYRange = arr.shape[1]//10, maxXRange = 5*arr.shape[0], maxYRange = 5*arr.shape[1])
img1a = pg.ImageItem(arr)
v1a.addItem(img1a)

rois = []
x=pg.EllipseROI([60, 10], [30, 20], pen=pg.mkPen('b', width=5),rotatable = False)
x.removeHandle(0)
rois.append(x)

for roi in rois:
    roi.sigRegionChanged.connect(img1a.setImage(arr))
    v1a.addItem(roi)

img1a.setImage(arr)

Here the image "panda.jpg" is given by: https://drive.google.com/drive/folders/1ejY0CjfEwS6SGS2qe_uRX2JvlruMKvPX?usp=sharing. Running the code and moving the ROI will give:

enter image description here

By clicking on the square light blue handle, the size of ROI can be changed. Since I set rotatable = False, the ROI cannot be rotated and clicking on the circular handle will not rotate the ROI .

However, the line x.removeHandle(0) should delete the light blue circular handle so that it does not show up on the screen at all . Is this a bug? What am I missing ?


Solution

  • When a QGraphicsItem is created it's not immediately added to a scene, and in that time frame some scene-related aspects are "stored" until the item is actually put to a scene. Some of those properties are pretty obvious (such as the item position), but others are not. One of them is adding child items. When a "main" item is added to the scene, all of its children (and grandchildren, great-grandchildren, etc.) are added along with it.

    This is what happens when a ROI is created: its handles are created in its __init__ (see the sources), but removeHandle() only removes the handle from the scene if the ROI actually has a scene().

    Remember that PyQt (and PySide) are bindings to Qt, meaning that we always work with python wrappers around C++ objects. Even when the last reference to a python object is deleted, only the python object is actually deleted, but if that object is a wrapper around a C++ object and that object has a parent, the actual object is not deleted after all.
    While pyqtgraph removes the handle from its internal list, the C++ object that represents the handle still exists as a child of the ROI, so, when the ROI is added to the scene, the handle still exists.

    A possible solution is to remove the handles after the ROI has been added to the scene:

    for roi in rois:
        roi.sigRegionChanged.connect(img1a.setImage(arr))
        v1a.addItem(roi)
        roi.removeHandle(0)
    

    This works because removeHandle only removes the handle when a scene exists for the item, and in this way the child item can be actually removed from the scene.

    Note that, according to the sources, EllipseROI adds two handles: rotate and scale. You will probably want to remove all of them:

    for roi in rois:
        roi.sigRegionChanged.connect(img1a.setImage(arr))
        v1a.addItem(roi)
        while roi.handles:
            roi.removeHandle(0)
    

    I suggest you to file a report on the pyqtgraph repository about this, solving it on their side should be pretty easy.