fix(image): guard layer access during/after teardown

This commit is contained in:
2026-07-14 12:53:04 +02:00
committed by Jan Wyzula
parent c1ea65f7f9
commit 9c6fa74f65
2 changed files with 34 additions and 2 deletions
+11 -1
View File
@@ -1036,6 +1036,8 @@ class ImageBase(PlotBase):
"""
# FIXME: this should be made more general
if not self.layer_manager:
return False
return self.layer_manager["main"].image.autorange
@autorange.setter
@@ -1056,6 +1058,8 @@ class ImageBase(PlotBase):
enabled(bool): Whether to enable autorange.
sync(bool): Whether to synchronize the autorange state across all layers.
"""
if not self.layer_manager:
return
for layer in self.layer_manager:
if not layer.sync.autorange:
continue
@@ -1077,6 +1081,8 @@ class ImageBase(PlotBase):
- "mean": Use the mean value of the image for autoranging.
"""
if not self.layer_manager:
return "mean"
return self.layer_manager["main"].image.autorange_mode
@autorange_mode.setter
@@ -1090,6 +1096,8 @@ class ImageBase(PlotBase):
# for qt Designer
if mode not in ["max", "mean"]:
return
if not self.layer_manager:
return
for layer in self.layer_manager:
if not layer.sync.autorange_mode:
continue
@@ -1125,6 +1133,8 @@ class ImageBase(PlotBase):
"""
Synchronize the autorange switch with the current autorange state and mode if changed from outside.
"""
if not self.layer_manager:
return
action: SwitchableToolBarAction = self.toolbar.components.get_action("image_autorange") # type: ignore
with action.signal_blocker():
action.set_default_action(f"{self.layer_manager['main'].image.autorange_mode}")
@@ -1133,7 +1143,7 @@ class ImageBase(PlotBase):
def _sync_colorbar_levels(self):
"""Immediately propagate current levels to the active colorbar."""
if not self._color_bar:
if not self._color_bar or not self.layer_manager:
return
total_vrange = (0, 0)
+23 -1
View File
@@ -1456,4 +1456,26 @@ def test_adjust_image_buffer_coerces_list_and_scalar(qtbot, mocked_client):
buf = view.adjust_image_buffer(image, [1, 2, 3]) # python list
assert buf.shape == (1, 3)
buf = view.adjust_image_buffer(image, np.array(42.0)) # 0-d scalar
assert buf.shape[0] == 2
assert buf.shape[0] == 2
##############################################
# Teardown safety
##############################################
def test_layer_accessors_safe_after_teardown(qtbot, mocked_client):
"""Once layer_manager is gone (post-cleanup), signal-driven accessors must
not raise. They used to subscript None or resurrect the 'main' layer."""
view = create_widget(qtbot, Image, client=mocked_client)
view.enable_full_colorbar = True
view.layer_manager = None # simulate post-cleanup state
# None of these should raise:
assert view.autorange is False
assert view.autorange_mode == "mean"
view.toggle_autorange(True, "mean")
view._set_autorange(True)
view.autorange_mode = "max"
view._sync_autorange_switch()
view._sync_colorbar_levels()