from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSlider, QCheckBox, QFileDialog, QSizePolicy, QFrame, QTabWidget, QSplitter ) from PySide6.QtCore import Signal, Qt from PySide6.QtGui import QPixmap, QIcon import json import os from pathlib import Path class EnhancedPerformanceOverlay(QFrame): """Enhanced performance metrics overlay with traffic light status and fixed positioning.""" def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" QFrame { background: rgba(20, 30, 40, 0.95); border: 2px solid #03DAC5; border-radius: 12px; color: #fff; font-family: 'Segoe UI', 'Arial', sans-serif; } """) # Fixed size to prevent overlay from changing size self.setFixedSize(400, 140) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.setAttribute(Qt.WA_TransparentForMouseEvents) layout = QVBoxLayout(self) layout.setContentsMargins(16, 12, 16, 12) layout.setSpacing(8) # Title row title_layout = QHBoxLayout() title = QLabel("📊 Real-time Performance Metrics") title.setStyleSheet(""" color: #03DAC5; font-weight: bold; font-size: 14px; margin-bottom: 4px; """) title_layout.addWidget(title) title_layout.addStretch() # Traffic light status self.traffic_light_status = QLabel("🚦 Traffic: Unknown") self.traffic_light_status.setStyleSheet(""" color: #FFD700; font-weight: bold; font-size: 13px; background: rgba(0,0,0,0.3); padding: 4px 8px; border-radius: 6px; """) title_layout.addWidget(self.traffic_light_status) layout.addLayout(title_layout) # Performance metrics row perf_layout = QHBoxLayout() perf_layout.setSpacing(16) # FPS and Inference in badges self.fps_label = QLabel("FPS: --") self.fps_label.setStyleSheet(""" background: #27AE60; color: white; font-weight: bold; font-size: 13px; padding: 6px 12px; border-radius: 8px; min-width: 70px; """) self.fps_label.setAlignment(Qt.AlignCenter) self.inference_label = QLabel("Inference: -- ms") self.inference_label.setStyleSheet(""" background: #3498DB; color: white; font-weight: bold; font-size: 13px; padding: 6px 12px; border-radius: 8px; min-width: 110px; """) self.inference_label.setAlignment(Qt.AlignCenter) perf_layout.addWidget(self.fps_label) perf_layout.addWidget(self.inference_label) perf_layout.addStretch() layout.addLayout(perf_layout) # System info row system_layout = QHBoxLayout() self.model_label = QLabel("Model: -") self.model_label.setStyleSheet(""" color: #E74C3C; font-weight: bold; font-size: 12px; background: rgba(231, 76, 60, 0.1); padding: 4px 8px; border-radius: 6px; """) self.device_label = QLabel("Device: -") self.device_label.setStyleSheet(""" color: #9B59B6; font-weight: bold; font-size: 12px; background: rgba(155, 89, 182, 0.1); padding: 4px 8px; border-radius: 6px; """) system_layout.addWidget(self.model_label) system_layout.addWidget(self.device_label) system_layout.addStretch() layout.addLayout(system_layout) # Vehicle counts row self.vehicle_stats_label = QLabel("🚗 Vehicles: 0 | 🚛 Trucks: 0 | 🚶 Pedestrians: 0 | 🏍️ Motorcycles: 0") self.vehicle_stats_label.setStyleSheet(""" color: #F39C12; font-weight: bold; font-size: 12px; background: rgba(243, 156, 18, 0.1); padding: 6px 10px; border-radius: 6px; """) layout.addWidget(self.vehicle_stats_label) def update_overlay(self, model, device, cars, trucks, peds, tlights, motorcycles): """Update performance metrics""" self.model_label.setText(f"Model: Yolov11x") self.device_label.setText(f"Device: {device}") self.vehicle_stats_label.setText(f"🚗 Vehicles: {cars} | 🚛 Trucks: {trucks} | 🚶 Pedestrians: {peds} | 🏍️ Motorcycles: {motorcycles}") def update_performance_metrics(self, fps, inference_time): """Update FPS and inference time""" if fps is not None: self.fps_label.setText(f"FPS: {fps:.1f}") else: self.fps_label.setText("FPS: --") if inference_time is not None: self.inference_label.setText(f"Inference: {inference_time:.1f} ms") else: self.inference_label.setText("Inference: -- ms") def update_traffic_light_status(self, traffic_light_data): """Update traffic light status""" if traffic_light_data and isinstance(traffic_light_data, dict): color = traffic_light_data.get('color', 'unknown') confidence = traffic_light_data.get('confidence', 0) if color.lower() == 'red': icon = "🔴" text_color = "#E74C3C" elif color.lower() == 'yellow': icon = "🟡" text_color = "#F39C12" elif color.lower() == 'green': icon = "🟢" text_color = "#27AE60" else: icon = "⚫" text_color = "#95A5A6" self.traffic_light_status.setText(f"{icon} Traffic: {color.title()} ({confidence:.2f})") self.traffic_light_status.setStyleSheet(f""" color: {text_color}; font-weight: bold; font-size: 13px; background: rgba(0,0,0,0.3); padding: 4px 8px; border-radius: 6px; """) else: self.traffic_light_status.setText("🚦 Traffic: Unknown") self.traffic_light_status.setStyleSheet(""" color: #95A5A6; font-weight: bold; font-size: 13px; background: rgba(0,0,0,0.3); padding: 4px 8px; border-radius: 6px; """) class PerformanceStatsWidget(QFrame): """Compact performance statistics widget to replace analytics tables.""" def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" QFrame { background: #1a1a1a; border: 1px solid #424242; border-radius: 8px; color: #fff; } """) layout = QVBoxLayout(self) layout.setContentsMargins(16, 16, 16, 16) layout.setSpacing(12) # Header header = QLabel("⚡ Performance Metrics") header.setStyleSheet(""" color: #03DAC5; font-weight: bold; font-size: 16px; padding: 8px 0px; border-bottom: 2px solid #03DAC5; margin-bottom: 8px; """) layout.addWidget(header) # Real-time stats self.fps_stat = QLabel("🎯 FPS: --") self.inference_stat = QLabel("⚡ Inference: -- ms") self.device_stat = QLabel("🖥️ Device: --") self.model_stat = QLabel("🤖 Model: --") # Vehicle counts self.vehicles_stat = QLabel("🚗 Vehicles: 0") self.pedestrians_stat = QLabel("🚶 Pedestrians: 0") self.traffic_lights_stat = QLabel("🚦 Traffic Lights: 0") # Traffic status self.traffic_status_stat = QLabel("🚦 Traffic Status: Unknown") stats = [ self.fps_stat, self.inference_stat, self.device_stat, self.model_stat, self.vehicles_stat, self.pedestrians_stat, self.traffic_lights_stat, self.traffic_status_stat ] for stat in stats: stat.setStyleSheet(""" color: #fff; font-size: 13px; padding: 6px 8px; background: rgba(255,255,255,0.05); border-radius: 6px; margin: 2px 0px; """) layout.addWidget(stat) layout.addStretch() def update_stats(self, stats_data): """Update all statistics""" # Debug: print what data we're receiving print(f"[PERF STATS DEBUG] Received stats keys: {list(stats_data.keys()) if stats_data else 'None'}") # Performance metrics - try multiple field names fps = stats_data.get('fps', 0) inference = stats_data.get('inference', stats_data.get('detection_time', stats_data.get('detection_time_ms', stats_data.get('inference_time', 0)))) # Try different field names for device and model device = (stats_data.get('device') or stats_data.get('device_name') or stats_data.get('processing_device') or 'GPU') # Default to GPU since we see it's working model = (stats_data.get('model') or stats_data.get('model_name') or stats_data.get('ai_model') or 'YOLO11') # Default to YOLO11 since that's what we're using # Extract model info from stats model = stats_data.get('model_name', 'Unknown') print(f"🔧 DEBUG UI: Received model_name='{model}' from stats") # If model name not available in stats, try to extract from model_path if model == 'Unknown': model_path = str(stats_data.get('model_path', '')) print(f"🔧 DEBUG UI: Fallback to model_path='{model_path}'") if 'yolo11n' in model_path.lower(): model = 'YOLO11n' elif 'yolo11x' in model_path.lower(): model = 'YOLO11x' elif 'yolo11s' in model_path.lower(): model = 'YOLO11s' elif 'yolo11m' in model_path.lower(): model = 'YOLO11m' elif 'yolo11l' in model_path.lower(): model = 'YOLO11l' else: model = 'YOLO11n' # Default fallback print(f"🔧 DEBUG UI: Final model name for display: '{model}'") print(f"[PERF STATS DEBUG] Device: {device}, Model: {model}, FPS: {fps}, Inference: {inference}") self.fps_stat.setText(f"🎯 FPS: {fps:.1f}" if fps else "🎯 FPS: --") self.inference_stat.setText(f"⚡ Inference: {inference:.1f} ms" if inference else "⚡ Inference: -- ms") self.device_stat.setText(f"🖥️ Device: {device}") self.model_stat.setText(f"🤖 Model: {model}") # Vehicle counts cars = stats_data.get('cars', 0) trucks = stats_data.get('trucks', 0) motorcycles = stats_data.get('motorcycles', 0) peds = stats_data.get('peds', 0) tlights = stats_data.get('tlights', 0) total_vehicles = cars + trucks + motorcycles self.vehicles_stat.setText(f"🚗 Vehicles: {total_vehicles}") self.pedestrians_stat.setText(f"🚶 Pedestrians: {peds}") self.traffic_lights_stat.setText(f"🚦 Traffic Lights: {tlights}") # Traffic status - try different field names traffic_light = (stats_data.get('traffic_light') or stats_data.get('traffic_light_data') or stats_data.get('latest_traffic_light')) print(f"[PERF STATS DEBUG] Traffic light data: {traffic_light}") if traffic_light and isinstance(traffic_light, dict): color = traffic_light.get('color', 'Unknown') confidence = traffic_light.get('confidence', 0) # Handle case where color might also be a dict or other type if isinstance(color, str): self.traffic_status_stat.setText(f"🚦 Traffic Status: {color.title()} ({confidence:.2f})") elif isinstance(color, dict): # Color is nested dict, try to extract actual color actual_color = color.get('color', 'Unknown') if isinstance(actual_color, str): self.traffic_status_stat.setText(f"🚦 Traffic Status: {actual_color.title()} ({confidence:.2f})") else: self.traffic_status_stat.setText("🚦 Traffic Status: Unknown") else: self.traffic_status_stat.setText(f"🚦 Traffic Status: {str(color)} ({confidence:.2f})") else: # Try to get traffic light info from other fields if 'traffic_light_color' in stats_data: color = stats_data.get('traffic_light_color', 'Unknown') if isinstance(color, str): self.traffic_status_stat.setText(f"🚦 Traffic Status: {color.title()}") else: self.traffic_status_stat.setText(f"🚦 Traffic Status: {str(color)}") else: self.traffic_status_stat.setText("🚦 Traffic Status: Unknown") class VideoDetectionOnlyTab(QWidget): """Standard video detection tab without smart intersection features""" 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() def __init__(self): super().__init__() self.video_loaded = False # Main layout with splitter for video and analytics main_layout = QVBoxLayout(self) main_layout.setContentsMargins(16, 16, 16, 16) main_layout.setSpacing(16) # File selection bar file_bar = self._create_file_bar() main_layout.addWidget(file_bar) # Create splitter for video and analytics splitter = QSplitter(Qt.Horizontal) # Left side - Video display video_widget = QWidget() video_layout = QVBoxLayout(video_widget) video_layout.setContentsMargins(0, 0, 0, 0) # Video display area video_frame = self._create_video_frame() video_layout.addWidget(video_frame, 1) # Control bar control_bar = self._create_control_bar() video_layout.addWidget(control_bar) splitter.addWidget(video_widget) # Right side - Performance Stats (replacing analytics tables) self.performance_stats = PerformanceStatsWidget() splitter.addWidget(self.performance_stats) # Set splitter proportions (70% video, 30% analytics) splitter.setSizes([700, 300]) splitter.setCollapsible(0, False) # Video section can't be collapsed splitter.setCollapsible(1, True) # Analytics can be collapsed main_layout.addWidget(splitter) def _create_file_bar(self): """Create file selection bar""" widget = QWidget() bar = QHBoxLayout(widget) 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) bar.addWidget(self.file_btn) bar.addWidget(self.file_label) bar.addStretch() return widget def _create_video_frame(self): """Create video display frame with fixed size""" video_frame = QFrame() video_frame.setStyleSheet(""" background: #121212; border: 1px solid #424242; border-radius: 8px; """) # Set fixed size to prevent resizing issues video_frame.setFixedSize(800, 450) # 16:9 aspect ratio, fixed size video_frame.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 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.") # Set fixed size for video label to match frame self.video_label.setFixedSize(800, 450) self.video_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.video_label.setScaledContents(True) # Scale content to fit video_layout.addWidget(self.video_label) return video_frame def _create_control_bar(self): """Create control bar""" widget = QWidget() control_bar = QHBoxLayout(widget) 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() return widget 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() 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() def update_display(self, pixmap): """Update display with new frame""" 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): """Update statistics display""" # Update right panel performance stats only self.performance_stats.update_stats(stats) def update_progress(self, value, max_value, timestamp): self.progress.setMaximum(max_value) self.progress.setValue(value) if isinstance(timestamp, float) or isinstance(timestamp, int): timestamp_str = f"{timestamp:.2f}" else: timestamp_str = str(timestamp) self.timestamp.setText(timestamp_str)