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

255 lines
12 KiB
Python

from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSlider, QCheckBox, QFileDialog, QSizePolicy, QGridLayout, QFrame, QSpacerItem
from PySide6.QtCore import Signal, Qt
from PySide6.QtGui import QPixmap, QIcon, QFont
class DiagnosticOverlay(QFrame):
"""Semi-transparent overlay for diagnostics."""
def __init__(self, parent=None):
super().__init__(parent)
self.setStyleSheet("""
background: rgba(0,0,0,0.5);
border-radius: 8px;
color: #fff;
font-family: 'Consolas', 'SF Mono', 'monospace';
font-size: 13px;
""")
# self.setFixedWidth(260) # Remove fixed width
self.setFixedHeight(90)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) # Allow horizontal stretch
self.setAttribute(Qt.WA_TransparentForMouseEvents)
layout = QVBoxLayout(self)
layout.setContentsMargins(12, 8, 12, 8)
self.model_label = QLabel("Model: -")
self.device_label = QLabel("Device: -")
self.stats_label = QLabel("Cars: 0 | Trucks: 0 | Ped: 0 | TLights: 0 | Moto: 0")
for w in [self.model_label, self.device_label, self.stats_label]:
w.setStyleSheet("color: #fff;")
layout.addWidget(w)
layout.addStretch(1)
def update_overlay(self, model, device, cars, trucks, peds, tlights, motorcycles):
self.model_label.setText(f"Model: {model}")
self.device_label.setText(f"Device: {device}")
self.stats_label.setText(f"Cars: {cars} | Trucks: {trucks} | Ped: {peds} | TLights: {tlights} | Moto: {motorcycles}")
class VideoDetectionTab(QWidget):
file_selected = Signal(str)
play_clicked = Signal()
pause_clicked = Signal()
stop_clicked = Signal()
detection_toggled = Signal(bool)
screenshot_clicked = Signal()
seek_changed = Signal(int)
auto_select_model_device = Signal() # New signal for auto model/device selection
def __init__(self):
super().__init__()
self.video_loaded = False
grid = QGridLayout(self)
grid.setContentsMargins(32, 24, 32, 24)
grid.setSpacing(0)
# File select bar (top)
file_bar = QHBoxLayout()
self.file_btn = QPushButton()
self.file_btn.setIcon(QIcon.fromTheme("folder-video"))
self.file_btn.setText("Select Video")
self.file_btn.setStyleSheet("padding: 8px 18px; border-radius: 8px; background: #232323; color: #fff;")
self.file_label = QLabel("No file selected")
self.file_label.setStyleSheet("color: #bbb; font-size: 13px;")
self.file_btn.clicked.connect(self._select_file)
file_bar.addWidget(self.file_btn)
file_bar.addWidget(self.file_label)
file_bar.addStretch(1)
# Video display area (centered, scalable)
video_frame = QFrame()
video_frame.setStyleSheet("""
background: #121212;
border: 1px solid #424242;
border-radius: 8px;
""")
video_frame.setMinimumSize(640, 360)
video_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
video_layout = QVBoxLayout(video_frame)
video_layout.setContentsMargins(0, 0, 0, 0)
video_layout.setAlignment(Qt.AlignCenter)
self.video_label = QLabel()
self.video_label.setAlignment(Qt.AlignCenter)
self.video_label.setStyleSheet("background: transparent; color: #888; font-size: 18px;")
self.video_label.setText("No video loaded. Please select a file.")
self.video_label.setMinimumSize(640, 360)
self.video_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
video_layout.addWidget(self.video_label)
# Diagnostic overlay (now below video, not over it)
self.overlay = DiagnosticOverlay()
self.overlay.setStyleSheet(self.overlay.styleSheet() + "border: 1px solid #03DAC5;")
self.overlay.setFixedHeight(90)
# FPS and Inference badges (below video)
self.fps_badge = QLabel("FPS: --")
self.fps_badge.setStyleSheet("background: #27ae60; color: #fff; border-radius: 12px; padding: 4px 24px; font-weight: bold; font-size: 15px;")
self.fps_badge.setAlignment(Qt.AlignCenter)
self.inference_badge = QLabel("Inference: -- ms")
self.inference_badge.setStyleSheet("background: #2980b9; color: #fff; border-radius: 12px; padding: 4px 24px; font-weight: bold; font-size: 15px;")
self.inference_badge.setAlignment(Qt.AlignCenter)
# Horizontal layout for overlay and badges
self.badge_bar = QHBoxLayout()
self.badge_bar.setContentsMargins(0, 8, 0, 8)
self.badge_bar.addWidget(self.fps_badge)
self.badge_bar.addSpacing(12)
self.badge_bar.addWidget(self.inference_badge)
self.badge_bar.addSpacing(18)
self.badge_bar.addWidget(self.overlay) # Overlay will stretch to fill right side
self.badge_bar.addStretch(10)
video_layout.addStretch(1) # Push badge bar to the bottom
video_layout.addLayout(self.badge_bar)
# Control bar (bottom)
control_bar = QHBoxLayout()
control_bar.setContentsMargins(0, 16, 0, 0)
# Playback controls
self.play_btn = QPushButton()
self.play_btn.setIcon(QIcon.fromTheme("media-playback-start"))
self.play_btn.setToolTip("Play")
self.play_btn.setFixedSize(48, 48)
self.play_btn.setEnabled(False)
self.play_btn.setStyleSheet(self._button_style())
self.pause_btn = QPushButton()
self.pause_btn.setIcon(QIcon.fromTheme("media-playback-pause"))
self.pause_btn.setToolTip("Pause")
self.pause_btn.setFixedSize(48, 48)
self.pause_btn.setEnabled(False)
self.pause_btn.setStyleSheet(self._button_style())
self.stop_btn = QPushButton()
self.stop_btn.setIcon(QIcon.fromTheme("media-playback-stop"))
self.stop_btn.setToolTip("Stop")
self.stop_btn.setFixedSize(48, 48)
self.stop_btn.setEnabled(False)
self.stop_btn.setStyleSheet(self._button_style())
for btn, sig in zip([self.play_btn, self.pause_btn, self.stop_btn], [self.play_clicked.emit, self.pause_clicked.emit, self.stop_clicked.emit]):
btn.clicked.connect(sig)
control_bar.addWidget(self.play_btn)
control_bar.addWidget(self.pause_btn)
control_bar.addWidget(self.stop_btn)
control_bar.addSpacing(16)
# Progress bar
self.progress = QSlider(Qt.Horizontal)
self.progress.setStyleSheet("QSlider::groove:horizontal { height: 6px; background: #232323; border-radius: 3px; } QSlider::handle:horizontal { background: #03DAC5; border-radius: 8px; width: 18px; }")
self.progress.setMinimumWidth(240)
self.progress.setEnabled(False)
self.progress.valueChanged.connect(self.seek_changed.emit)
control_bar.addWidget(self.progress, 2)
self.timestamp = QLabel("00:00 / 00:00")
self.timestamp.setStyleSheet("color: #bbb; font-size: 13px;")
control_bar.addWidget(self.timestamp)
control_bar.addSpacing(16)
# Detection toggle & screenshot
self.detection_toggle = QCheckBox("Enable Detection")
self.detection_toggle.setChecked(True)
self.detection_toggle.setStyleSheet("color: #fff; font-size: 14px;")
self.detection_toggle.setEnabled(False)
self.detection_toggle.toggled.connect(self.detection_toggled.emit)
control_bar.addWidget(self.detection_toggle)
self.screenshot_btn = QPushButton()
self.screenshot_btn.setIcon(QIcon.fromTheme("camera-photo"))
self.screenshot_btn.setText("Screenshot")
self.screenshot_btn.setToolTip("Save current frame as image")
self.screenshot_btn.setEnabled(False)
self.screenshot_btn.setStyleSheet(self._button_style())
self.screenshot_btn.clicked.connect(self.screenshot_clicked.emit)
control_bar.addWidget(self.screenshot_btn)
control_bar.addStretch(1)
# Layout grid
grid.addLayout(file_bar, 0, 0, 1, 1)
grid.addWidget(video_frame, 1, 0, 1, 1)
grid.addLayout(self.badge_bar, 2, 0, 1, 1)
grid.addLayout(control_bar, 3, 0, 1, 1)
grid.setRowStretch(1, 1)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
def _button_style(self):
return """
QPushButton {
background: #232323;
border-radius: 24px;
color: #fff;
font-size: 15px;
border: none;
}
QPushButton:hover {
background: #03DAC5;
color: #222;
}
QPushButton:pressed {
background: #018786;
}
"""
def _select_file(self):
file_path, _ = QFileDialog.getOpenFileName(self, "Select Video File", "", "Video Files (*.mp4 *.avi *.mov *.mkv *.webm);;All Files (*)")
if file_path:
self.file_label.setText(file_path)
self.file_selected.emit(file_path)
self.video_loaded = True
self._enable_controls(True)
self.video_label.setText("")
self.auto_select_model_device.emit() # Request auto model/device selection
def _enable_controls(self, enabled):
self.play_btn.setEnabled(enabled)
self.pause_btn.setEnabled(enabled)
self.stop_btn.setEnabled(enabled)
self.progress.setEnabled(enabled)
self.detection_toggle.setEnabled(enabled)
self.screenshot_btn.setEnabled(enabled)
if enabled:
self.auto_select_model_device.emit() # Also trigger auto-select when controls are enabled
def update_display(self, pixmap):
# Maintain aspect ratio
if pixmap:
scaled = pixmap.scaled(self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.video_label.setPixmap(scaled)
self._set_controls_enabled(True)
self.video_label.setStyleSheet("background: transparent; color: #888; font-size: 18px;")
else:
self.video_label.clear()
self.video_label.setText("No video loaded. Please select a video file.")
self._set_controls_enabled(False)
self.video_label.setStyleSheet("background: transparent; color: #F44336; font-size: 18px;")
def _set_controls_enabled(self, enabled):
for btn in [self.play_btn, self.pause_btn, self.stop_btn, self.progress, self.detection_toggle, self.screenshot_btn]:
btn.setEnabled(enabled)
def update_stats(self, stats):
# Accepts a stats dict for extensibility
cars = stats.get('cars', 0)
trucks = stats.get('trucks', 0)
peds = stats.get('peds', 0)
tlights = stats.get('tlights', 0)
motorcycles = stats.get('motorcycles', 0)
fps = stats.get('fps', None)
# Try all possible keys for inference time
inference = stats.get('inference', stats.get('detection_time', stats.get('detection_time_ms', None)))
model = stats.get('model', stats.get('model_name', '-'))
device = stats.get('device', stats.get('device_name', '-'))
# Update overlay
self.overlay.update_overlay(model, device, cars, trucks, peds, tlights, motorcycles)
# Update FPS and Inference badges
if fps is not None:
self.fps_badge.setText(f"FPS: {fps:.2f}")
else:
self.fps_badge.setText("FPS: --")
if inference is not None:
self.inference_badge.setText(f"Inference: {inference:.1f} ms")
else:
self.inference_badge.setText("Inference: -- ms")
def update_progress(self, value, max_value, timestamp):
self.progress.setMaximum(max_value)
self.progress.setValue(value)
# Format timestamp as string (e.g., "00:00 / 00:00" or just str)
if isinstance(timestamp, float) or isinstance(timestamp, int):
timestamp_str = f"{timestamp:.2f}"
else:
timestamp_str = str(timestamp)
self.timestamp.setText(timestamp_str)