Files
tomcat_bec/tests/tests_devices/test_gfcam.py
gac-x05la ae85d179f5 refactor: std daq integration
Major refactor of the std daq integration for the PCO Edge camera and Gigafrost camera.
New live processing capabilities have been added, and the code has been cleaned up for better maintainability.
2025-06-16 16:59:08 +02:00

338 lines
14 KiB
Python

from unittest import mock
import pytest
from tomcat_bec.devices.gigafrost.gigafrost_base import GigaFrostBase
from tomcat_bec.devices.gigafrost.gigafrostcamera import GigaFrostCamera, default_config
from tomcat_bec.devices.std_daq.std_daq_client import StdDaqClient
from tomcat_bec.devices.std_daq.std_daq_preview import StdDaqPreview
@pytest.fixture()
def gfcam_base():
gfcam = GigaFrostCamera(
"X02DA-CAM-GF2:",
name="gfcam",
std_daq_rest="http://example.com/rest",
std_daq_ws="ws://example.com/ws",
)
for component in gfcam.component_names:
type.__setattr__(GigaFrostCamera, component, mock.MagicMock())
yield gfcam
def test_gfcam_init_raises_without_rest_ws():
with pytest.raises(ValueError) as excinfo:
GigaFrostCamera("X02DA-CAM-GF2:", name="gfcam")
excinfo.match("std_daq_rest and std_daq_ws must be provided")
def test_gfcam_init():
gfcam = GigaFrostCamera(
"X02DA-CAM-GF2:",
name="gfcam",
std_daq_rest="http://example.com/rest",
std_daq_ws="ws://example.com/ws",
)
assert gfcam.name == "gfcam"
assert isinstance(gfcam.backend, StdDaqClient)
assert gfcam.live_preview is None
def test_gfcam_init_with_live_preview():
gfcam = GigaFrostCamera(
"X02DA-CAM-GF2:",
name="gfcam",
std_daq_rest="http://example.com/rest",
std_daq_ws="ws://example.com/ws",
std_daq_live="http://example.com/live_preview",
)
assert gfcam.live_preview is not None
assert isinstance(gfcam.live_preview, StdDaqPreview)
def test_gfcam_configure(gfcam_base):
with mock.patch.object(gfcam_base, "stop_camera") as stop_camera:
with mock.patch.object(gfcam_base.backend, "set_config") as set_config:
with mock.patch.object(GigaFrostBase, "configure") as base_configure:
gfcam_base.configure({})
stop_camera.assert_called_once()
stop_camera().wait.assert_called_once()
set_config.assert_not_called()
config = default_config()
base_configure.assert_called_once_with(config)
def test_gfcam_default_config_copies():
assert isinstance(default_config(), dict)
assert id(default_config()) != id(default_config())
def test_gfcam_configure_sets_exp_time_in_ms(gfcam_base):
with mock.patch.object(gfcam_base, "stop_camera") as stop_camera:
with mock.patch.object(gfcam_base.backend, "set_config") as set_config:
with mock.patch.object(GigaFrostBase, "configure") as base_configure:
gfcam_base.configure({"exp_time": 0.1})
stop_camera.assert_called_once()
stop_camera().wait.assert_called_once()
set_config.assert_not_called()
config = default_config()
config.update({"exposure": 100}) # in ms
base_configure.assert_called_once_with(config)
def test_gfcam_set_acquisition_mode_invalid(gfcam_base):
"""Test setting invalid acquisition mode"""
with pytest.raises(RuntimeError) as excinfo:
gfcam_base.set_acquisition_mode("invalid_mode")
excinfo.match("Unsupported acquisition mode: invalid_mode")
@pytest.mark.parametrize(
"mode_soft, mode_external, mode_always, expected_result",
[
(0, 0, 0, None), # No enable mode set
(1, 0, 0, "soft"), # Only soft mode enabled
(0, 1, 0, "external"), # Only external mode enabled
(1, 1, 0, "soft+ext"), # Both soft and external enabled
(0, 0, 1, "always"), # Always mode enabled
(1, 0, 1, "always"), # Always overrides soft
(0, 1, 1, "always"), # Always overrides external
(1, 1, 1, "always"), # Always overrides both soft and external
],
)
def test_gfcam_enable_mode_property(
gfcam_base, mode_soft, mode_external, mode_always, expected_result
):
"""Test that the enable_mode property returns the correct mode based on signal values"""
# Configure the mock return values for the mode signals
gfcam_base.mode_endbl_soft.get.return_value = mode_soft
gfcam_base.mode_enbl_ext.get.return_value = mode_external
gfcam_base.mode_enbl_auto.get.return_value = mode_always
# Check that the property returns the expected result
assert gfcam_base.enable_mode == expected_result
@pytest.mark.parametrize(
"mode,expected_settings",
[
("soft", {"mode_enbl_ext": 0, "mode_endbl_soft": 1, "mode_enbl_auto": 0}),
("external", {"mode_enbl_ext": 1, "mode_endbl_soft": 0, "mode_enbl_auto": 0}),
("soft+ext", {"mode_enbl_ext": 1, "mode_endbl_soft": 1, "mode_enbl_auto": 0}),
("always", {"mode_enbl_ext": 0, "mode_endbl_soft": 0, "mode_enbl_auto": 1}),
],
)
def test_gfcam_enable_mode_setter(gfcam_base, mode, expected_settings):
"""Test setting the enable mode of the GigaFRoST camera"""
# Mock the const.gf_valid_enable_modes to avoid importing the constants
with mock.patch(
"tomcat_bec.devices.gigafrost.gigafrostcamera.const.gf_valid_enable_modes",
["soft", "external", "soft+ext", "always"],
):
# Set the enable mode
gfcam_base.enable_mode = mode
# Verify the correct signals were set
gfcam_base.mode_enbl_ext.set.assert_called_once_with(expected_settings["mode_enbl_ext"])
gfcam_base.mode_endbl_soft.set.assert_called_once_with(expected_settings["mode_endbl_soft"])
gfcam_base.mode_enbl_auto.set.assert_called_once_with(expected_settings["mode_enbl_auto"])
# Verify wait was called on each set operation
gfcam_base.mode_enbl_ext.set().wait.assert_called_once()
gfcam_base.mode_endbl_soft.set().wait.assert_called_once()
gfcam_base.mode_enbl_auto.set().wait.assert_called_once()
# Verify parameters were committed
gfcam_base.set_param.set.assert_called_once_with(1)
gfcam_base.set_param.set().wait.assert_called_once()
def test_gfcam_enable_mode_setter_invalid(gfcam_base):
"""Test setting an invalid enable mode raises an error"""
# Mock the const.gf_valid_enable_modes to avoid importing the constants
with mock.patch(
"tomcat_bec.devices.gigafrost.gigafrostcamera.const.gf_valid_enable_modes",
["soft", "external", "soft+ext", "always"],
):
with pytest.raises(ValueError) as excinfo:
gfcam_base.enable_mode = "invalid_mode"
assert "Invalid enable mode invalid_mode!" in str(excinfo.value)
assert "Valid modes are:" in str(excinfo.value)
# Verify no signals were set
gfcam_base.mode_enbl_ext.set.assert_not_called()
gfcam_base.mode_endbl_soft.set.assert_not_called()
gfcam_base.mode_enbl_auto.set.assert_not_called()
gfcam_base.set_param.set.assert_not_called()
@pytest.mark.parametrize(
"mode_auto, mode_soft, mode_timer, mode_external, expected_result",
[
(0, 0, 0, 0, None), # No trigger mode set
(1, 0, 0, 0, "auto"), # Only auto mode enabled
(0, 1, 0, 0, "soft"), # Only soft mode enabled
(0, 0, 1, 0, "timer"), # Only timer mode enabled
(0, 0, 0, 1, "external"), # Only external mode enabled
(1, 1, 0, 0, "auto"), # Auto takes precedence over soft
(1, 0, 1, 0, "auto"), # Auto takes precedence over timer
(1, 0, 0, 1, "auto"), # Auto takes precedence over external
(0, 1, 1, 0, "soft"), # Soft takes precedence over timer
(0, 1, 0, 1, "soft"), # Soft takes precedence over external
(0, 0, 1, 1, "timer"), # Timer takes precedence over external
(1, 1, 1, 1, "auto"), # Auto takes precedence over all
],
)
def test_gfcam_trigger_mode_property(
gfcam_base, mode_auto, mode_soft, mode_timer, mode_external, expected_result
):
"""Test that the trigger_mode property returns the correct mode based on signal values"""
# Configure the mock return values for the mode signals
gfcam_base.mode_trig_auto.get.return_value = mode_auto
gfcam_base.mode_trig_soft.get.return_value = mode_soft
gfcam_base.mode_trig_timer.get.return_value = mode_timer
gfcam_base.mode_trig_ext.get.return_value = mode_external
# Check that the property returns the expected result
assert gfcam_base.trigger_mode == expected_result
@pytest.mark.parametrize(
"mode,expected_settings",
[
(
"auto",
{"mode_trig_auto": 1, "mode_trig_soft": 0, "mode_trig_timer": 0, "mode_trig_ext": 0},
),
(
"soft",
{"mode_trig_auto": 0, "mode_trig_soft": 1, "mode_trig_timer": 0, "mode_trig_ext": 0},
),
(
"timer",
{"mode_trig_auto": 0, "mode_trig_soft": 0, "mode_trig_timer": 1, "mode_trig_ext": 0},
),
(
"external",
{"mode_trig_auto": 0, "mode_trig_soft": 0, "mode_trig_timer": 0, "mode_trig_ext": 1},
),
],
)
def test_gfcam_trigger_mode_setter(gfcam_base, mode, expected_settings):
"""Test setting the trigger mode of the GigaFRoST camera"""
# Set the trigger mode
gfcam_base.trigger_mode = mode
# Verify the correct signals were set
gfcam_base.mode_trig_auto.set.assert_called_with(expected_settings["mode_trig_auto"])
gfcam_base.mode_trig_soft.set.assert_called_with(expected_settings["mode_trig_soft"])
gfcam_base.mode_trig_timer.set.assert_called_with(expected_settings["mode_trig_timer"])
gfcam_base.mode_trig_ext.set.assert_called_with(expected_settings["mode_trig_ext"])
# Verify wait was called on each set operation
gfcam_base.mode_trig_auto.set().wait.assert_called_once()
gfcam_base.mode_trig_soft.set().wait.assert_called_once()
gfcam_base.mode_trig_timer.set().wait.assert_called_once()
gfcam_base.mode_trig_ext.set().wait.assert_called_once()
# Verify parameters were committed
gfcam_base.set_param.set.assert_called_once_with(1)
gfcam_base.set_param.set().wait.assert_called_once()
def test_gfcam_trigger_mode_setter_invalid(gfcam_base):
"""Test setting an invalid trigger mode raises an error"""
with pytest.raises(ValueError) as excinfo:
gfcam_base.trigger_mode = "invalid_mode"
assert "Invalid trigger mode!" in str(excinfo.value)
assert "Valid modes are: ['auto', 'external', 'timer', 'soft']" in str(excinfo.value)
# Verify no signals were set
gfcam_base.mode_trig_auto.set.assert_not_called()
gfcam_base.mode_trig_soft.set.assert_not_called()
gfcam_base.mode_trig_timer.set.assert_not_called()
gfcam_base.mode_trig_ext.set.assert_not_called()
gfcam_base.set_param.set.assert_not_called()
@pytest.mark.parametrize(
"start_bit, end_bit, expected_result",
[
(0, 0, "off"), # Both bits off
(1, 0, "start"), # Only start bit on
(0, 1, "end"), # Only end bit on
(1, 1, "start+end"), # Both bits on
],
)
def test_gfcam_fix_nframes_mode_property(gfcam_base, start_bit, end_bit, expected_result):
"""Test that the fix_nframes_mode property returns the correct mode based on bit values"""
# Configure the mock return values for the bits
gfcam_base.cnt_startbit.get.return_value = start_bit
# Note: The original code has a bug here - it calls cnt_startbit.get() twice instead of cnt_endbit.get()
# For testing purposes, we'll mock both appropriately
gfcam_base.cnt_endbit.get.return_value = end_bit
# Check that the property returns the expected result
assert gfcam_base.fix_nframes_mode == expected_result
@pytest.mark.parametrize(
"mode, expected_settings",
[
("off", {"cnt_startbit": 0, "cnt_endbit": 0}),
("start", {"cnt_startbit": 1, "cnt_endbit": 0}),
("end", {"cnt_startbit": 0, "cnt_endbit": 1}),
("start+end", {"cnt_startbit": 1, "cnt_endbit": 1}),
],
)
def test_gfcam_fix_nframes_mode_setter(gfcam_base, mode, expected_settings):
"""Test setting the fixed number of frames mode of the GigaFRoST camera"""
# Mock the const.gf_valid_fix_nframe_modes to avoid importing the constants
with mock.patch(
"tomcat_bec.devices.gigafrost.gigafrostcamera.const.gf_valid_fix_nframe_modes",
["off", "start", "end", "start+end"],
):
# Set the mode
gfcam_base.fix_nframes_mode = mode
# Verify the class attribute was set
assert gfcam_base._fix_nframes_mode == mode
# Verify the correct signals were set
gfcam_base.cnt_startbit.set.assert_called_once_with(expected_settings["cnt_startbit"])
gfcam_base.cnt_endbit.set.assert_called_once_with(expected_settings["cnt_endbit"])
# Verify wait was called on each set operation
gfcam_base.cnt_startbit.set().wait.assert_called_once()
gfcam_base.cnt_endbit.set().wait.assert_called_once()
# Verify parameters were committed
gfcam_base.set_param.set.assert_called_once_with(1)
gfcam_base.set_param.set().wait.assert_called_once()
def test_gfcam_fix_nframes_mode_setter_invalid(gfcam_base):
"""Test setting an invalid fixed number of frames mode raises an error"""
# Mock the const.gf_valid_fix_nframe_modes to avoid importing the constants
with mock.patch(
"tomcat_bec.devices.gigafrost.gigafrostcamera.const.gf_valid_fix_nframe_modes",
["off", "start", "end", "start+end"],
):
with pytest.raises(ValueError) as excinfo:
gfcam_base.fix_nframes_mode = "invalid_mode"
assert "Invalid fixed frame number mode!" in str(excinfo.value)
assert "Valid modes are:" in str(excinfo.value)
# Verify no signals were set
gfcam_base.cnt_startbit.set.assert_not_called()
gfcam_base.cnt_endbit.set.assert_not_called()
gfcam_base.set_param.set.assert_not_called()