1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-09 10:17:50 +01:00
Files
bec_widgets/tests/unit_tests/test_image_view_next_gen.py

486 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import numpy as np
import pyqtgraph as pg
import pytest
from bec_widgets.widgets.plots.image.image import Image
from tests.unit_tests.client_mocks import mocked_client
from tests.unit_tests.conftest import create_widget
##################################################
# Image widget base functionality tests
##################################################
def test_initialization_defaults(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
assert bec_image_view.color_map == "plasma"
assert bec_image_view.autorange is True
assert bec_image_view.autorange_mode == "mean"
assert bec_image_view.config.lock_aspect_ratio is True
assert bec_image_view.main_image is not None
assert bec_image_view._color_bar is None
def test_setting_color_map(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.color_map = "viridis"
assert bec_image_view.color_map == "viridis"
assert bec_image_view.config.color_map == "viridis"
def test_invalid_color_map_handling(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
previous_colormap = bec_image_view.color_map
bec_image_view.color_map = "invalid_colormap_name"
assert bec_image_view.color_map == previous_colormap
assert bec_image_view.main_image.color_map == previous_colormap
def test_toggle_autorange(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.autorange = False
assert bec_image_view.autorange is False
bec_image_view.toggle_autorange(True, "max")
assert bec_image_view.autorange is True
assert bec_image_view.autorange_mode == "max"
assert bec_image_view.main_image.autorange is True
assert bec_image_view.main_image.autorange_mode == "max"
assert bec_image_view.main_image.config.autorange is True
assert bec_image_view.main_image.config.autorange_mode == "max"
def test_lock_aspect_ratio(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.lock_aspect_ratio = True
assert bec_image_view.lock_aspect_ratio is True
assert bool(bec_image_view.plot_item.getViewBox().state["aspectLocked"]) is True
assert bec_image_view.config.lock_aspect_ratio is True
def test_set_vrange(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.vrange = (10, 100)
assert bec_image_view.vrange == (10, 100)
assert bec_image_view.main_image.levels == (10, 100)
assert bec_image_view.main_image.config.v_range == (10, 100)
def test_enable_simple_colorbar(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.enable_simple_colorbar = True
assert bec_image_view.enable_simple_colorbar is True
assert bec_image_view.config.color_bar == "simple"
assert isinstance(bec_image_view._color_bar, pg.ColorBarItem)
# Enabling color bar should not cancel autorange
assert bec_image_view.autorange is True
assert bec_image_view.autorange_mode == "mean"
assert bec_image_view.main_image.autorange is True
assert bec_image_view.main_image.autorange_mode == "mean"
def test_enable_full_colorbar(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.enable_full_colorbar = True
assert bec_image_view.enable_full_colorbar is True
assert bec_image_view.config.color_bar == "full"
assert isinstance(bec_image_view._color_bar, pg.HistogramLUTItem)
# Enabling color bar should not cancel autorange
assert bec_image_view.autorange is True
assert bec_image_view.autorange_mode == "mean"
assert bec_image_view.main_image.autorange is True
assert bec_image_view.main_image.autorange_mode == "mean"
@pytest.mark.parametrize("colorbar_type", ["simple", "full"])
def test_enable_colorbar_with_vrange(qtbot, mocked_client, colorbar_type):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.enable_colorbar(True, colorbar_type, (0, 100))
if colorbar_type == "simple":
assert isinstance(bec_image_view._color_bar, pg.ColorBarItem)
assert bec_image_view.enable_simple_colorbar is True
else:
assert isinstance(bec_image_view._color_bar, pg.HistogramLUTItem)
assert bec_image_view.enable_full_colorbar is True
assert bec_image_view.config.color_bar == colorbar_type
assert bec_image_view.vrange == (0, 100)
assert bec_image_view.main_image.levels == (0, 100)
assert bec_image_view._color_bar is not None
def test_image_setup_image_2d(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.image(monitor="eiger", monitor_type="2d")
assert bec_image_view.monitor == "eiger"
assert bec_image_view.main_image.config.source == "device_monitor_2d"
assert bec_image_view.main_image.config.monitor_type == "2d"
assert bec_image_view.main_image.raw_data is None
assert bec_image_view.main_image.image is None
def test_image_setup_image_1d(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.image(monitor="eiger", monitor_type="1d")
assert bec_image_view.monitor == "eiger"
assert bec_image_view.main_image.config.source == "device_monitor_1d"
assert bec_image_view.main_image.config.monitor_type == "1d"
assert bec_image_view.main_image.raw_data is None
assert bec_image_view.main_image.image is None
def test_image_setup_image_auto(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.image(monitor="eiger", monitor_type="auto")
assert bec_image_view.monitor == "eiger"
assert bec_image_view.main_image.config.source == "auto"
assert bec_image_view.main_image.config.monitor_type == "auto"
assert bec_image_view.main_image.raw_data is None
assert bec_image_view.main_image.image is None
def test_image_data_update_2d(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
test_data = np.random.rand(20, 30)
message = {"data": test_data}
metadata = {}
bec_image_view.on_image_update_2d(message, metadata)
np.testing.assert_array_equal(bec_image_view._main_image.image, test_data)
def test_image_data_update_1d(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
waveform1 = np.random.rand(50)
waveform2 = np.random.rand(60) # Different length, tests padding logic
metadata = {"scan_id": "scan_test"}
bec_image_view.on_image_update_1d({"data": waveform1}, metadata)
assert bec_image_view._main_image.raw_data.shape == (1, 50)
bec_image_view.on_image_update_1d({"data": waveform2}, metadata)
assert bec_image_view._main_image.raw_data.shape == (2, 60)
def test_toolbar_actions_presence(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
assert "autorange_image" in bec_image_view.toolbar.widgets
assert "lock_aspect_ratio" in bec_image_view.toolbar.bundles["mouse_interaction"]
assert "processing" in bec_image_view.toolbar.bundles
assert "selection" in bec_image_view.toolbar.bundles
def test_image_processing_fft_toggle(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.fft = True
assert bec_image_view.fft is True
bec_image_view.fft = False
assert bec_image_view.fft is False
def test_image_processing_log_toggle(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.log = True
assert bec_image_view.log is True
bec_image_view.log = False
assert bec_image_view.log is False
def test_image_rotation_and_transpose(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.num_rotation_90 = 2
assert bec_image_view.num_rotation_90 == 2
bec_image_view.transpose = True
assert bec_image_view.transpose is True
@pytest.mark.parametrize("colorbar_type", ["none", "simple", "full"])
def test_setting_vrange_with_colorbar(qtbot, mocked_client, colorbar_type):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
if colorbar_type == "simple":
bec_image_view.enable_simple_colorbar = True
elif colorbar_type == "full":
bec_image_view.enable_full_colorbar = True
bec_image_view.vrange = (0, 100)
assert bec_image_view.vrange == (0, 100)
assert bec_image_view.main_image.levels == (0, 100)
assert bec_image_view.main_image.config.v_range == (0, 100)
assert bec_image_view.v_min == 0
assert bec_image_view.v_max == 100
if colorbar_type == "simple":
assert isinstance(bec_image_view._color_bar, pg.ColorBarItem)
assert bec_image_view._color_bar.levels() == (0, 100)
elif colorbar_type == "full":
assert isinstance(bec_image_view._color_bar, pg.HistogramLUTItem)
assert bec_image_view._color_bar.getLevels() == (0, 100)
###################################
# Toolbar Actions
###################################
def test_setup_image_from_toolbar(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.selection_bundle.device_combo_box.setCurrentText("eiger")
bec_image_view.selection_bundle.dim_combo_box.setCurrentText("2d")
assert bec_image_view.monitor == "eiger"
assert bec_image_view.main_image.config.source == "device_monitor_2d"
assert bec_image_view.main_image.config.monitor_type == "2d"
assert bec_image_view.main_image.raw_data is None
assert bec_image_view.main_image.image is None
def test_image_actions_interactions(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.autorange = False # Change the initial state to False
bec_image_view.autorange_mean_action.action.trigger()
assert bec_image_view.autorange is True
assert bec_image_view.main_image.autorange is True
assert bec_image_view.autorange_mode == "mean"
bec_image_view.autorange_max_action.action.trigger()
assert bec_image_view.autorange is True
assert bec_image_view.main_image.autorange is True
assert bec_image_view.autorange_mode == "max"
bec_image_view.toolbar.widgets["lock_aspect_ratio"].action.trigger()
assert bec_image_view.lock_aspect_ratio is False
assert bool(bec_image_view.plot_item.getViewBox().state["aspectLocked"]) is False
def test_image_toggle_action_fft(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.processing_bundle.fft.action.trigger()
assert bec_image_view.fft is True
assert bec_image_view.main_image.fft is True
assert bec_image_view.main_image.config.processing.fft is True
def test_image_toggle_action_log(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.processing_bundle.log.action.trigger()
assert bec_image_view.log is True
assert bec_image_view.main_image.log is True
assert bec_image_view.main_image.config.processing.log is True
def test_image_toggle_action_transpose(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.processing_bundle.transpose.action.trigger()
assert bec_image_view.transpose is True
assert bec_image_view.main_image.transpose is True
assert bec_image_view.main_image.config.processing.transpose is True
def test_image_toggle_action_rotate_right(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.processing_bundle.right.action.trigger()
assert bec_image_view.num_rotation_90 == 3
assert bec_image_view.main_image.num_rotation_90 == 3
assert bec_image_view.main_image.config.processing.num_rotation_90 == 3
def test_image_toggle_action_rotate_left(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
bec_image_view.processing_bundle.left.action.trigger()
assert bec_image_view.num_rotation_90 == 1
assert bec_image_view.main_image.num_rotation_90 == 1
assert bec_image_view.main_image.config.processing.num_rotation_90 == 1
def test_image_toggle_action_reset(qtbot, mocked_client):
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
# Setup some processing
bec_image_view.fft = True
bec_image_view.log = True
bec_image_view.transpose = True
bec_image_view.num_rotation_90 = 2
bec_image_view.processing_bundle.reset.action.trigger()
assert bec_image_view.num_rotation_90 == 0
assert bec_image_view.main_image.num_rotation_90 == 0
assert bec_image_view.main_image.config.processing.num_rotation_90 == 0
assert bec_image_view.fft is False
assert bec_image_view.main_image.fft is False
assert bec_image_view.log is False
assert bec_image_view.main_image.log is False
assert bec_image_view.transpose is False
assert bec_image_view.main_image.transpose is False
def test_roi_add_remove_and_properties(qtbot, mocked_client):
view = create_widget(qtbot, Image, client=mocked_client)
# Add ROIs
rect = view.add_roi(kind="rect", name="rect_roi", line_width=7)
circ = view.add_roi(kind="circle", name="circ_roi", line_width=5)
assert rect in view.roi_controller.rois
assert circ in view.roi_controller.rois
assert rect.label == "rect_roi"
assert circ.label == "circ_roi"
assert rect.line_width == 7
assert circ.line_width == 5
# Change properties
rect.label = "rect_roi2"
circ.line_color = "#ff0000"
assert rect.label == "rect_roi2"
assert circ.line_color == "#ff0000"
# Remove by name
view.remove_roi("rect_roi2")
assert rect not in view.roi_controller.rois
# Remove by index
view.remove_roi(0)
assert not view.roi_controller.rois
def test_roi_controller_palette_signal(qtbot, mocked_client):
view = create_widget(qtbot, Image, client=mocked_client)
controller = view.roi_controller
changed = []
controller.paletteChanged.connect(lambda cmap: changed.append(cmap))
view.add_roi(kind="rect")
controller.colormap = "plasma"
assert changed and changed[0] == "plasma"
def test_roi_controller_clear_and_get_methods(qtbot, mocked_client):
view = create_widget(qtbot, Image, client=mocked_client)
r1 = view.add_roi(kind="rect", name="r1")
r2 = view.add_roi(kind="circle", name="c1")
controller = view.roi_controller
assert controller.get_roi_by_name("r1") == r1
assert controller.get_roi(1) == r2
controller.clear()
assert not controller.rois
def test_roi_get_data_from_image_with_no_image(qtbot, mocked_client):
view = create_widget(qtbot, Image, client=mocked_client)
roi = view.add_roi(kind="rect")
# Remove all images from scene
for item in list(view.plot_item.items):
if hasattr(item, "image"):
view.plot_item.removeItem(item)
with pytest.raises(RuntimeError):
roi.get_data_from_image()
##################################################
# Settings and popups
##################################################
def test_show_roi_manager_popup(qtbot, mocked_client):
"""
Verify that the ROI-manager dialog opens and closes correctly,
and that the matching toolbar icon stays in sync.
"""
view = create_widget(qtbot, Image, client=mocked_client, popups=True)
# ROI-manager toggle is exposed via the toolbar.
assert "roi_mgr" in view.toolbar.widgets
roi_action = view.toolbar.widgets["roi_mgr"].action
assert roi_action.isChecked() is False, "Should start unchecked"
# Open the popup.
view.show_roi_manager_popup()
assert view.roi_manager_dialog is not None
assert view.roi_manager_dialog.isVisible()
assert roi_action.isChecked() is True, "Icon should toggle on"
# Close again.
view.roi_manager_dialog.close()
assert view.roi_manager_dialog is None
assert roi_action.isChecked() is False, "Icon should toggle off"
###################################
# ROI Plots & Crosshair Switch
###################################
def test_crosshair_roi_panels_visibility(qtbot, mocked_client):
"""
Verify that enabling the ROIcrosshair shows ROI panels and disabling hides them.
"""
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
switch = bec_image_view.toolbar.widgets["switch_crosshair"]
# Initially panels should be hidden
assert bec_image_view.side_panel_x.panel_height == 0
assert bec_image_view.side_panel_y.panel_width == 0
# Enable ROI crosshair
switch.actions["crosshair_roi"].action.trigger()
qtbot.wait(500)
# Panels must be visible
assert bec_image_view.side_panel_x.panel_height > 0
assert bec_image_view.side_panel_y.panel_width > 0
# Disable ROI crosshair
switch.actions["crosshair_roi"].action.trigger()
qtbot.wait(500)
# Panels hidden again
assert bec_image_view.side_panel_x.panel_height == 0
assert bec_image_view.side_panel_y.panel_width == 0
def test_roi_plot_data_from_image(qtbot, mocked_client):
"""
Check that ROI plots receive correct slice data from the 2D image.
"""
import numpy as np
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
# Provide deterministic 2D data
test_data = np.arange(25).reshape(5, 5)
bec_image_view.on_image_update_2d({"data": test_data}, {})
# Activate ROI crosshair
switch = bec_image_view.toolbar.widgets["switch_crosshair"]
switch.actions["crosshair_roi"].action.trigger()
qtbot.wait(50)
# Simulate crosshair at row 2, col 3
bec_image_view.update_image_slices((0, 2, 3))
# Extract plotted data
x_items = bec_image_view.x_roi.plot_item.listDataItems()
y_items = bec_image_view.y_roi.plot_item.listDataItems()
assert len(x_items) == 1
assert len(y_items) == 1
# Vertical slice (column)
_, v_slice = x_items[0].getData()
np.testing.assert_array_equal(v_slice, test_data[:, 3])
# Horizontal slice (row)
h_slice, _ = y_items[0].getData()
np.testing.assert_array_equal(h_slice, test_data[2])