From 651c150e23ac7ccf80ca983f2ac3c58c1b99f806 Mon Sep 17 00:00:00 2001 From: bartool Date: Sun, 10 May 2026 21:52:34 +0200 Subject: [PATCH] test controllera Co-authored-by: Copilot --- app/camera.py | 148 --------------------------------------------- app/main_window.py | 24 +++++++- 2 files changed, 21 insertions(+), 151 deletions(-) delete mode 100644 app/camera.py diff --git a/app/camera.py b/app/camera.py deleted file mode 100644 index 729aaf2..0000000 --- a/app/camera.py +++ /dev/null @@ -1,148 +0,0 @@ -from PySide6.QtCore import QThread, QTimer, QMutex, QWaitCondition, Signal, QObject, QElapsedTimer -import cv2 -import time -import logging -from dataclasses import dataclass -logger = logging.getLogger(__name__) - -@dataclass -class VideoMetrics: - frames_processed: int - frames_dropped: int - last_frame_time: float - last_cap_time: float - fps_average: float - fps_last_time: float - fps_frame_count: float - - def update_fps(self) -> None: - fps_now = time.perf_counter() - elypsed = fps_now - self.fps_last_time - if elypsed < 1.0: - return - - self.fps_frame_count = self.frames_processed - self.fps_frame_count - self.fps_average = self.fps_frame_count / elypsed - self.fps_frame_count = self.frames_processed - self.fps_last_time = fps_now - - -class VideoStreamWorker(QObject): - # Emitowany z wątku roboczego - będzie przenoszony do głównego wątku przez VideoStream - _internal_frame = Signal(object) - - def __init__(self, source): - super().__init__() - self.source = source - self.cap = None - self.fps = 30.0 - self.width = 0 - self.height = 0 - self.running = False - self.metrics = VideoMetrics( - frames_processed=0, - frames_dropped=0, - last_frame_time=0.0, - last_cap_time=0.0, - fps_average=0.0, - fps_last_time=time.perf_counter(), - fps_frame_count=0.0 - ) - - logger.debug(f"VideoStreamWorker initialized with source: {source}") - - def set_source(self, source): - logger.debug(f"Setting new video source: {source}") - self.source = source - if self.running: - self.stop() - self.run() - - def run(self): - """Główna pętla wątku roboczego.""" - if self.source is None: - logger.warning("No video source provided") - return - - self.cap = cv2.VideoCapture(self.source) - if not self.cap.isOpened(): - logger.error(f"Failed to open video source: {self.source}") - return - - self.fps = self.cap.get(cv2.CAP_PROP_FPS) - if self.fps <= 0: - self.fps = 30.0 - - self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - - logger.debug(f"Video opened: {self.source} (fps: {self.fps}, size: {self.width}x{self.height})") - - frame_interval = 1.0 / self.fps - self.running = True - frame_emit_time = time.perf_counter() - - while self.running: - next_frame_time = frame_emit_time + frame_interval - - ret, frame = self.cap.read() - - if not ret: - logger.debug("End of video stream or read error") - break - - current_time = time.perf_counter() - self.metrics.last_cap_time = current_time - frame_emit_time - - if current_time < next_frame_time: - sleep_time = next_frame_time - current_time - time.sleep(sleep_time) - else: - self.metrics.frames_dropped += 1 - logger.debug(f"Frame drops counted: {self.metrics.frames_dropped}") - - frame_emit_time = time.perf_counter() - self._internal_frame.emit(frame) - - self.metrics.frames_processed += 1 - self.metrics.update_fps() - - self.cap.release() - - def stop(self): - self.running = False - self.mutex.lock() - self.condition.wakeAll() - self.mutex.unlock() - - -class VideoStream(QObject): - """Klasa fasadowa do użycia w głównym wątku GUI.""" - frame_ready = Signal(object) # To będzie emitowane z głównego wątku - - def __init__(self, source): - super().__init__() - self.worker = VideoStreamWorker(source) - self.worker_thread = QThread() - - # Przenosimy workera do osobnego wątku - self.worker.moveToThread(self.worker_thread) - - # Łączymy sygnały - self.worker._internal_frame.connect(self._on_frame) - self.worker_thread.started.connect(self.worker.run) - self.worker_thread.finished.connect(self.worker.deleteLater) - self.worker_thread.finished.connect(self.worker_thread.deleteLater) - - def start(self): - self.worker_thread.start() - - def stop(self): - self.worker.stop() - self.worker_thread.quit() - self.worker_thread.wait() - - def _on_frame(self, frame): - """Slot wywoływany w głównym wątku po otrzymaniu klatki.""" - # Tutaj możesz dodać korekcję ekspozycji jeśli nie zrobiłeś tego w workerze - self.frame_ready.emit(frame) \ No newline at end of file diff --git a/app/main_window.py b/app/main_window.py index d93f146..d944e11 100644 --- a/app/main_window.py +++ b/app/main_window.py @@ -2,10 +2,11 @@ from enum import Enum from typing import Any import logging -from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QMainWindow, QStyle, QToolButton, QWidget, QVBoxLayout, QPushButton -from PySide6.QtGui import QIcon +from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QMainWindow, QStyle, QToolButton, QWidget, QVBoxLayout, QPushButton, QLabel +from PySide6.QtGui import QIcon, QPixmap from PySide6.QtCore import Qt, QSize +from .video_stream.controller import VideoStreamController, FrameMetrics logger = logging.getLogger(__name__) class VideoMode(Enum): @@ -70,13 +71,22 @@ class MainWindow(QMainWindow): self.video_mode = VideoMode.STREAMING logger.debug(f"Initial video mode: {self.video_mode}") + + self.video_controller = VideoStreamController() + self.video_controller.change_source(0) # Start with default camera + self.video_controller.image_ready.connect(self.update_frame) + self.setup_ui() + self.video_controller.start() + def setup_ui(self): self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.central_widget.setStyleSheet("background-color: #001e1e;") + self.video_label = QLabel(self.central_widget) + self.toolbar_widget = QWidget(self.central_widget) self.toolbar_widget.setMinimumWidth(400) self.toolbar_widget.setObjectName("bottomToolbar") @@ -185,4 +195,12 @@ class MainWindow(QMainWindow): def pause_video(self): logger.debug("Pausing video") self.video_mode = VideoMode.STOPPED - set_icon(self.action_button, "play") \ No newline at end of file + set_icon(self.action_button, "play") + + def closeEvent(self, event: Any) -> None: + logger.debug("Closing main window, cleaning up resources.") + self.video_controller.cleanup() + super().closeEvent(event) + + def update_frame(self, frame): + self.video_label.setPixmap(QPixmap.fromImage(frame)) \ No newline at end of file