- Implemented CameraSettingsDialog to manage UVC and Qt camera controls. - Integrated UVC parameter sliders and auto controls for brightness, contrast, saturation, hue, sharpness, gamma, white balance, backlight compensation, and exposure. - Added functionality to change white balance and exposure settings via Qt controls. - Updated MainWindow to open CameraSettingsDialog and manage UVC controller lifecycle. - Enhanced AppMenuBar to include a Camera Settings option. - Created tests for UVC controller abstraction layer and parameter info. - Documented camera specifications and supported features in new markdown files.
118 lines
4.1 KiB
Python
118 lines
4.1 KiB
Python
"""Tests for the UVC controller abstraction layer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from app.camera.uvc.base import UvcParam, UvcParamInfo
|
|
from app.camera.uvc.stub import NullUvcController
|
|
|
|
|
|
class TestNullUvcController:
|
|
"""NullUvcController must implement the full interface, all as no-ops."""
|
|
|
|
def setup_method(self) -> None:
|
|
self.ctrl = NullUvcController()
|
|
|
|
def test_is_not_open(self) -> None:
|
|
assert not self.ctrl.is_open()
|
|
|
|
def test_open_returns_false(self) -> None:
|
|
assert self.ctrl.open("any device") is False
|
|
|
|
def test_close_does_not_raise(self) -> None:
|
|
self.ctrl.close() # must not raise
|
|
|
|
def test_get_param_info_returns_unsupported(self) -> None:
|
|
info = self.ctrl.get_param_info(UvcParam.BRIGHTNESS)
|
|
assert isinstance(info, UvcParamInfo)
|
|
assert info.supported is False
|
|
assert info.param is UvcParam.BRIGHTNESS
|
|
|
|
def test_get_all_params_covers_all_uvc_params(self) -> None:
|
|
infos = self.ctrl.get_all_params()
|
|
returned_params = {i.param for i in infos}
|
|
all_params = set(UvcParam)
|
|
assert returned_params == all_params
|
|
|
|
def test_get_all_params_all_unsupported(self) -> None:
|
|
for info in self.ctrl.get_all_params():
|
|
assert info.supported is False
|
|
|
|
def test_set_value_returns_false(self) -> None:
|
|
assert self.ctrl.set_value(UvcParam.CONTRAST, 50) is False
|
|
|
|
def test_set_auto_returns_false(self) -> None:
|
|
assert self.ctrl.set_auto(UvcParam.WHITE_BALANCE, True) is False
|
|
|
|
def test_set_value_does_not_raise(self) -> None:
|
|
for param in UvcParam:
|
|
self.ctrl.set_value(param, 0) # must not raise
|
|
|
|
def test_set_auto_does_not_raise(self) -> None:
|
|
for param in UvcParam:
|
|
self.ctrl.set_auto(param, True) # must not raise
|
|
self.ctrl.set_auto(param, False)
|
|
|
|
|
|
class TestUvcParamInfo:
|
|
"""UvcParamInfo dataclass sanity checks."""
|
|
|
|
def test_defaults(self) -> None:
|
|
info = UvcParamInfo(param=UvcParam.BRIGHTNESS, supported=True)
|
|
assert info.minimum == 0
|
|
assert info.maximum == 100
|
|
assert info.default == 50
|
|
assert info.current == 50
|
|
assert info.step == 1
|
|
assert info.auto_supported is False
|
|
assert info.auto_enabled is False
|
|
|
|
def test_unsupported_flag(self) -> None:
|
|
info = UvcParamInfo(param=UvcParam.GAMMA, supported=False)
|
|
assert info.supported is False
|
|
|
|
def test_custom_range(self) -> None:
|
|
info = UvcParamInfo(
|
|
param=UvcParam.HUE,
|
|
supported=True,
|
|
minimum=-180,
|
|
maximum=180,
|
|
default=0,
|
|
current=-45,
|
|
)
|
|
assert info.minimum == -180
|
|
assert info.maximum == 180
|
|
assert info.current == -45
|
|
|
|
|
|
class TestMakeUvcControllerFallback:
|
|
"""make_uvc_controller falls back to NullUvcController when no lib available."""
|
|
|
|
def test_returns_controller_instance(self) -> None:
|
|
from app.camera.uvc import make_uvc_controller
|
|
from app.camera.uvc.base import UvcControllerBase
|
|
ctrl = make_uvc_controller("Test Camera")
|
|
assert isinstance(ctrl, UvcControllerBase)
|
|
|
|
def test_stub_used_when_native_lib_absent(self, monkeypatch) -> None:
|
|
"""If the native import fails, should return NullUvcController."""
|
|
import builtins
|
|
real_import = builtins.__import__
|
|
|
|
def patched_import(name, *args, **kwargs):
|
|
if name in ("duvc_ctl", "uvc"):
|
|
raise ImportError(f"Mocked missing: {name}")
|
|
return real_import(name, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(builtins, "__import__", patched_import)
|
|
# Re-import to exercise factory with patched import
|
|
import importlib
|
|
|
|
import app.camera.uvc as uvc_pkg
|
|
importlib.reload(uvc_pkg)
|
|
ctrl = uvc_pkg.make_uvc_controller("Test Camera")
|
|
# Should be functional (not raise), may be Null or platform controller
|
|
from app.camera.uvc.base import UvcControllerBase
|
|
assert isinstance(ctrl, UvcControllerBase)
|
|
# Reload back to normal
|
|
importlib.reload(uvc_pkg)
|