Files
Traffic-Intersection-Monito…/qt_app_pyside1/ui/live_multi_cam_tab.py

169 lines
8.6 KiB
Python

from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QIcon, QImage, QPixmap
from PySide6.QtWidgets import QWidget, QGridLayout, QVBoxLayout, QLabel, QPushButton, QHBoxLayout, QFrame, QComboBox, QCheckBox
import cv2
import numpy as np
class CameraFeedWidget(QFrame):
settings_clicked = Signal(int)
detection_toggled = Signal(int, bool)
def __init__(self, cam_number):
super().__init__()
self.cam_number = cam_number
self.setFrameShape(QFrame.Box)
self.setLineWidth(3)
self.setStyleSheet("QFrame { border: 3px solid gray; border-radius: 8px; }")
layout = QVBoxLayout()
top_bar = QHBoxLayout()
self.overlay_label = QLabel(f"<b>CAM {cam_number}</b>")
self.gear_btn = QPushButton()
self.gear_btn.setIcon(QIcon.fromTheme("settings"))
self.gear_btn.setFixedSize(24,24)
self.gear_btn.clicked.connect(lambda: self.settings_clicked.emit(self.cam_number))
top_bar.addWidget(self.overlay_label)
top_bar.addStretch()
top_bar.addWidget(self.gear_btn)
layout.addLayout(top_bar)
self.video_label = QLabel("No Feed")
self.video_label.setMinimumHeight(160)
self.fps_label = QLabel("FPS: 0")
self.count_label = QLabel("Cars: 0 | Trucks: 0 | Ped: 0 | TLights: 0 | Moto: 0")
self.detection_toggle = QCheckBox("Detection ON")
self.detection_toggle.setChecked(True)
self.detection_toggle.toggled.connect(lambda checked: self.detection_toggled.emit(self.cam_number, checked))
self.start_stop_btn = QPushButton("Start")
layout.addWidget(self.video_label)
layout.addWidget(self.fps_label)
layout.addWidget(self.count_label)
layout.addWidget(self.detection_toggle)
layout.addWidget(self.start_stop_btn)
self.setLayout(layout)
def set_active(self, active):
color = "#00FF00" if active else "gray"
self.setStyleSheet(f"QFrame {{ border: 3px solid {color}; border-radius: 8px; }}")
class LiveMultiCamTab(QWidget):
source_changed = Signal(int, object) # cam_number, source
run_requested = Signal(int, bool) # cam_number, start/stop
detection_toggled = Signal(int, bool) # cam_number, enabled
settings_clicked = Signal(int)
global_detection_toggled = Signal(bool)
device_changed = Signal(str)
video_dropped = Signal(int, object) # cam_number, dropped source
snapshot_requested = Signal(int) # cam_number
def __init__(self):
super().__init__()
# Info bar at the top (only for Live Detection tab)
info_bar = QHBoxLayout()
self.model_label = QLabel("Model: -")
self.device_label = QLabel("Device: -")
self.yolo_label = QLabel("YOLO Version: -")
self.resolution_label = QLabel("Resolution: -")
self.cam1_fps = QLabel("CAM 1 FPS: -")
self.cam2_fps = QLabel("CAM 2 FPS: -")
self.cam3_fps = QLabel("CAM 3 FPS: -")
self.cam4_fps = QLabel("CAM 4 FPS: -")
info_bar.addWidget(self.model_label)
info_bar.addWidget(self.device_label)
info_bar.addWidget(self.yolo_label)
info_bar.addWidget(self.resolution_label)
info_bar.addWidget(self.cam1_fps)
info_bar.addWidget(self.cam2_fps)
info_bar.addWidget(self.cam3_fps)
info_bar.addWidget(self.cam4_fps)
info_bar.addStretch()
grid = QGridLayout()
self.cameras = []
for i in range(4):
cam_widget = CameraFeedWidget(i+1)
cam_widget.start_stop_btn.clicked.connect(lambda checked, n=i+1: self._handle_start_stop(n))
cam_widget.settings_clicked.connect(self.settings_clicked.emit)
cam_widget.detection_toggled.connect(self.detection_toggled.emit)
# Add snapshot button for each camera
snapshot_btn = QPushButton("Snapshot")
snapshot_btn.clicked.connect(lambda checked=False, n=i+1: self.snapshot_requested.emit(n))
cam_widget.layout().addWidget(snapshot_btn)
self.cameras.append(cam_widget)
grid.addWidget(cam_widget, i//2, i%2)
controls = QHBoxLayout()
self.start_all_btn = QPushButton("Start All")
self.stop_all_btn = QPushButton("Stop All")
self.global_detection_toggle = QCheckBox("Detection ON (All)")
self.global_detection_toggle.setChecked(True)
self.device_selector = QComboBox()
self.device_selector.addItems(["CPU", "GPU", "NPU"])
self.start_all_btn.clicked.connect(lambda: self._handle_all(True))
self.stop_all_btn.clicked.connect(lambda: self._handle_all(False))
self.global_detection_toggle.toggled.connect(self.global_detection_toggled.emit)
self.device_selector.currentTextChanged.connect(self.device_changed.emit)
controls.addWidget(self.start_all_btn)
controls.addWidget(self.stop_all_btn)
controls.addWidget(self.global_detection_toggle)
controls.addWidget(QLabel("Device:"))
controls.addWidget(self.device_selector)
main_layout = QVBoxLayout()
main_layout.addLayout(info_bar)
main_layout.addLayout(grid)
main_layout.addLayout(controls)
self.setLayout(main_layout)
def _handle_start_stop(self, cam_number):
btn = self.cameras[cam_number-1].start_stop_btn
start = btn.text() == "Start"
self.run_requested.emit(cam_number, start)
btn.setText("Stop" if start else "Start")
def _handle_all(self, start):
for i, cam in enumerate(self.cameras):
self.run_requested.emit(i+1, start)
cam.start_stop_btn.setText("Stop" if start else "Start")
def update_display(self, cam_number, pixmap):
# If pixmap is None, show a user-friendly message and disable controls
if pixmap is None:
self.cameras[cam_number-1].video_label.setText("No feed. Click 'Start' to connect a camera or select a video.")
self.cameras[cam_number-1].video_label.setStyleSheet("color: #F44336; font-size: 15px; background: transparent;")
self._set_controls_enabled(cam_number-1, False)
else:
self.cameras[cam_number-1].video_label.setPixmap(pixmap)
self.cameras[cam_number-1].video_label.setStyleSheet("background: transparent;")
self._set_controls_enabled(cam_number-1, True)
def _set_controls_enabled(self, cam_idx, enabled):
for btn in [self.cam_widgets[cam_idx]['start_btn'], self.cam_widgets[cam_idx]['snapshot_btn']]:
btn.setEnabled(enabled)
def update_display_np(self, np_frame):
"""Display a NumPy frame in CAM 1 (single source live mode)."""
import cv2
import numpy as np
if np_frame is None or not isinstance(np_frame, np.ndarray) or np_frame.size == 0:
print(f"[LiveMultiCamTab] ⚠️ Received None or empty frame for CAM 1")
return
try:
rgb_frame = cv2.cvtColor(np_frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_frame.shape
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtCore import Qt
bytes_per_line = ch * w
qimg = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(qimg)
scaled_pixmap = pixmap.scaled(
self.cameras[0].video_label.width(),
self.cameras[0].video_label.height(),
Qt.KeepAspectRatio, Qt.SmoothTransformation
)
self.cameras[0].video_label.setPixmap(scaled_pixmap)
self.cameras[0].video_label.update()
print(f"[LiveMultiCamTab] 🟢 Frame displayed for CAM 1")
except Exception as e:
print(f"[LiveMultiCamTab] ❌ Error displaying frame for CAM 1: {e}")
import traceback
traceback.print_exc()
def update_fps(self, cam_number, fps):
self.cameras[cam_number-1].fps_label.setText(f"FPS: {fps}")
def update_counts(self, cam_number, cars, trucks, peds, tlights, motorcycles):
self.cameras[cam_number-1].count_label.setText(
f"Cars: {cars} | Trucks: {trucks} | Ped: {peds} | TLights: {tlights} | Moto: {motorcycles}")
def update_stats(self, cam_number, stats):
# Placeholder: expects stats dict with keys: cars, trucks, peds, tlights, motorcycles, fps
self.update_counts(cam_number, stats.get('cars', 0), stats.get('trucks', 0), stats.get('peds', 0), stats.get('tlights', 0), stats.get('motorcycles', 0))
self.update_fps(cam_number, stats.get('fps', 0))
def set_detection_active(self, cam_number, active):
self.cameras[cam_number-1].set_active(active)