Skip to content

Commit 37ad84d

Browse files
committed
Async recording stop and queued frame dispatch
Move recording shutdown off the UI thread and finalize it via a Qt signal so stop actions no longer block the interface. RecordingManager now uses a lock-protected recorder map plus a bounded background frame-dispatch queue/thread to decouple frame intake from disk writes, drop frames under sustained backpressure, and cleanly stop/tear down recorders. The recording-with-overlays UI path is also disabled in this change.
1 parent e4f22df commit 37ad84d

2 files changed

Lines changed: 191 additions & 43 deletions

File tree

dlclivegui/gui/main_window.py

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
import json
77
import logging
88
import os
9+
import threading
910
import time
1011
from pathlib import Path
1112

1213
import cv2
1314
import numpy as np
14-
from PySide6.QtCore import QRect, QSettings, Qt, QTimer, QUrl
15+
from PySide6.QtCore import QRect, QSettings, Qt, QTimer, QUrl, Signal
1516
from PySide6.QtGui import (
1617
QAction,
1718
QActionGroup,
@@ -87,6 +88,8 @@
8788
class DLCLiveMainWindow(QMainWindow):
8889
"""Main application window."""
8990

91+
_recording_stopped_async = Signal()
92+
9093
def __init__(self, config: ApplicationSettings | None = None):
9194
super().__init__()
9295
self.setWindowTitle("DeepLabCut Live GUI")
@@ -178,6 +181,9 @@ def __init__(self, config: ApplicationSettings | None = None):
178181
self._dlc_tile_scale: tuple[float, float] = (1.0, 1.0) # (scale_x, scale_y)
179182
# Display flag (decoupled from frame capture for performance)
180183
self._display_dirty: bool = False
184+
# Recording state
185+
self._recording_stopping = False
186+
self._recording_stopped_async.connect(self._on_recording_stopped_async)
181187

182188
self._load_icons()
183189
self._preview_pixmap = QPixmap(LOGO_ALPHA)
@@ -641,11 +647,11 @@ def _build_recording_group(self) -> QGroupBox:
641647
form.addRow(grid)
642648

643649
# Recording options
644-
self.record_with_overlays_checkbox = QCheckBox("Record video with overlays")
645-
self.record_with_overlays_checkbox.setToolTip(
646-
"Enable to include pose overlays in recorded video (keypoints & bounding boxes)"
647-
)
648-
self.record_with_overlays_checkbox.setChecked(False)
650+
# self.record_with_overlays_checkbox = QCheckBox("Record video with overlays")
651+
# self.record_with_overlays_checkbox.setToolTip(
652+
# "Enable to include pose overlays in recorded video (keypoints & bounding boxes)"
653+
# )
654+
# self.record_with_overlays_checkbox.setChecked(False)
649655

650656
self.fast_encoding_checkbox = QCheckBox("Use faster encoding parameters")
651657
self.fast_encoding_checkbox.setToolTip(
@@ -658,7 +664,7 @@ def _build_recording_group(self) -> QGroupBox:
658664
recording_options = QWidget()
659665
recording_options_layout = QHBoxLayout(recording_options)
660666
recording_options_layout.setContentsMargins(0, 0, 0, 0)
661-
recording_options_layout.addWidget(self.record_with_overlays_checkbox)
667+
# recording_options_layout.addWidget(self.record_with_overlays_checkbox)
662668
recording_options_layout.addWidget(self.fast_encoding_checkbox)
663669
recording_options_layout.addStretch(1)
664670

@@ -1454,8 +1460,8 @@ def _on_recording_frame_ready(
14541460
if not self._rec_manager.is_active:
14551461
return
14561462

1457-
if self.record_with_overlays_checkbox.isChecked():
1458-
frame = self._render_overlays_for_recording(camera_id, frame)
1463+
# if self.record_with_overlays_checkbox.isChecked():
1464+
# frame = self._render_overlays_for_recording(camera_id, frame)
14591465

14601466
self._rec_manager.write_frame(camera_id, frame, timestamp, timestamp_metadata=timestamp_metadata)
14611467

@@ -1611,9 +1617,35 @@ def _stop_multi_camera_recording(self) -> None:
16111617
if not self._rec_manager.is_active:
16121618
return
16131619

1614-
self.multi_camera_controller.set_recording_frame_do_emit(False)
1620+
if getattr(self, "_recording_stopping", False):
1621+
return
1622+
1623+
self._recording_stopping = True
16151624

1616-
self._rec_manager.stop_all()
1625+
self.start_record_button.setEnabled(False)
1626+
self.stop_record_button.setEnabled(False)
1627+
self.statusBar().showMessage("Stopping multi-camera recording…", 3000)
1628+
1629+
# Stop frame emission immediately so no new frames enter recording pipeline.
1630+
try:
1631+
self.multi_camera_controller.set_recording_frame_do_emit(False)
1632+
except Exception:
1633+
logger.exception("Failed to disable recording frame emission")
1634+
1635+
def worker():
1636+
try:
1637+
self._rec_manager.stop_all()
1638+
finally:
1639+
self._recording_stopped_async.emit()
1640+
1641+
threading.Thread(
1642+
target=worker,
1643+
name="StopRecordingWorker",
1644+
daemon=True,
1645+
).start()
1646+
1647+
def _on_recording_stopped_async(self) -> None:
1648+
self._recording_stopping = False
16171649
self.start_record_button.setEnabled(True)
16181650
self.stop_record_button.setEnabled(False)
16191651
self.statusBar().showMessage("Multi-camera recording stopped", 3000)

0 commit comments

Comments
 (0)