Add test to stop camera Thing APIs diverging futher without review

This commit is contained in:
Julian Stirling 2025-08-01 23:06:33 +01:00
parent afc621b847
commit d994119b36

133
tests/test_cameras.py Normal file
View file

@ -0,0 +1,133 @@
"""Generic tests for cameras, specific camera hardware reqipres hardware specific tests."""
import pytest
from openflexure_microscope_server.things.camera import BaseCamera
from openflexure_microscope_server.things.camera.simulation import SimulatedCamera
from openflexure_microscope_server.things.camera.opencv import OpenCVCamera
@pytest.fixture
def mock_picam_thing(mocker):
"""Import PiCamera without hardware well enough to get a ThingDescription."""
dummy_cam = mocker.Mock()
mock_picamera2 = mocker.MagicMock()
mock_picamera2.return_value.__enter__.return_value = dummy_cam
mock_picamera2.return_value.__exit__.return_value = None
mocker.patch.dict(
"sys.modules",
{
"picamera2": mock_picamera2,
"picamera2.encoders": mocker.Mock(),
"picamera2.outputs": mocker.Mock(),
},
)
mocker.patch(
"openflexure_microscope_server.things.camera.picamera.recalibrate_utils.load_default_tuning",
return_value={"mock": "tuning"},
)
from openflexure_microscope_server.things.camera.picamera import StreamingPiCamera2
return StreamingPiCamera2()
def _get_clean_camera_description(camera_thing):
"""Return actions and properties for a camera Thing, separating those exposed to the UI.
Return cleaned sets of actions and properties for camera_thing, excluding
calibration actions exposed to the UI as primary or secondary calibration actions,
and excluding the properties exposed in manual camera settings.
:returns: Dict with keys:
* "actions" - Actions not explicitly exposed to the UI by this camera.
* "properties" - Properties not explicitly exposed to the UI as manual camera
settings.
* "calibration_actions" - Actions explicitly exposed to the UI by this camera.
* "manual_properties" - Properties explicitly exposed to the UI as manual
camera settings.
"""
camera_thing.path = "/mock/"
td = camera_thing.thing_description()
actions = set(td.actions.keys())
properties = set(td.properties.keys())
# Calibration actions are camera specific
primary = [btn.action for btn in camera_thing.primary_calibration_actions]
secondary = [btn.action for btn in camera_thing.secondary_calibration_actions]
calibration_actions = set(primary + secondary)
actions -= calibration_actions
# Manual properties are also camera specific
manual_props = {ctrl.property_name for ctrl in camera_thing.manual_camera_settings}
properties -= manual_props
return {
"actions": actions,
"properties": properties,
"calibration_actions": calibration_actions,
"manual_properties": manual_props,
}
def test_thing_description_equivalence(mock_picam_thing):
"""Ensure extra actions and properties are not added to camera Things without explicit approval.
This test verifies that extra actions are not unintentionally introduced to
camera child classes. Any addition of actions must be accompanied by an update
to this test, prompting review and justification for why the action does not
belong to the general base class.
"""
base_td = BaseCamera().thing_description()
base_actions = set(base_td.actions.keys())
base_props = set(base_td.properties.keys())
sim_description = _get_clean_camera_description(SimulatedCamera())
opencv_description = _get_clean_camera_description(OpenCVCamera())
picamera_description = _get_clean_camera_description(mock_picam_thing)
# Note. These are the actions and properties that are not exposed to the UI via
# `primary_calibration_actions`, `secondary_calibration_actions`, or
# `manual_camera_settings`.
sim_actions = sim_description["actions"]
sim_props = sim_description["properties"]
opencv_actions = opencv_description["actions"]
opencv_props = opencv_description["properties"]
picamera_actions = picamera_description["actions"]
picamera_props = picamera_description["properties"]
# Camera actions and properties should generally be equivalent except for exposed
# manual settings and calibration actions.
assert opencv_actions == sim_actions == base_actions
assert opencv_props == sim_props == base_props
# For now PiCamera has a number of extra actions and properties. These should be
# reduced over time by creating a way to use the functionality in a way as clearly
# defined as PiCamera specific, or by replicating the functionality for other
# cameras.
picamera_extra_actions = {
"flat_lens_shading_chrominance",
"set_static_green_equalisation",
"stop_streaming",
"reset_ccm",
}
picamera_extra_props = {
"mjpeg_bitrate",
"colour_correction_matrix",
"lens_shading_tables",
"sensor_resolution",
"lens_shading_is_static",
"capture_metadata",
"camera_configuration",
"stream_resolution",
"tuning",
"sensor_modes",
"sensor_mode",
}
assert picamera_actions - base_actions == picamera_extra_actions
assert picamera_props - base_props == picamera_extra_props