如何在PyQt6与PyQtGraph中恢复ROI交互并定制鼠标行为
问题
使用PyQt6和PyQtGraph开发DICOM图像可视化界面,通过继承pg.ImageView实现ImageContainer类展示图像。已通过替换self.view的mousePressEvent和mouseMoveEvent实现自定义鼠标行为,同时用eventFilter实现鼠标悬停显示像素值。
允许用户添加继承自pg.EllipseROI的CustomEllipseROI椭圆ROI,默认ROI支持移动和调整大小,但由于修改了ImageContainer的鼠标行为,该功能失效,尝试给CustomEllipseROI添加eventFilter也未解决。
需求: 重新启用ROI的移动、调整大小功能,同时保留自定义鼠标行为,甚至能为ROI定制特定鼠标行为。
解决方案
直接替换self.view的鼠标事件方法会覆盖PyQtGraph内部的事件处理链,导致ROI无法接收鼠标事件。需要保留原有事件处理逻辑,再插入自定义行为,推荐以下实现方式:
方式一:保留原view鼠标事件并追加自定义逻辑
保存view原本的鼠标事件方法,在自定义方法中先调用原方法,确保ROI的事件被正常处理,再执行自定义逻辑。
方式二:给view安装事件过滤器(更灵活)
通过事件过滤器拦截view的鼠标事件,先判断事件是否命中ROI,若ROI已处理事件则跳过自定义逻辑,否则执行自己的鼠标行为。
修改后的完整代码(方式一)
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") # 调用父类方法保证ROI的悬停样式正常 super().hoverEvent(event) 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) # 保存view原本的鼠标事件方法 self.original_view_mouse_press = self.view.mousePressEvent self.original_view_mouse_move = self.view.mouseMoveEvent # 替换为自定义方法 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") # 调用显示像素值的函数 elif event.type() == QEvent.Type.HoverLeave: print("Leave Image") # 清空像素值显示 return super().eventFilter(obj, event) def mouse_press_event(self, event): # 先调用原view的鼠标点击事件,保证ROI能处理点击 self.original_view_mouse_press(event) # 仅当事件未被ROI处理时,执行自定义逻辑 if not event.isAccepted(): self.prevPos = event.pos() def mouse_move_event(self, event): # 先调用原view的鼠标移动事件,保证ROI能处理拖拽/调整 self.original_view_mouse_move(event) # 仅当事件未被ROI处理时,执行自定义逻辑 if not event.isAccepted(): if event.buttons() == Qt.MouseButton.MiddleButton: print("Middle Button on Image") # 调用调整对比度的函数 elif event.buttons() == Qt.MouseButton.LeftButton: print("Left Button on Image") # 调用切换切片的函数 elif event.buttons() == Qt.MouseButton.RightButton: print("Right Button on Image") # 调用缩放的函数 if __name__ == "__main__": app = QApplication(sys.argv) main = ImageContainer() main.show() sys.exit(app.exec())
关键修改说明
- 保存了
self.view原本的鼠标事件方法,在自定义方法中优先调用,确保ROI的事件处理逻辑正常执行。 - 通过
event.isAccepted()判断事件是否被ROI处理,只有未被处理时才执行自定义鼠标行为,避免冲突。 - 在
CustomEllipseROI的hoverEvent中调用父类方法,保证ROI的悬停样式(如控制点高亮)正常显示。
定制ROI特定鼠标行为
如果需要给ROI添加专属鼠标行为,可直接在CustomEllipseROI中重写对应的事件方法,比如:
class CustomEllipseROI(pg.EllipseROI): def __init__(self, pos, size, **args): super().__init__(pos, size, **args) def hoverEvent(self, event): print("Hover ROI") super().hoverEvent(event) def mousePressEvent(self, event): print("Clicked on ROI") # 调用父类方法保证ROI原有功能正常 super().mousePressEvent(event)
内容的提问来源于stack exchange,提问作者Icron
相关产品推荐
相关产品推荐

