Refactor detection handling by separating DetectionWorker and integrating it into MainWindow
This commit is contained in:
@@ -50,38 +50,20 @@ def rotate_frame(frame: np.ndarray, degrees: int) -> np.ndarray:
|
||||
|
||||
class CameraWorker(QThread):
|
||||
frame_ready = Signal(object)
|
||||
detection_ready = Signal(object)
|
||||
camera_error = Signal(str)
|
||||
|
||||
def __init__(self, config: dict[str, Any], app_config: Any) -> None:
|
||||
super().__init__()
|
||||
self.config = config
|
||||
self.app_config = app_config
|
||||
self.pipeline = DetectionPipeline(config, app_config)
|
||||
self._running = threading.Event()
|
||||
self._running.set()
|
||||
self._detecting = False
|
||||
self._accepted = False
|
||||
self._frame_count = 0
|
||||
self._capture: cv2.VideoCapture | None = None
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def stop(self) -> None:
|
||||
self._running.clear()
|
||||
|
||||
@Slot()
|
||||
def start_detection(self) -> None:
|
||||
with self._lock:
|
||||
self._detecting = True
|
||||
self._accepted = False
|
||||
self._frame_count = 0
|
||||
|
||||
@Slot()
|
||||
def accept_detection(self) -> None:
|
||||
with self._lock:
|
||||
self._detecting = False
|
||||
self._accepted = True
|
||||
|
||||
@Slot(dict)
|
||||
def update_camera_config(self, camera_config: dict[str, Any]) -> None:
|
||||
with self._lock:
|
||||
@@ -115,7 +97,6 @@ class CameraWorker(QThread):
|
||||
rotation_degrees = int(self.config["camera"].get("rotation_degrees", 0))
|
||||
frame = rotate_frame(frame, rotation_degrees)
|
||||
self.frame_ready.emit(frame)
|
||||
self._maybe_detect(frame)
|
||||
finally:
|
||||
capture.release()
|
||||
self._capture = None
|
||||
@@ -131,15 +112,53 @@ class CameraWorker(QThread):
|
||||
continue
|
||||
capture.set(CV_CAP_PROPS[name], float(value))
|
||||
|
||||
def _maybe_detect(self, frame: np.ndarray) -> None:
|
||||
|
||||
class DetectionWorker(QThread):
|
||||
detection_ready = Signal(object)
|
||||
|
||||
def __init__(self, config: dict[str, Any], app_config: Any) -> None:
|
||||
super().__init__()
|
||||
self.config = config
|
||||
self.app_config = app_config
|
||||
self.pipeline: DetectionPipeline | None = None
|
||||
self._running = threading.Event()
|
||||
self._running.set()
|
||||
self._pending = threading.Event()
|
||||
self._lock = threading.Lock()
|
||||
self._pending_frame: np.ndarray | None = None
|
||||
self._busy = False
|
||||
|
||||
def stop(self) -> None:
|
||||
self._running.clear()
|
||||
self._pending.set()
|
||||
|
||||
def request_detection(self, frame: np.ndarray) -> bool:
|
||||
with self._lock:
|
||||
detecting = self._detecting and not self._accepted
|
||||
frame_stride = max(1, int(self.config["detection"].get("frame_stride", 5)))
|
||||
self._frame_count += 1
|
||||
should_detect = detecting and self._frame_count % frame_stride == 0
|
||||
if self._busy or self._pending_frame is not None:
|
||||
return False
|
||||
self._pending_frame = frame.copy()
|
||||
self._pending.set()
|
||||
return True
|
||||
|
||||
if not should_detect:
|
||||
return
|
||||
def run(self) -> None:
|
||||
while self._running.is_set():
|
||||
self._pending.wait(0.2)
|
||||
if not self._running.is_set():
|
||||
break
|
||||
|
||||
result: DetectionResult = self.pipeline.process(frame)
|
||||
self.detection_ready.emit(result)
|
||||
with self._lock:
|
||||
frame = self._pending_frame
|
||||
self._pending_frame = None
|
||||
self._pending.clear()
|
||||
if frame is None:
|
||||
continue
|
||||
self._busy = True
|
||||
|
||||
try:
|
||||
if self.pipeline is None:
|
||||
self.pipeline = DetectionPipeline(self.config, self.app_config)
|
||||
result: DetectionResult = self.pipeline.process(frame)
|
||||
self.detection_ready.emit(result)
|
||||
finally:
|
||||
with self._lock:
|
||||
self._busy = False
|
||||
|
||||
@@ -21,7 +21,7 @@ from PySide6.QtWidgets import (
|
||||
QStyle,
|
||||
)
|
||||
|
||||
from app.camera import CameraWorker
|
||||
from app.camera import CameraWorker, DetectionWorker
|
||||
from app.config import AppConfig
|
||||
from app.detection import DetectionResult
|
||||
from app.media import MediaStore, VideoRecorder
|
||||
@@ -36,6 +36,8 @@ class MainWindow(QMainWindow):
|
||||
self.last_frame: np.ndarray | None = None
|
||||
self.overlay_result: DetectionResult | None = None
|
||||
self.last_detection: DetectionResult | None = None
|
||||
self.detecting = False
|
||||
self.detection_frame_count = 0
|
||||
self.media_store = MediaStore(self.config, self.app_config)
|
||||
self.video_recorder = VideoRecorder(self.config, self.app_config)
|
||||
|
||||
@@ -45,10 +47,13 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.worker = CameraWorker(self.config, self.app_config)
|
||||
self.worker.frame_ready.connect(self.on_frame_ready)
|
||||
self.worker.detection_ready.connect(self.on_detection_ready)
|
||||
self.worker.camera_error.connect(self.on_camera_error)
|
||||
self.worker.start()
|
||||
|
||||
self.detection_worker = DetectionWorker(self.config, self.app_config)
|
||||
self.detection_worker.detection_ready.connect(self.on_detection_ready)
|
||||
self.detection_worker.start()
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
self.stage = QWidget()
|
||||
self.setCentralWidget(self.stage)
|
||||
@@ -150,7 +155,9 @@ class MainWindow(QMainWindow):
|
||||
if self.video_recorder.is_recording:
|
||||
self.video_recorder.stop(self.current_metadata("video"))
|
||||
self.worker.stop()
|
||||
self.detection_worker.stop()
|
||||
self.worker.wait(2000)
|
||||
self.detection_worker.wait(2000)
|
||||
super().closeEvent(event)
|
||||
|
||||
@Slot(object)
|
||||
@@ -158,10 +165,13 @@ class MainWindow(QMainWindow):
|
||||
self.last_frame = frame.copy()
|
||||
if self.video_recorder.is_recording:
|
||||
self.video_recorder.write(frame)
|
||||
self._maybe_request_detection(frame)
|
||||
self._show_frame(frame)
|
||||
|
||||
@Slot(object)
|
||||
def on_detection_ready(self, result: DetectionResult) -> None:
|
||||
if not self.detecting:
|
||||
return
|
||||
self.last_detection = result
|
||||
self.overlay_result = result if result.xyxy else None
|
||||
self._update_result_text(result)
|
||||
@@ -172,11 +182,12 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def start_detection(self) -> None:
|
||||
self.overlay_result = None
|
||||
self.detecting = True
|
||||
self.detection_frame_count = 0
|
||||
self.result_text.setPlainText("Wykrywanie...")
|
||||
self.worker.start_detection()
|
||||
|
||||
def accept_detection(self) -> None:
|
||||
self.worker.accept_detection()
|
||||
self.detecting = False
|
||||
self.overlay_result = None
|
||||
if self.last_detection:
|
||||
self._update_result_text(self.last_detection, accepted=True)
|
||||
@@ -218,6 +229,17 @@ class MainWindow(QMainWindow):
|
||||
self.app_config.save(self.config)
|
||||
self.worker.update_camera_config(camera_config)
|
||||
|
||||
def _maybe_request_detection(self, frame: np.ndarray) -> None:
|
||||
if not self.detecting:
|
||||
return
|
||||
|
||||
frame_stride = max(1, int(self.config["detection"].get("frame_stride", 5)))
|
||||
self.detection_frame_count += 1
|
||||
if self.detection_frame_count % frame_stride != 0:
|
||||
return
|
||||
|
||||
self.detection_worker.request_detection(frame)
|
||||
|
||||
def current_metadata(self, media_type: str) -> dict[str, Any]:
|
||||
return {
|
||||
"media_type": media_type,
|
||||
|
||||
Reference in New Issue
Block a user