mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 11:11:49 +02:00
refactor(image): move image item creation to layer manager
This commit is contained in:
@ -137,13 +137,7 @@ class Image(ImageBase):
|
|||||||
lambda: ImageLayerConfig(monitor=None, monitor_type="auto", source="auto")
|
lambda: ImageLayerConfig(monitor=None, monitor_type="auto", source="auto")
|
||||||
)
|
)
|
||||||
super().__init__(
|
super().__init__(
|
||||||
parent=parent,
|
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
||||||
main_image=ImageItem(parent_image=self),
|
|
||||||
config=config,
|
|
||||||
client=client,
|
|
||||||
gui_id=gui_id,
|
|
||||||
popups=popups,
|
|
||||||
**kwargs,
|
|
||||||
)
|
)
|
||||||
self.layer_removed.connect(self._on_layer_removed)
|
self.layer_removed.connect(self._on_layer_removed)
|
||||||
self.scan_id = None
|
self.scan_id = None
|
||||||
@ -498,6 +492,7 @@ class Image(ImageBase):
|
|||||||
"""
|
"""
|
||||||
Disconnect the image update signals and clean up the image.
|
Disconnect the image update signals and clean up the image.
|
||||||
"""
|
"""
|
||||||
|
self.layer_removed.disconnect(self._on_layer_removed)
|
||||||
for layer_name in list(self.subscriptions.keys()):
|
for layer_name in list(self.subscriptions.keys()):
|
||||||
config = self.subscriptions[layer_name]
|
config = self.subscriptions[layer_name]
|
||||||
if config.monitor is not None:
|
if config.monitor is not None:
|
||||||
|
@ -80,10 +80,12 @@ class ImageLayerManager:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
parent: ImageBase,
|
||||||
plot_item: pg.PlotItem,
|
plot_item: pg.PlotItem,
|
||||||
on_add: SignalInstance | None = None,
|
on_add: SignalInstance | None = None,
|
||||||
on_remove: SignalInstance | None = None,
|
on_remove: SignalInstance | None = None,
|
||||||
):
|
):
|
||||||
|
self.parent = parent
|
||||||
self.plot_item = plot_item
|
self.plot_item = plot_item
|
||||||
self.on_add = on_add
|
self.on_add = on_add
|
||||||
self.on_remove = on_remove
|
self.on_remove = on_remove
|
||||||
@ -92,7 +94,6 @@ class ImageLayerManager:
|
|||||||
def add(
|
def add(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
image: ImageItem,
|
|
||||||
z_position: int | Literal["top", "bottom"] | None = None,
|
z_position: int | Literal["top", "bottom"] | None = None,
|
||||||
sync: ImageLayerSync | None = None,
|
sync: ImageLayerSync | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@ -107,14 +108,17 @@ class ImageLayerManager:
|
|||||||
sync (ImageLayerSync | None): The synchronization settings for the image layer.
|
sync (ImageLayerSync | None): The synchronization settings for the image layer.
|
||||||
**kwargs: ImageLayerSync settings. Only used if sync is None.
|
**kwargs: ImageLayerSync settings. Only used if sync is None.
|
||||||
"""
|
"""
|
||||||
|
if name in self.layers:
|
||||||
|
raise ValueError(f"Layer with name '{name}' already exists.")
|
||||||
if sync is None:
|
if sync is None:
|
||||||
sync = ImageLayerSync(**kwargs)
|
sync = ImageLayerSync(**kwargs)
|
||||||
if z_position is None or z_position == "top":
|
if z_position is None or z_position == "top":
|
||||||
z_position = self._get_top_z_position()
|
z_position = self._get_top_z_position()
|
||||||
elif z_position == "bottom":
|
elif z_position == "bottom":
|
||||||
z_position = self._get_bottom_z_position()
|
z_position = self._get_bottom_z_position()
|
||||||
|
image = ImageItem(parent_image=self.parent, object_name=name)
|
||||||
image.setZValue(z_position)
|
image.setZValue(z_position)
|
||||||
image.destroyed.connect(lambda: self._remove_destroyed_layer(name))
|
image.removed.connect(lambda: self._remove_destroyed_layer(name))
|
||||||
self.layers[name] = ImageLayer(name=name, image=image, sync=sync)
|
self.layers[name] = ImageLayer(name=name, image=image, sync=sync)
|
||||||
self.plot_item.addItem(image)
|
self.plot_item.addItem(image)
|
||||||
|
|
||||||
@ -131,30 +135,28 @@ class ImageLayerManager:
|
|||||||
Args:
|
Args:
|
||||||
layer (str): The name of the layer to remove.
|
layer (str): The name of the layer to remove.
|
||||||
"""
|
"""
|
||||||
self.remove(layer, pop=True)
|
self.remove(layer)
|
||||||
if self.on_remove is not None:
|
if self.on_remove is not None:
|
||||||
self.on_remove.emit(layer)
|
self.on_remove.emit(layer)
|
||||||
|
|
||||||
def remove(self, layer: ImageLayer | str, pop=True):
|
def remove(self, layer: ImageLayer | str):
|
||||||
"""
|
"""
|
||||||
Remove an image layer from the widget.
|
Remove an image layer from the widget.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
layer (ImageLayer | str): The image layer to remove. Can be the layer object or the name of the layer.
|
layer (ImageLayer | str): The image layer to remove. Can be the layer object or the name of the layer.
|
||||||
pop (bool): Whether to remove the layer from the manager's layers dictionary. If False, the layer is only removed from the plot item.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(layer, str):
|
if isinstance(layer, str):
|
||||||
name = layer
|
name = layer
|
||||||
else:
|
else:
|
||||||
name = layer.name
|
name = layer.name
|
||||||
if pop:
|
|
||||||
removed_layer = self.layers.pop(name, None)
|
removed_layer = self.layers.pop(name, None)
|
||||||
else:
|
|
||||||
removed_layer = self.layers.get(name, None)
|
|
||||||
if not removed_layer:
|
if not removed_layer:
|
||||||
return
|
return
|
||||||
self.plot_item.removeItem(removed_layer.image)
|
self.plot_item.removeItem(removed_layer.image)
|
||||||
removed_layer.image.remove()
|
removed_layer.image.remove(emit=False)
|
||||||
removed_layer.image.deleteLater()
|
removed_layer.image.deleteLater()
|
||||||
removed_layer.image = None
|
removed_layer.image = None
|
||||||
|
|
||||||
@ -162,9 +164,9 @@ class ImageLayerManager:
|
|||||||
"""
|
"""
|
||||||
Clear all image layers from the manager.
|
Clear all image layers from the manager.
|
||||||
"""
|
"""
|
||||||
for layer in self.layers.values():
|
for layer in list(self.layers.keys()):
|
||||||
# Remove each layer from the plot item and delete it
|
# Remove each layer from the plot item and delete it
|
||||||
self.remove(layer, pop=False)
|
self.remove(layer)
|
||||||
self.layers.clear()
|
self.layers.clear()
|
||||||
|
|
||||||
def _get_top_z_position(self) -> int:
|
def _get_top_z_position(self) -> int:
|
||||||
@ -234,11 +236,9 @@ class ImageBase(PlotBase):
|
|||||||
layer_added = Signal(str)
|
layer_added = Signal(str)
|
||||||
layer_removed = Signal(str)
|
layer_removed = Signal(str)
|
||||||
|
|
||||||
def __init__(self, *args, main_image: ImageItem, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize the ImageBase widget.
|
Initialize the ImageBase widget.
|
||||||
Args:
|
|
||||||
main_image (ImageItem): The main image item to be displayed. This is the main image layer, also used as reference for autoranging.
|
|
||||||
"""
|
"""
|
||||||
self.x_roi = None
|
self.x_roi = None
|
||||||
self.y_roi = None
|
self.y_roi = None
|
||||||
@ -248,9 +248,9 @@ class ImageBase(PlotBase):
|
|||||||
# Headless controller keeps the canonical list.
|
# Headless controller keeps the canonical list.
|
||||||
self.roi_manager_dialog = None
|
self.roi_manager_dialog = None
|
||||||
self.layers: ImageLayerManager = ImageLayerManager(
|
self.layers: ImageLayerManager = ImageLayerManager(
|
||||||
self.plot_item, on_add=self.layer_added, on_remove=self.layer_removed
|
self, plot_item=self.plot_item, on_add=self.layer_added, on_remove=self.layer_removed
|
||||||
)
|
)
|
||||||
self.layers.add("main", main_image)
|
self.layers.add("main")
|
||||||
|
|
||||||
self.autorange = True
|
self.autorange = True
|
||||||
self.autorange_mode = "mean"
|
self.autorange_mode = "mean"
|
||||||
@ -644,7 +644,7 @@ class ImageBase(PlotBase):
|
|||||||
else:
|
else:
|
||||||
x = coordinates[1]
|
x = coordinates[1]
|
||||||
y = coordinates[2]
|
y = coordinates[2]
|
||||||
image = self.main_image.image
|
image = self.layers["main"].image.image
|
||||||
if image is None:
|
if image is None:
|
||||||
return
|
return
|
||||||
max_row, max_col = image.shape[0] - 1, image.shape[1] - 1
|
max_row, max_col = image.shape[0] - 1, image.shape[1] - 1
|
||||||
|
@ -43,6 +43,7 @@ class ImageItemConfig(ConnectionConfig): # TODO review config
|
|||||||
|
|
||||||
|
|
||||||
class ImageItem(BECConnector, pg.ImageItem):
|
class ImageItem(BECConnector, pg.ImageItem):
|
||||||
|
|
||||||
RPC = True
|
RPC = True
|
||||||
USER_ACCESS = [
|
USER_ACCESS = [
|
||||||
"color_map",
|
"color_map",
|
||||||
@ -69,6 +70,7 @@ class ImageItem(BECConnector, pg.ImageItem):
|
|||||||
]
|
]
|
||||||
|
|
||||||
vRangeChangedManually = Signal(tuple)
|
vRangeChangedManually = Signal(tuple)
|
||||||
|
removed = Signal()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -274,6 +276,8 @@ class ImageItem(BECConnector, pg.ImageItem):
|
|||||||
self.buffer = []
|
self.buffer = []
|
||||||
self.max_len = 0
|
self.max_len = 0
|
||||||
|
|
||||||
def remove(self):
|
def remove(self, emit: bool = True):
|
||||||
self.clear()
|
self.clear()
|
||||||
super().remove()
|
super().remove()
|
||||||
|
if emit:
|
||||||
|
self.removed.emit()
|
||||||
|
@ -10,7 +10,7 @@ from bec_widgets.widgets.plots.image.image_item import ImageItem
|
|||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def image_layer_manager():
|
def image_layer_manager():
|
||||||
"""Fixture to create an instance of ImageLayer."""
|
"""Fixture to create an instance of ImageLayer."""
|
||||||
layer = ImageLayerManager(plot_item=mock.MagicMock(spec=pg.PlotItem))
|
layer = ImageLayerManager(parent=None, plot_item=mock.MagicMock(spec=pg.PlotItem))
|
||||||
yield layer
|
yield layer
|
||||||
layer.clear()
|
layer.clear()
|
||||||
|
|
||||||
@ -23,27 +23,22 @@ def test_image_layer_manager_initialization(image_layer_manager):
|
|||||||
|
|
||||||
def test_add_image_layer(image_layer_manager):
|
def test_add_image_layer(image_layer_manager):
|
||||||
"""Test adding an image layer to the ImageLayerManager."""
|
"""Test adding an image layer to the ImageLayerManager."""
|
||||||
image = ImageItem()
|
layer = image_layer_manager.add(name="Test Layer")
|
||||||
layer = image_layer_manager.add(name="Test Layer", image=image)
|
|
||||||
assert layer.image.zValue() == 0
|
assert layer.image.zValue() == 0
|
||||||
|
|
||||||
image2 = ImageItem()
|
layer2 = image_layer_manager.add(name="Test Layer 2")
|
||||||
layer2 = image_layer_manager.add(name="Test Layer 2", image=image2)
|
|
||||||
assert layer2.image.zValue() == 1
|
assert layer2.image.zValue() == 1
|
||||||
|
|
||||||
image3 = ImageItem()
|
layer3 = image_layer_manager.add(name="Test Layer 3", z_position="top")
|
||||||
layer3 = image_layer_manager.add(name="Test Layer 3", image=image3, z_position="top")
|
|
||||||
assert layer3.image.zValue() == 2
|
assert layer3.image.zValue() == 2
|
||||||
|
|
||||||
image4 = ImageItem()
|
layer4 = image_layer_manager.add(name="Test Layer 4", z_position="bottom")
|
||||||
layer4 = image_layer_manager.add(name="Test Layer 4", image=image4, z_position="bottom")
|
|
||||||
assert layer4.image.zValue() == -1
|
assert layer4.image.zValue() == -1
|
||||||
|
|
||||||
|
|
||||||
def test_remove_image_layer(image_layer_manager):
|
def test_remove_image_layer(image_layer_manager):
|
||||||
"""Test removing an image layer from the ImageLayerManager."""
|
"""Test removing an image layer from the ImageLayerManager."""
|
||||||
image = ImageItem()
|
layer = image_layer_manager.add(name="Test Layer")
|
||||||
layer = image_layer_manager.add(name="Test Layer", image=image)
|
|
||||||
assert len(image_layer_manager) == 1
|
assert len(image_layer_manager) == 1
|
||||||
|
|
||||||
image_layer_manager.remove(layer)
|
image_layer_manager.remove(layer)
|
||||||
@ -52,8 +47,7 @@ def test_remove_image_layer(image_layer_manager):
|
|||||||
|
|
||||||
def test_clear_image_layers(image_layer_manager):
|
def test_clear_image_layers(image_layer_manager):
|
||||||
"""Test clearing all image layers from the ImageLayerManager."""
|
"""Test clearing all image layers from the ImageLayerManager."""
|
||||||
image = ImageItem()
|
layer = image_layer_manager.add(name="Test Layer")
|
||||||
layer = image_layer_manager.add(name="Test Layer", image=image)
|
|
||||||
assert len(image_layer_manager) == 1
|
assert len(image_layer_manager) == 1
|
||||||
|
|
||||||
image_layer_manager.clear()
|
image_layer_manager.clear()
|
||||||
@ -62,8 +56,7 @@ def test_clear_image_layers(image_layer_manager):
|
|||||||
|
|
||||||
def test_image_layer_manager_getitem(image_layer_manager):
|
def test_image_layer_manager_getitem(image_layer_manager):
|
||||||
"""Test getting an image layer by index."""
|
"""Test getting an image layer by index."""
|
||||||
image = ImageItem()
|
layer = image_layer_manager.add(name="Test Layer")
|
||||||
layer = image_layer_manager.add(name="Test Layer", image=image)
|
|
||||||
assert image_layer_manager["Test Layer"] == layer
|
assert image_layer_manager["Test Layer"] == layer
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
@ -75,10 +68,8 @@ def test_image_layer_manager_getitem(image_layer_manager):
|
|||||||
|
|
||||||
def test_image_layer_iteration(image_layer_manager):
|
def test_image_layer_iteration(image_layer_manager):
|
||||||
"""Test iterating over image layers."""
|
"""Test iterating over image layers."""
|
||||||
image = ImageItem()
|
layer = image_layer_manager.add(name="Test Layer")
|
||||||
layer = image_layer_manager.add(name="Test Layer", image=image)
|
|
||||||
assert list(image_layer_manager) == [layer]
|
assert list(image_layer_manager) == [layer]
|
||||||
|
|
||||||
image2 = ImageItem()
|
layer2 = image_layer_manager.add(name="Test Layer 2")
|
||||||
layer2 = image_layer_manager.add(name="Test Layer 2", image=image2)
|
|
||||||
assert list(image_layer_manager) == [layer, layer2]
|
assert list(image_layer_manager) == [layer, layer2]
|
||||||
|
@ -117,8 +117,8 @@ def test_image_setup_image_2d(qtbot, mocked_client):
|
|||||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||||
bec_image_view.image(monitor="eiger", monitor_type="2d")
|
bec_image_view.image(monitor="eiger", monitor_type="2d")
|
||||||
assert bec_image_view.monitor == "eiger"
|
assert bec_image_view.monitor == "eiger"
|
||||||
assert bec_image_view.main_image.config.source == "device_monitor_2d"
|
assert bec_image_view.subscriptions["main"].source == "device_monitor_2d"
|
||||||
assert bec_image_view.main_image.config.monitor_type == "2d"
|
assert bec_image_view.subscriptions["main"].monitor_type == "2d"
|
||||||
assert bec_image_view.main_image.raw_data is None
|
assert bec_image_view.main_image.raw_data is None
|
||||||
assert bec_image_view.main_image.image is None
|
assert bec_image_view.main_image.image is None
|
||||||
|
|
||||||
@ -127,8 +127,8 @@ def test_image_setup_image_1d(qtbot, mocked_client):
|
|||||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||||
bec_image_view.image(monitor="eiger", monitor_type="1d")
|
bec_image_view.image(monitor="eiger", monitor_type="1d")
|
||||||
assert bec_image_view.monitor == "eiger"
|
assert bec_image_view.monitor == "eiger"
|
||||||
assert bec_image_view.main_image.config.source == "device_monitor_1d"
|
assert bec_image_view.subscriptions["main"].source == "device_monitor_1d"
|
||||||
assert bec_image_view.main_image.config.monitor_type == "1d"
|
assert bec_image_view.subscriptions["main"].monitor_type == "1d"
|
||||||
assert bec_image_view.main_image.raw_data is None
|
assert bec_image_view.main_image.raw_data is None
|
||||||
assert bec_image_view.main_image.image is None
|
assert bec_image_view.main_image.image is None
|
||||||
|
|
||||||
@ -137,8 +137,8 @@ def test_image_setup_image_auto(qtbot, mocked_client):
|
|||||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||||
bec_image_view.image(monitor="eiger", monitor_type="auto")
|
bec_image_view.image(monitor="eiger", monitor_type="auto")
|
||||||
assert bec_image_view.monitor == "eiger"
|
assert bec_image_view.monitor == "eiger"
|
||||||
assert bec_image_view.main_image.config.source == "auto"
|
assert bec_image_view.subscriptions["main"].source == "auto"
|
||||||
assert bec_image_view.main_image.config.monitor_type == "auto"
|
assert bec_image_view.subscriptions["main"].monitor_type == "auto"
|
||||||
assert bec_image_view.main_image.raw_data is None
|
assert bec_image_view.main_image.raw_data is None
|
||||||
assert bec_image_view.main_image.image is None
|
assert bec_image_view.main_image.image is None
|
||||||
|
|
||||||
@ -235,8 +235,8 @@ def test_setup_image_from_toolbar(qtbot, mocked_client):
|
|||||||
bec_image_view.selection_bundle.dim_combo_box.setCurrentText("2d")
|
bec_image_view.selection_bundle.dim_combo_box.setCurrentText("2d")
|
||||||
|
|
||||||
assert bec_image_view.monitor == "eiger"
|
assert bec_image_view.monitor == "eiger"
|
||||||
assert bec_image_view.main_image.config.source == "device_monitor_2d"
|
assert bec_image_view.subscriptions["main"].source == "device_monitor_2d"
|
||||||
assert bec_image_view.main_image.config.monitor_type == "2d"
|
assert bec_image_view.subscriptions["main"].monitor_type == "2d"
|
||||||
assert bec_image_view.main_image.raw_data is None
|
assert bec_image_view.main_image.raw_data is None
|
||||||
assert bec_image_view.main_image.image is None
|
assert bec_image_view.main_image.image is None
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user