-
Notifications
You must be signed in to change notification settings - Fork 28
Upgrade & fix GenTL multi-camera mode #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
f360b73
971f629
60b7607
4032402
b2e53dc
ab2598e
95c8647
f2d0cc7
02bcbb9
92dd624
5f56a5f
9b77c42
90241f6
760825f
2e54469
0af5615
ed1a9f5
0d5e1dc
ce587cb
ddb5fa9
af60667
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| from __future__ import annotations | ||
|
|
||
| import copy | ||
| import logging | ||
| import time | ||
| from dataclasses import dataclass | ||
|
|
@@ -14,6 +15,7 @@ | |
|
|
||
| from dlclivegui.cameras import CameraFactory | ||
| from dlclivegui.cameras.base import CameraBackend | ||
| from dlclivegui.cameras.factory import camera_identity_key | ||
|
|
||
| # from dlclivegui.config import CameraSettings | ||
| from dlclivegui.config import CameraSettings | ||
|
|
@@ -42,7 +44,7 @@ class SingleCameraWorker(QObject): | |
| def __init__(self, camera_id: str, settings: CameraSettings): | ||
| super().__init__() | ||
| self._camera_id = camera_id | ||
| self._settings = settings | ||
| self._settings = copy.deepcopy(settings) | ||
| self._stop_event = Event() | ||
| self._backend: CameraBackend | None = None | ||
| self._max_consecutive_errors = 5 | ||
|
|
@@ -53,7 +55,24 @@ def run(self) -> None: | |
| self._stop_event.clear() | ||
|
|
||
| try: | ||
| LOGGER.debug( | ||
| "[Worker %s] before create: backend=%s index=%s properties=%s", | ||
| self._camera_id, | ||
| self._settings.backend, | ||
| self._settings.index, | ||
| self._settings.properties, | ||
| ) | ||
|
|
||
| self._backend = CameraFactory.create(self._settings) | ||
|
|
||
| LOGGER.debug( | ||
| "[Worker %s] after create: backend=%s index=%s properties=%s", | ||
| self._camera_id, | ||
| self._backend.settings.backend, | ||
| self._backend.settings.index, | ||
| self._backend.settings.properties, | ||
| ) | ||
|
|
||
| self._backend.open() | ||
| except Exception as exc: | ||
| LOGGER.exception(f"Failed to initialize camera {self._camera_id}", exc_info=exc) | ||
|
|
@@ -102,11 +121,27 @@ def stop(self) -> None: | |
| self._stop_event.set() | ||
|
|
||
|
|
||
| def get_camera_id(settings: CameraSettings) -> str: | ||
| """Generate a unique camera ID from settings.""" | ||
| def get_display_id(settings: CameraSettings) -> str: | ||
| return f"{settings.backend}:{settings.index}" | ||
|
|
||
|
|
||
| def get_camera_id(settings: CameraSettings) -> str: | ||
| """Generate a unique camera ID from stable backend identity.""" | ||
| backend = (settings.backend or "").lower() | ||
| props = settings.properties if isinstance(settings.properties, dict) else {} | ||
| ns = props.get(backend, {}) if isinstance(props.get(backend), dict) else {} | ||
|
|
||
| device_id = ns.get("device_id") | ||
| if device_id: | ||
| return f"{backend}:{device_id}" | ||
|
|
||
| serial = ns.get("serial_number") or ns.get("device_serial_number") or ns.get("serial") | ||
| if serial: | ||
| return f"{backend}:serial:{serial}" | ||
|
|
||
| return f"{backend}:index:{int(settings.index)}" | ||
|
|
||
|
|
||
| class MultiCameraController(QObject): | ||
| """Controller for managing multiple cameras simultaneously.""" | ||
|
|
||
|
|
@@ -153,6 +188,32 @@ def start(self, camera_settings: list[CameraSettings]) -> None: | |
| LOGGER.warning("No active cameras to start") | ||
| return | ||
|
|
||
| # Check for dupes | ||
| seen = {} | ||
| for s in active_settings: | ||
| camera_id = get_camera_id(s) | ||
| try: | ||
| key = camera_identity_key(s) | ||
| except Exception: | ||
|
Comment on lines
+197
to
+203
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm is there a reason that
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! Changed in af60667 |
||
| LOGGER.exception( | ||
| "Failed to compute camera identity key for %s; falling back to camera_id", | ||
| camera_id, | ||
| ) | ||
| key = camera_id | ||
|
|
||
| if key in seen: | ||
| self.initialization_failed.emit( | ||
| [ | ||
| ( | ||
| camera_id, | ||
| f"Duplicate camera configuration. Conflicts with {seen[key]}", | ||
| ) | ||
| ] | ||
| ) | ||
| return | ||
|
|
||
| seen[key] = camera_id | ||
|
|
||
|
C-Achard marked this conversation as resolved.
|
||
| self._running = True | ||
| self._frames.clear() | ||
| self._timestamps.clear() | ||
|
|
@@ -165,13 +226,16 @@ def start(self, camera_settings: list[CameraSettings]) -> None: | |
|
|
||
| def _start_camera(self, settings: CameraSettings) -> None: | ||
| """Start a single camera.""" | ||
| cam_id = get_camera_id(settings) | ||
| settings_copy = copy.deepcopy(settings) | ||
| cam_id = get_display_id(settings_copy) | ||
| if cam_id in self._workers: | ||
|
C-Achard marked this conversation as resolved.
|
||
| LOGGER.warning(f"Camera {cam_id} already has a worker") | ||
| return | ||
|
deruyter92 marked this conversation as resolved.
|
||
|
|
||
| LOGGER.info(f"[MultiCameraController] Starting {cam_id} with settings: {settings_copy}") | ||
|
|
||
| # Normalize and store the dataclass once | ||
| self._settings[cam_id] = settings | ||
| self._settings[cam_id] = settings_copy | ||
| dc = self._settings[cam_id] | ||
| worker = SingleCameraWorker(cam_id, dc) | ||
| thread = QThread() | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.