diff --git a/bec_widgets/widgets/plots/image/setting_widgets/image_roi_tree.py b/bec_widgets/widgets/plots/image/setting_widgets/image_roi_tree.py index 174d2c1d..c7fa5cad 100644 --- a/bec_widgets/widgets/plots/image/setting_widgets/image_roi_tree.py +++ b/bec_widgets/widgets/plots/image/setting_widgets/image_roi_tree.py @@ -3,6 +3,7 @@ from __future__ import annotations import math from typing import TYPE_CHECKING +from bec_lib import bec_logger from bec_qthemes import material_icon from qtpy.QtCore import QEvent, Qt from qtpy.QtGui import QColor @@ -39,6 +40,9 @@ if TYPE_CHECKING: from bec_widgets.widgets.plots.image.image import Image +logger = bec_logger.logger + + class ROILockButton(QToolButton): """Keeps its icon and checked state in sync with a single ROI.""" @@ -447,6 +451,18 @@ class ROIPropertyTree(BECWidget, QWidget): def cleanup(self): self.cmap.close() self.cmap.deleteLater() + if self.controller and hasattr(self.controller, "rois"): + for roi in self.controller.rois: # disconnect all signals from ROIs + try: + if isinstance(roi, RectangularROI): + roi.edgesChanged.disconnect() + else: + roi.centerChanged.disconnect() + roi.penChanged.disconnect() + roi.nameChanged.disconnect() + except (RuntimeError, TypeError) as e: + logger.error(f"Failed to disconnect roi qt signal: {e}") + super().cleanup() diff --git a/tests/unit_tests/test_image_roi_tree.py b/tests/unit_tests/test_image_roi_tree.py index c0dc38ea..6480e870 100644 --- a/tests/unit_tests/test_image_roi_tree.py +++ b/tests/unit_tests/test_image_roi_tree.py @@ -399,3 +399,35 @@ def test_new_roi_respects_global_lock(roi_tree, image_widget, qtbot): assert not roi.movable # Disable global lock again roi_tree.lock_all_action.action.setChecked(False) + + +def test_cleanup_disconnect_signals(roi_tree, image_widget): + """Test that cleanup disconnects ROI signals so further changes do not update the tree.""" + # Add a rectangular ROI + roi = image_widget.add_roi(kind="rect", name="cleanup_test", pos=(10, 10), size=(20, 20)) + item = roi_tree.roi_items[roi] + + # Test that signals are connected before cleanup + pre_name = item.text(roi_tree.COL_ROI) + pre_coord = item.child(2).text(roi_tree.COL_PROPS) + # Change ROI properties to see updates + roi.label = "connected_name" + roi.setPos(30, 30) + # Verify that the tree item updated + assert item.text(roi_tree.COL_ROI) == "connected_name" + assert item.child(2).text(roi_tree.COL_PROPS) != pre_coord + + # Perform cleanup to disconnect signals + roi_tree.cleanup() + + # Store initial state + initial_name = item.text(roi_tree.COL_ROI) + initial_coord = item.child(2).text(roi_tree.COL_PROPS) + + # Change ROI properties after cleanup + roi.label = "changed_name" + roi.setPos(50, 50) + + # Verify that the tree item was not updated + assert item.text(roi_tree.COL_ROI) == initial_name + assert item.child(2).text(roi_tree.COL_PROPS) == initial_coord