feat: Add Camera Settings dialog for UVC controls and Qt parameters
- 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.
This commit is contained in:
117
tests/test_uvc.py
Normal file
117
tests/test_uvc.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user