From 932206d4cb3cc21575ef867335a30dc154e5347d Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Fri, 28 Aug 2026 15:05:23 +0200 Subject: [PATCH 1/3] Remove separate multi-cam display signal Simplify multi-camera frame delivery by removing the throttled `display_ready` path and relying on `frame_ready` for both processing and UI updates. The main window now marks display state dirty when `frame_ready` arrives, and the controller no longer maintains GUI FPS-based display emission logic. --- dlclivegui/gui/main_window.py | 11 +--------- .../services/multi_camera_controller.py | 22 ------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index d049863e..abb87702 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -855,7 +855,6 @@ def _connect_signals(self) -> None: # Multi-camera controller signals (used for both single and multi-camera modes) self.multi_camera_controller.frame_ready.connect(self._on_multi_frame_processing_ready) - self.multi_camera_controller.display_ready.connect(self._on_multi_frame_display_ready) self.multi_camera_controller.all_started.connect(self._on_multi_camera_started) self.multi_camera_controller.all_stopped.connect(self._on_multi_camera_stopped) self.multi_camera_controller.camera_error.connect(self._on_multi_camera_error) @@ -1702,6 +1701,7 @@ def _on_multi_frame_processing_ready(self, frame_data: MultiFrameData) -> None: """ self._multi_camera_frames = frame_data.frames self._multi_camera_display_ids = frame_data.display_ids or {} + self._display_dirty = True self._try_start_pending_recording() src_id = frame_data.source_camera_id if src_id: @@ -1755,15 +1755,6 @@ def _on_multi_frame_processing_ready(self, frame_data: MultiFrameData) -> None: self._dlc_timing.note_frame() self._dlc_timing.maybe_log() - def _on_multi_frame_display_ready(self, frame_data: MultiFrameData) -> None: - """Throttled UI/display path. - - Called at GUI_MAX_DISPLAY_FPS, not at camera capture FPS for performance reasons. - """ - self._multi_camera_frames = frame_data.frames - self._multi_camera_display_ids = frame_data.display_ids or {} - self._display_dirty = True - def _on_multi_camera_started(self) -> None: """Handle all cameras started event.""" self.preview_button.setEnabled(False) diff --git a/dlclivegui/services/multi_camera_controller.py b/dlclivegui/services/multi_camera_controller.py index 134efe35..65740199 100644 --- a/dlclivegui/services/multi_camera_controller.py +++ b/dlclivegui/services/multi_camera_controller.py @@ -4,7 +4,6 @@ import copy import logging -import time from dataclasses import dataclass from functools import partial from threading import Lock @@ -183,22 +182,6 @@ def set_recording_frame_is_enabled(self, enabled: bool) -> None: for worker in list(self._workers.values()): worker.set_recording_enabled(enabled) - def _should_emit_display_ready(self) -> bool: - """ - Return True if enough time has passed since the last display_ready emission, based on GUI_MAX_DISPLAY_FPS. - """ - if self._gui_display_max_fps <= 0: - return True - - now = time.perf_counter() - min_interval = 1.0 / max(self._gui_display_max_fps, 1e-9) - - if now - self._gui_display_last_emit < min_interval: - return False - - self._gui_display_last_emit = now - return True - def start(self, camera_settings: list[CameraSettings]) -> None: """Start multiple cameras.""" if self._running or self._stopping: @@ -496,11 +479,6 @@ def _on_frame_captured( with timing.measure("Multi.emit.frame_ready"): self.frame_ready.emit(frame_data) - # GUI-only path: throttled display updates - if self._should_emit_display_ready(): - with timing.measure("Multi.emit.display_ready"): - self.display_ready.emit(frame_data) - timing.note_frame() timing.maybe_log() From af2b3a74237d7ba9f11e078865ba9bee4bd8c5a6 Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Fri, 28 Aug 2026 15:24:06 +0200 Subject: [PATCH 2/3] Use Triplex font in tiled frame labels --- dlclivegui/utils/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlclivegui/utils/display.py b/dlclivegui/utils/display.py index 5cd29744..7dcf3208 100644 --- a/dlclivegui/utils/display.py +++ b/dlclivegui/utils/display.py @@ -113,7 +113,7 @@ def create_tiled_frame( resized, label, (10, 30), - cv2.FONT_HERSHEY_SIMPLEX, + cv2.FONT_HERSHEY_TRIPLEX, 0.7, (0, 255, 0), 2, From be551f28d4abd8e752826466e77196ef262ad5fe Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Mon, 2 Nov 2026 09:41:24 +0100 Subject: [PATCH 3/3] Remove leftover Signal for frame display --- dlclivegui/services/multi_camera_controller.py | 8 +------- tests/gui/test_main.py | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/dlclivegui/services/multi_camera_controller.py b/dlclivegui/services/multi_camera_controller.py index 65740199..60fd5587 100644 --- a/dlclivegui/services/multi_camera_controller.py +++ b/dlclivegui/services/multi_camera_controller.py @@ -17,7 +17,6 @@ # from dlclivegui.config import CameraSettings from dlclivegui.config import ( - GUI_MAX_DISPLAY_FPS, MULTI_CAMERA_WORKER_DO_LOG_TIMING, CameraSettings, CameraTriggerSettings, @@ -108,11 +107,10 @@ class MultiCameraController(QObject): """Controller for managing multiple cameras simultaneously.""" # Signals - frame_ready = Signal(object) # MultiFrameData (full cam FPS; inference only) + frame_ready = Signal(object) # MultiFrameData (full cam FPS; inference and display) # recording_frame_ready = Signal( # str, object, float, object # ) # camera_id, frame, timestamp, timestamp_metadata (full cam FPS; for recording) - display_ready = Signal(object) # MultiFrameData for GUI display (throttled to GUI_MAX_DISPLAY_FPS) camera_started = Signal(str, object) # camera_id, settings camera_stopped = Signal(str) # camera_id camera_error = Signal(str, str) # camera_id, error_message @@ -142,9 +140,6 @@ def __init__(self): self._failed_cameras: dict[str, str] = {} # camera_id -> error message self._expected_cameras: int = 0 # Number of cameras we're trying to start - # GUI display max FPS (for throttling display updates when many cameras are active) - self._gui_display_max_fps: float = GUI_MAX_DISPLAY_FPS - self._gui_display_last_emit: float = 0.0 # Performance logs self._timing_per_cam: dict[str, WorkerTimingStats] = {} @@ -330,7 +325,6 @@ def _maybe_finalize_stop(self) -> None: self._running = False self._timing_per_cam.clear() - self._gui_display_last_emit = 0.0 self._workers.clear() self._settings.clear() diff --git a/tests/gui/test_main.py b/tests/gui/test_main.py index db373822..37b65128 100644 --- a/tests/gui/test_main.py +++ b/tests/gui/test_main.py @@ -58,7 +58,6 @@ def fake_start(_camera_settings): def finish_start(): ctrl.all_started.emit() ctrl.frame_ready.emit(frame_data) - ctrl.display_ready.emit(frame_data) QTimer.singleShot(0, finish_start)