How can I change mouse behavior depending on the item hovered over with PyQt6 and Pyqtgraph?

83 views Asked by At

I'm creating a DICOM image visualization interface with PyQt6 and PyQtGraph. I display the image using an ImageContainer class, a subclass of pg.ImageView. I've modified the mouse behavior during click using mousePressEvent and mouseMoveEvent. I also display the pixel values hovered by the mouse without clicking, using an eventFilter.

I allow the user to add an elliptical region of interest (ROI) using a CustomEllipseROI class, a subclass of pg.EllipseROI. By default, the ROI is movable and resizable, but since I've modified the mouse behavior of the ImageContainer instance, it no longer works.

I tried to implement another eventFilter in the CustomEllipseROI class, but failed.

Question: How can I enable the movement and resizing of the ROI again, and even customize the mouse behavior specifically for the ROI?

Here's a minimal reproducible example. Thanks for your help!

import sys
import pyqtgraph as pg
import numpy as np

from PyQt6.QtWidgets import QApplication
from PyQt6.QtCore import Qt, QEvent

class CustomEllipseROI(pg.EllipseROI):
    def __init__(self, pos, size, **args):
        super().__init__(pos, size, **args)
   
    def hoverEvent(self, event):
        print("Hover ROI")

class ImageContainer(pg.ImageView):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.create_data()
        self.init_image()
        self.create_ROI()

    def create_data(self):
        dataRed = np.ones((100, 200, 200)) * np.linspace(90, 150, 100)[:, np.newaxis, np.newaxis]
        dataRed += pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 100
        dataGrn = np.ones((100, 200, 200)) * np.linspace(90, 180, 100)[:, np.newaxis, np.newaxis]
        dataGrn += pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 100
        dataBlu = np.ones((100, 200, 200)) * np.linspace(180, 90, 100)[:, np.newaxis, np.newaxis]
        dataBlu += pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 100
        self.data = np.concatenate(
            (dataRed[:, :, :, np.newaxis], dataGrn[:, :, :, np.newaxis], dataBlu[:, :, :, np.newaxis]), axis=3
        )

    def init_image(self):
        self.setImage(self.data)
        self.ui.histogram.hide()
        self.ui.roiBtn.hide()
        self.ui.menuBtn.hide()
        self.ui.roiPlot.hide()

        self.setAttribute(Qt.WidgetAttribute.WA_Hover)
        self.installEventFilter(self)

        self.view.mousePressEvent = self.mouse_press_event
        self.view.mouseMoveEvent = self.mouse_move_event

    def create_ROI(self):
        self.ellipseROI_item = CustomEllipseROI((50, 50), (50, 50))
        self.view.addItem(self.ellipseROI_item)

    def eventFilter(self, obj, event):
        if event.type() == QEvent.Type.HoverMove:
            print("Hover Image")
            # Call function that displays the image value at mouse position
        elif event.type() == QEvent.Type.HoverLeave:
            print("Leave Image")
            # Clear value display
        return super().eventFilter(obj, event)
    
    def mouse_press_event(self, event):
        self.prevPos = event.pos()

    def mouse_move_event(self, event):
        if event.buttons() == Qt.MouseButton.MiddleButton:
            print("Middle Button on Image")
            # Call function to change contrast
        elif event.buttons() == Qt.MouseButton.LeftButton:
            print("Left Button on Image")
            # Call function to move across slices of a volume
        elif event.buttons() == Qt.MouseButton.RightButton:
            print("Right Button on Image")
            # Call function to zoom in/out

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main = ImageContainer()
    main.show()
    sys.exit(app.exec())```
1

There are 1 answers

2
Sen ZmaKi On

The problem is that you're overriding the self.view.mousePressEvent method without calling the original method. It is best practise to return with a call to the original method of any method you're overriding.

My solution adds the return call to both self.view.mousePressEvent and self.view.mouseMoveEvent as it is best practise but the bug only occurs because of self.view.mousePressEvent specifically.

class ImageContainer(pg.ImageView)

    def init_image(self):
        # Other stuff.. .

        self.original_view_mouse_press_event = self.view.mousePressEvent
        self.original_view_mouse_move_event = self.view.mouseMoveEvent
        self.view.mousePressEvent = self.mouse_press_event
        self.view.mouseMoveEvent = self.mouse_move_event
    
    
    def mouse_press_event(self, event):
        self.prevPos = event.pos()
        return self.original_view_mouse_press_event(event)
    
    def mouse_move_event(self, event):
        if event.buttons() == Qt.MouseButton.MiddleButton:
            print("Middle Button on Image")
            # Call function to change contrast
        elif event.buttons() == Qt.MouseButton.LeftButton:
            print("Left Button on Image")
            # Call function to move across slices of a volume
        elif event.buttons() == Qt.MouseButton.RightButton:
            print("Right Button on Image")
            # Call function to zoom in/out
        return self.original_view_mouse_move_event(event)