Final repository
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,14 @@ from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox,
|
||||
QSlider, QCheckBox, QPushButton, QGroupBox, QFormLayout,
|
||||
QSpinBox, QDoubleSpinBox, QTabWidget, QLineEdit, QFileDialog,
|
||||
QSpacerItem, QSizePolicy
|
||||
QSpacerItem, QSizePolicy, QScrollArea
|
||||
)
|
||||
from PySide6.QtCore import Qt, Signal, Slot
|
||||
from PySide6.QtGui import QFont
|
||||
|
||||
# Import VLM insights widget
|
||||
from ui.vlm_insights_widget import VLMInsightsWidget
|
||||
|
||||
class ConfigPanel(QWidget):
|
||||
"""Side panel for application configuration."""
|
||||
|
||||
@@ -362,10 +365,62 @@ class ConfigPanel(QWidget):
|
||||
|
||||
violation_layout.addWidget(violation_group)
|
||||
|
||||
# === VLM Insights Tab ===
|
||||
vlm_tab = QWidget()
|
||||
vlm_layout = QVBoxLayout(vlm_tab)
|
||||
|
||||
# Create scroll area for VLM insights
|
||||
vlm_scroll = QScrollArea()
|
||||
vlm_scroll.setWidgetResizable(True)
|
||||
vlm_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
vlm_scroll.setStyleSheet("""
|
||||
QScrollArea {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
""")
|
||||
|
||||
# Add VLM insights widget
|
||||
print("[CONFIG PANEL DEBUG] Creating VLM insights widget...")
|
||||
self.vlm_insights = VLMInsightsWidget()
|
||||
print("[CONFIG PANEL DEBUG] VLM insights widget created successfully")
|
||||
vlm_scroll.setWidget(self.vlm_insights)
|
||||
vlm_layout.addWidget(vlm_scroll)
|
||||
|
||||
# Smart Intersection Tab - Scene Analytics
|
||||
smart_intersection_tab = QWidget()
|
||||
si_layout = QVBoxLayout(smart_intersection_tab)
|
||||
|
||||
# Smart Intersection config widget
|
||||
si_scroll = QScrollArea()
|
||||
si_scroll.setWidgetResizable(True)
|
||||
si_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
si_scroll.setStyleSheet("""
|
||||
QScrollArea {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
""")
|
||||
|
||||
try:
|
||||
from ui.smart_intersection_config import SmartIntersectionConfigPanel
|
||||
self.smart_intersection_config = SmartIntersectionConfigPanel()
|
||||
si_scroll.setWidget(self.smart_intersection_config)
|
||||
print("[CONFIG PANEL DEBUG] Smart Intersection config panel created successfully")
|
||||
except Exception as e:
|
||||
print(f"[CONFIG PANEL DEBUG] Error creating Smart Intersection config: {e}")
|
||||
self.smart_intersection_config = None
|
||||
si_scroll.setWidget(QLabel(f"Smart Intersection config unavailable: {e}"))
|
||||
|
||||
si_layout.addWidget(si_scroll)
|
||||
|
||||
# Add all tabs
|
||||
tabs.addTab(detection_tab, "Detection")
|
||||
tabs.addTab(display_tab, "Display")
|
||||
tabs.addTab(violation_tab, "Violations")
|
||||
tabs.addTab(vlm_tab, "🤖 AI Insights") # Add VLM insights tab
|
||||
tabs.addTab(smart_intersection_tab, "🚦 Smart Intersection") # Add Smart Intersection tab
|
||||
print("[CONFIG PANEL DEBUG] Added AI Insights and Smart Intersection tabs to config panel")
|
||||
|
||||
layout.addWidget(tabs)
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QMainWindow, QTabWidget, QDockWidget, QMessageBox,
|
||||
QApplication, QFileDialog, QSplashScreen, QVBoxLayout, QWidget
|
||||
QApplication, QFileDialog, QSplashScreen, QVBoxLayout, QWidget, QLabel
|
||||
)
|
||||
from PySide6.QtCore import Qt, QTimer, QSettings, QSize, Slot
|
||||
from PySide6.QtGui import QIcon, QPixmap, QAction
|
||||
from PySide6.QtGui import QIcon, QPixmap, QAction, QFont
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -24,19 +24,26 @@ if hasattr(Qt, 'qInstallMessageHandler'):
|
||||
from ui.analytics_tab import AnalyticsTab
|
||||
from ui.violations_tab import ViolationsTab
|
||||
from ui.export_tab import ExportTab
|
||||
from ui.config_panel import ConfigPanel
|
||||
from ui.live_multi_cam_tab import LiveMultiCamTab
|
||||
from ui.video_detection_tab import VideoDetectionTab
|
||||
from ui.modern_config_panel import ModernConfigPanel
|
||||
from ui.modern_live_detection_tab import ModernLiveDetectionTab
|
||||
# from ui.video_analysis_tab import VideoAnalysisTab
|
||||
# from ui.video_detection_tab import VideoDetectionTab # Commented out - split into two separate tabs
|
||||
from ui.video_detection_only_tab import VideoDetectionOnlyTab
|
||||
from ui.smart_intersection_tab import SmartIntersectionTab
|
||||
from ui.global_status_panel import GlobalStatusPanel
|
||||
from ui.vlm_insights_widget import VLMInsightsWidget # Import the new VLM Insights Widget
|
||||
from ui.dashboard_tab import DashboardTab # Import the new Dashboard Tab
|
||||
|
||||
# Import controllers
|
||||
from controllers.video_controller_new import VideoController
|
||||
from controllers.analytics_controller import AnalyticsController
|
||||
from controllers.performance_overlay import PerformanceOverlay
|
||||
from controllers.model_manager import ModelManager
|
||||
# VLM Controller removed - functionality moved to insights widget
|
||||
|
||||
# Import utilities
|
||||
from utils.helpers import load_configuration, save_configuration, save_snapshot
|
||||
from utils.data_publisher import DataPublisher
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
"""Main application window."""
|
||||
@@ -58,6 +65,9 @@ class MainWindow(QMainWindow):
|
||||
# Connect signals and slots
|
||||
self.connectSignals()
|
||||
|
||||
# Initialize config panel with current configuration
|
||||
self.config_panel.set_config(self.config)
|
||||
|
||||
# Restore settings
|
||||
self.restoreSettings()
|
||||
|
||||
@@ -70,49 +80,134 @@ class MainWindow(QMainWindow):
|
||||
def setupUI(self):
|
||||
"""Set up the user interface"""
|
||||
# Window properties
|
||||
self.setWindowTitle("Traffic Monitoring System (OpenVINO PySide6)")
|
||||
self.setWindowTitle("Traffic Intersection Monitoring System")
|
||||
self.setMinimumSize(1200, 800)
|
||||
self.resize(1400, 900)
|
||||
|
||||
# Set up central widget with tabs
|
||||
self.tabs = QTabWidget()
|
||||
|
||||
# Style the tabs
|
||||
self.tabs.setStyleSheet("""
|
||||
QTabWidget::pane {
|
||||
border: 1px solid #444;
|
||||
background-color: #2b2b2b;
|
||||
}
|
||||
QTabBar::tab {
|
||||
background-color: #3c3c3c;
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
margin: 2px;
|
||||
border: 1px solid #555;
|
||||
border-bottom: none;
|
||||
border-radius: 4px 4px 0px 0px;
|
||||
min-width: 120px;
|
||||
}
|
||||
QTabBar::tab:selected {
|
||||
background-color: #0078d4;
|
||||
border-color: #0078d4;
|
||||
}
|
||||
QTabBar::tab:hover {
|
||||
background-color: #4a4a4a;
|
||||
}
|
||||
QTabBar::tab:!selected {
|
||||
margin-top: 2px;
|
||||
}
|
||||
""")
|
||||
|
||||
# Create tabs
|
||||
self.live_tab = LiveMultiCamTab()
|
||||
self.video_detection_tab = VideoDetectionTab()
|
||||
self.live_tab = ModernLiveDetectionTab()
|
||||
# self.video_analysis_tab = VideoAnalysisTab()
|
||||
# self.video_detection_tab = VideoDetectionTab() # Commented out - split into two separate tabs
|
||||
self.video_detection_only_tab = VideoDetectionOnlyTab()
|
||||
self.smart_intersection_tab = SmartIntersectionTab()
|
||||
self.analytics_tab = AnalyticsTab()
|
||||
self.violations_tab = ViolationsTab()
|
||||
self.export_tab = ExportTab()
|
||||
# Remove VLM tab - VLM functionality moved to settings panel
|
||||
# self.vlm_tab = VLMTab() # Create the VLM tab
|
||||
from ui.performance_graphs import PerformanceGraphsWidget
|
||||
self.performance_tab = PerformanceGraphsWidget()
|
||||
|
||||
# Add Dashboard tab
|
||||
try:
|
||||
self.dashboard_tab = DashboardTab()
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not create Dashboard tab: {e}")
|
||||
self.dashboard_tab = None
|
||||
|
||||
# Add User Guide tab
|
||||
try:
|
||||
from ui.user_guide_tab import UserGuideTab
|
||||
self.user_guide_tab = UserGuideTab()
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not create User Guide tab: {e}")
|
||||
self.user_guide_tab = None
|
||||
|
||||
# Add tabs to tab widget
|
||||
self.tabs.addTab(self.live_tab, "Live Detection")
|
||||
self.tabs.addTab(self.video_detection_tab, "Video Detection")
|
||||
self.tabs.addTab(self.performance_tab, "🔥 Performance & Latency")
|
||||
# self.tabs.addTab(self.video_analysis_tab, "Video Analysis")
|
||||
# self.tabs.addTab(self.video_detection_tab, "Smart Intersection") # Commented out - split into two tabs
|
||||
self.tabs.addTab(self.video_detection_only_tab, "Video Detection")
|
||||
# self.tabs.addTab(self.smart_intersection_tab, "Smart Intersection") # Temporarily hidden
|
||||
if self.dashboard_tab:
|
||||
self.tabs.addTab(self.dashboard_tab, "Dashboard")
|
||||
self.tabs.addTab(self.performance_tab, "Performance & Latency")
|
||||
self.tabs.addTab(self.analytics_tab, "Analytics")
|
||||
self.tabs.addTab(self.violations_tab, "Violations")
|
||||
# VLM functionality moved to settings panel
|
||||
# self.tabs.addTab(self.vlm_tab, "🔍 Vision AI") # Add VLM tab with icon
|
||||
self.tabs.addTab(self.export_tab, "Export & Config")
|
||||
|
||||
# Add User Guide tab if available
|
||||
if self.user_guide_tab:
|
||||
self.tabs.addTab(self.user_guide_tab, "Help")
|
||||
|
||||
# Create config panel in dock widget
|
||||
self.config_panel = ConfigPanel()
|
||||
self.config_panel = ModernConfigPanel()
|
||||
dock = QDockWidget("Settings", self)
|
||||
dock.setObjectName("SettingsDock") # Set object name to avoid warning
|
||||
dock.setWidget(self.config_panel)
|
||||
dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable)
|
||||
dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
|
||||
|
||||
# Set minimum and preferred size for the dock widget
|
||||
dock.setMinimumWidth(400)
|
||||
dock.resize(450, 800) # Set preferred width and height
|
||||
|
||||
self.addDockWidget(Qt.RightDockWidgetArea, dock)
|
||||
|
||||
# Create status bar
|
||||
self.statusBar().showMessage("Initializing...")
|
||||
|
||||
# Create main layout with header
|
||||
main_layout = QVBoxLayout()
|
||||
|
||||
# Add header title above tabs
|
||||
header_label = QLabel("Traffic Intersection Monitoring System")
|
||||
header_label.setAlignment(Qt.AlignCenter)
|
||||
header_font = QFont()
|
||||
header_font.setPointSize(14)
|
||||
header_font.setBold(True)
|
||||
header_label.setFont(header_font)
|
||||
header_label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #ffffff;
|
||||
background-color: #2b2b2b;
|
||||
padding: 10px;
|
||||
border-bottom: 2px solid #0078d4;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
""")
|
||||
main_layout.addWidget(header_label)
|
||||
|
||||
main_layout.addWidget(self.tabs)
|
||||
central = QWidget()
|
||||
central.setLayout(main_layout)
|
||||
self.setCentralWidget(central)
|
||||
|
||||
# Create menu bar
|
||||
self.setupMenus()
|
||||
# Create menu bar - commented out for cleaner interface
|
||||
# self.setupMenus()
|
||||
|
||||
# Create performance overlay
|
||||
self.performance_overlay = PerformanceOverlay()
|
||||
@@ -131,6 +226,17 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# Create analytics controller
|
||||
self.analytics_controller = AnalyticsController()
|
||||
|
||||
# Initialize data publisher for InfluxDB
|
||||
print("[MAIN WINDOW DEBUG] Initializing Data Publisher...")
|
||||
self.data_publisher = DataPublisher(self.config_file)
|
||||
print("[MAIN WINDOW DEBUG] Data Publisher initialized successfully")
|
||||
|
||||
# VLM controller - using only local VLM folder, no backend
|
||||
print("[MAIN WINDOW DEBUG] Initializing VLM Controller with local VLM folder...")
|
||||
from controllers.vlm_controller_new import VLMController
|
||||
self.vlm_controller = VLMController() # No backend URL needed
|
||||
print("[MAIN WINDOW DEBUG] VLM Controller initialized successfully")
|
||||
|
||||
# Setup update timer for performance overlay
|
||||
self.perf_timer = QTimer()
|
||||
@@ -138,11 +244,56 @@ class MainWindow(QMainWindow):
|
||||
self.perf_timer.start(1000) # Update every second
|
||||
|
||||
# Connect video_file_controller outputs to video_detection_tab
|
||||
self.video_file_controller.frame_ready.connect(self.video_detection_tab.update_display, Qt.QueuedConnection)
|
||||
self.video_file_controller.stats_ready.connect(self.video_detection_tab.update_stats, Qt.QueuedConnection)
|
||||
self.video_file_controller.progress_ready.connect(lambda value, max_value, timestamp: self.video_detection_tab.update_progress(value, max_value, timestamp), Qt.QueuedConnection)
|
||||
# Connect video file controller signals to both video tabs
|
||||
self.video_file_controller.frame_ready.connect(self.video_detection_only_tab.update_display, Qt.QueuedConnection)
|
||||
self.video_file_controller.stats_ready.connect(self.video_detection_only_tab.update_stats, Qt.QueuedConnection)
|
||||
self.video_file_controller.progress_ready.connect(lambda value, max_value, timestamp: self.video_detection_only_tab.update_progress(value, max_value, timestamp), Qt.QueuedConnection)
|
||||
|
||||
self.video_file_controller.frame_ready.connect(self.smart_intersection_tab.update_display, Qt.QueuedConnection)
|
||||
self.video_file_controller.stats_ready.connect(self.smart_intersection_tab.update_stats, Qt.QueuedConnection)
|
||||
self.video_file_controller.progress_ready.connect(lambda value, max_value, timestamp: self.smart_intersection_tab.update_progress(value, max_value, timestamp), Qt.QueuedConnection)
|
||||
|
||||
# Connect video frames to VLM insights for analysis
|
||||
if hasattr(self.video_file_controller, 'raw_frame_ready'):
|
||||
print("[MAIN WINDOW DEBUG] Connecting raw_frame_ready signal to VLM insights")
|
||||
self.video_file_controller.raw_frame_ready.connect(
|
||||
self._forward_frame_to_vlm, Qt.QueuedConnection
|
||||
)
|
||||
print("[MAIN WINDOW DEBUG] raw_frame_ready signal connected to VLM insights")
|
||||
|
||||
# Also connect to analytics tab
|
||||
print("[MAIN WINDOW DEBUG] Connecting raw_frame_ready signal to analytics tab")
|
||||
self.video_file_controller.raw_frame_ready.connect(
|
||||
self._forward_frame_to_analytics, Qt.QueuedConnection
|
||||
)
|
||||
print("[MAIN WINDOW DEBUG] raw_frame_ready signal connected to analytics tab")
|
||||
else:
|
||||
print("[MAIN WINDOW DEBUG] raw_frame_ready signal not found in video_file_controller")
|
||||
# Connect auto model/device selection signal
|
||||
self.video_detection_tab.auto_select_model_device.connect(self.video_file_controller.auto_select_model_device, Qt.QueuedConnection)
|
||||
# Connect video tab auto-select signals
|
||||
self.video_detection_only_tab.auto_select_model_device.connect(self.video_file_controller.auto_select_model_device, Qt.QueuedConnection)
|
||||
self.smart_intersection_tab.auto_select_model_device.connect(self.video_file_controller.auto_select_model_device, Qt.QueuedConnection)
|
||||
|
||||
# Connect VLM insights analysis requests to a simple mock handler (since optimum is disabled)
|
||||
print("[MAIN WINDOW DEBUG] Checking for VLM insights widget...")
|
||||
if hasattr(self.config_panel, 'vlm_insights_widget'):
|
||||
print("[MAIN WINDOW DEBUG] VLM insights widget found, connecting signals...")
|
||||
self.config_panel.vlm_insights_widget.analyze_frame_requested.connect(self._handle_vlm_analysis, Qt.QueuedConnection)
|
||||
print("[MAIN WINDOW DEBUG] VLM insights analysis signal connected")
|
||||
|
||||
# Connect pause state signal from video file controller to VLM insights
|
||||
if hasattr(self.video_file_controller, 'pause_state_changed'):
|
||||
self.video_file_controller.pause_state_changed.connect(self.config_panel.vlm_insights_widget.on_video_paused, Qt.QueuedConnection)
|
||||
print("[MAIN WINDOW DEBUG] VLM insights pause state signal connected")
|
||||
else:
|
||||
print("[MAIN WINDOW DEBUG] pause_state_changed signal not found in video_file_controller")
|
||||
else:
|
||||
print("[MAIN WINDOW DEBUG] VLM insights widget NOT found in config panel")
|
||||
|
||||
# Old VLM tab connections removed - functionality moved to insights widget
|
||||
# self.vlm_tab.process_image_requested.connect(self.vlm_controller.process_image, Qt.QueuedConnection)
|
||||
# self.video_controller.frame_np_ready.connect(self.vlm_tab.set_frame, Qt.QueuedConnection)
|
||||
# self.video_file_controller.frame_np_ready.connect(self.vlm_tab.set_frame, Qt.QueuedConnection)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
@@ -150,6 +301,7 @@ class MainWindow(QMainWindow):
|
||||
f"Error initializing controllers: {str(e)}"
|
||||
)
|
||||
print(f"Error details: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def connectSignals(self):
|
||||
@@ -212,14 +364,46 @@ class MainWindow(QMainWindow):
|
||||
self.export_tab.reload_config_btn.clicked.connect(self.load_config)
|
||||
self.export_tab.export_btn.clicked.connect(self.export_data)
|
||||
|
||||
# Video Detection tab connections
|
||||
self.video_detection_tab.file_selected.connect(self._handle_video_file_selected)
|
||||
self.video_detection_tab.play_clicked.connect(self._handle_video_play)
|
||||
self.video_detection_tab.pause_clicked.connect(self._handle_video_pause)
|
||||
self.video_detection_tab.stop_clicked.connect(self._handle_video_stop)
|
||||
self.video_detection_tab.detection_toggled.connect(self._handle_video_detection_toggle)
|
||||
self.video_detection_tab.screenshot_clicked.connect(self._handle_video_screenshot)
|
||||
self.video_detection_tab.seek_changed.connect(self._handle_video_seek)
|
||||
# Video Detection tab connections (standard tab)
|
||||
self.video_detection_only_tab.file_selected.connect(self._handle_video_file_selected)
|
||||
self.video_detection_only_tab.play_clicked.connect(self._handle_video_play)
|
||||
self.video_detection_only_tab.pause_clicked.connect(self._handle_video_pause)
|
||||
self.video_detection_only_tab.stop_clicked.connect(self._handle_video_stop)
|
||||
self.video_detection_only_tab.detection_toggled.connect(self._handle_video_detection_toggle)
|
||||
self.video_detection_only_tab.screenshot_clicked.connect(self._handle_video_screenshot)
|
||||
self.video_detection_only_tab.seek_changed.connect(self._handle_video_seek)
|
||||
|
||||
# Smart Intersection tab connections
|
||||
self.smart_intersection_tab.file_selected.connect(self._handle_video_file_selected)
|
||||
self.smart_intersection_tab.play_clicked.connect(self._handle_video_play)
|
||||
self.smart_intersection_tab.pause_clicked.connect(self._handle_video_pause)
|
||||
self.smart_intersection_tab.stop_clicked.connect(self._handle_video_stop)
|
||||
self.smart_intersection_tab.detection_toggled.connect(self._handle_video_detection_toggle)
|
||||
self.smart_intersection_tab.screenshot_clicked.connect(self._handle_video_screenshot)
|
||||
self.smart_intersection_tab.seek_changed.connect(self._handle_video_seek)
|
||||
|
||||
# Smart Intersection specific connections
|
||||
self.smart_intersection_tab.smart_intersection_enabled.connect(self._handle_smart_intersection_enabled)
|
||||
self.smart_intersection_tab.multi_camera_mode_enabled.connect(self._handle_multi_camera_mode)
|
||||
self.smart_intersection_tab.roi_configuration_changed.connect(self._handle_roi_configuration_changed)
|
||||
self.smart_intersection_tab.scene_analytics_toggled.connect(self._handle_scene_analytics_toggle)
|
||||
|
||||
# Connect smart intersection controller if available
|
||||
try:
|
||||
from controllers.smart_intersection_controller import SmartIntersectionController
|
||||
self.smart_intersection_controller = SmartIntersectionController()
|
||||
|
||||
# Connect scene analytics signals
|
||||
self.video_file_controller.frame_np_ready.connect(
|
||||
self.smart_intersection_controller.process_frame, Qt.QueuedConnection
|
||||
)
|
||||
self.smart_intersection_controller.scene_analytics_ready.connect(
|
||||
self._handle_scene_analytics_update, Qt.QueuedConnection
|
||||
)
|
||||
print("✅ Smart Intersection Controller connected")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Smart Intersection Controller not available: {e}")
|
||||
self.smart_intersection_controller = None
|
||||
|
||||
# Connect OpenVINO device info signal to config panel from BOTH controllers
|
||||
self.video_controller.device_info_ready.connect(self.config_panel.update_devices_info, Qt.QueuedConnection)
|
||||
@@ -227,7 +411,57 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# After connecting video_file_controller and video_detection_tab, trigger auto model/device update
|
||||
QTimer.singleShot(0, self.video_file_controller.auto_select_model_device.emit)
|
||||
|
||||
# Connect performance statistics from both controllers
|
||||
self.video_controller.performance_stats_ready.connect(self.update_performance_graphs)
|
||||
self.video_file_controller.performance_stats_ready.connect(self.update_performance_graphs)
|
||||
|
||||
# Connect enhanced performance tab signals
|
||||
if hasattr(self, 'performance_tab'):
|
||||
try:
|
||||
# Connect performance tab signals for better integration
|
||||
self.performance_tab.spike_detected.connect(self.handle_performance_spike)
|
||||
self.performance_tab.device_switched.connect(self.handle_device_switch_notification)
|
||||
self.performance_tab.performance_data_updated.connect(self.handle_performance_data_update)
|
||||
print("✅ Performance tab signals connected successfully")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Could not connect performance tab signals: {e}")
|
||||
|
||||
@Slot(dict)
|
||||
def handle_performance_spike(self, spike_data):
|
||||
"""Handle performance spike detection"""
|
||||
try:
|
||||
latency = spike_data.get('latency', 0)
|
||||
device = spike_data.get('device', 'Unknown')
|
||||
print(f"🚨 Performance spike detected: {latency:.1f}ms on {device}")
|
||||
|
||||
# Optionally show notification or log to analytics
|
||||
if hasattr(self, 'analytics_tab'):
|
||||
# Could add spike to analytics if needed
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error handling performance spike: {e}")
|
||||
|
||||
@Slot(str)
|
||||
def handle_device_switch_notification(self, device):
|
||||
"""Handle device switch notification"""
|
||||
try:
|
||||
print(f"🔄 Device switched to: {device}")
|
||||
# Could update UI elements or show notification
|
||||
except Exception as e:
|
||||
print(f"❌ Error handling device switch notification: {e}")
|
||||
|
||||
@Slot(dict)
|
||||
def handle_performance_data_update(self, performance_data):
|
||||
"""Handle performance data updates for other components"""
|
||||
try:
|
||||
# Could forward to other tabs or components that need performance data
|
||||
if hasattr(self, 'analytics_tab'):
|
||||
# Forward performance data to analytics if needed
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"❌ Error handling performance data update: {e}")
|
||||
def setupMenus(self):
|
||||
"""Set up application menus"""
|
||||
# File menu
|
||||
@@ -284,16 +518,46 @@ class MainWindow(QMainWindow):
|
||||
if not config:
|
||||
return
|
||||
|
||||
# Update config
|
||||
for section in config:
|
||||
if section in self.config:
|
||||
self.config[section].update(config[section])
|
||||
else:
|
||||
self.config[section] = config[section]
|
||||
# Convert flat config to nested structure for model manager
|
||||
nested_config = {
|
||||
"detection": {}
|
||||
}
|
||||
|
||||
# Update model manager
|
||||
# Map config panel values to model manager format
|
||||
if 'device' in config:
|
||||
nested_config["detection"]["device"] = config['device']
|
||||
if 'model' in config:
|
||||
# Convert YOLOv11x format to yolo11x format for model manager
|
||||
model_name = config['model'].lower()
|
||||
if 'yolov11' in model_name:
|
||||
model_name = model_name.replace('yolov11', 'yolo11')
|
||||
elif model_name == 'auto':
|
||||
model_name = 'auto'
|
||||
nested_config["detection"]["model"] = model_name
|
||||
if 'confidence_threshold' in config:
|
||||
nested_config["detection"]["confidence_threshold"] = config['confidence_threshold']
|
||||
if 'iou_threshold' in config:
|
||||
nested_config["detection"]["iou_threshold"] = config['iou_threshold']
|
||||
|
||||
print(f"🔧 Main Window: Applying config to model manager: {nested_config}")
|
||||
print(f"🔧 Main Window: Received config from panel: {config}")
|
||||
|
||||
# Update config
|
||||
for section in nested_config:
|
||||
if section in self.config:
|
||||
self.config[section].update(nested_config[section])
|
||||
else:
|
||||
self.config[section] = nested_config[section]
|
||||
|
||||
# Update model manager with nested config
|
||||
if self.model_manager:
|
||||
self.model_manager.update_config(self.config)
|
||||
self.model_manager.update_config(nested_config)
|
||||
|
||||
# Refresh model information in video controllers
|
||||
if hasattr(self, 'video_controller') and self.video_controller:
|
||||
self.video_controller.refresh_model_info()
|
||||
if hasattr(self, 'video_file_controller') and self.video_file_controller:
|
||||
self.video_file_controller.refresh_model_info()
|
||||
|
||||
# Save config to file
|
||||
save_configuration(self.config, self.config_file)
|
||||
@@ -302,7 +566,9 @@ class MainWindow(QMainWindow):
|
||||
self.export_tab.update_config_display(self.config)
|
||||
|
||||
# Update status
|
||||
self.statusBar().showMessage("Configuration applied", 2000)
|
||||
device = config.get('device', 'Unknown')
|
||||
model = config.get('model', 'Unknown')
|
||||
self.statusBar().showMessage(f"Configuration applied - Device: {device}, Model: {model}", 3000)
|
||||
|
||||
@Slot()
|
||||
def load_config(self):
|
||||
@@ -642,6 +908,7 @@ class MainWindow(QMainWindow):
|
||||
confidence_str = f" (Confidence: {confidence:.2f})" if confidence > 0 else ""
|
||||
else:
|
||||
traffic_light_color = traffic_light_info
|
||||
confidence = 1.0
|
||||
confidence_str = ""
|
||||
|
||||
if traffic_light_color != 'unknown':
|
||||
@@ -653,6 +920,16 @@ class MainWindow(QMainWindow):
|
||||
else:
|
||||
color_text = str(traffic_light_color).upper()
|
||||
self.statusBar().showMessage(f"Traffic Light: {color_text}{confidence_str}")
|
||||
|
||||
# Publish traffic light status to InfluxDB
|
||||
if hasattr(self, 'data_publisher') and self.data_publisher:
|
||||
try:
|
||||
color_for_publishing = traffic_light_color
|
||||
if isinstance(traffic_light_color, dict):
|
||||
color_for_publishing = traffic_light_color.get("color", "unknown")
|
||||
self.data_publisher.publish_traffic_light_status(color_for_publishing, confidence)
|
||||
except Exception as e:
|
||||
print(f"❌ Error publishing traffic light status: {e}")
|
||||
@Slot(dict)
|
||||
def handle_violation_detected(self, violation):
|
||||
"""Handle a detected traffic violation"""
|
||||
@@ -663,9 +940,28 @@ class MainWindow(QMainWindow):
|
||||
# Add to violations tab
|
||||
self.violations_tab.add_violation(violation)
|
||||
|
||||
# Update analytics tab with violation data
|
||||
if hasattr(self.analytics_tab, 'update_violation_data'):
|
||||
self.analytics_tab.update_violation_data(violation)
|
||||
print(f"[ANALYTICS DEBUG] Violation data forwarded to analytics tab")
|
||||
|
||||
# Update analytics
|
||||
if self.analytics_controller:
|
||||
self.analytics_controller.register_violation(violation)
|
||||
|
||||
# Publish violation to InfluxDB
|
||||
if hasattr(self, 'data_publisher') and self.data_publisher:
|
||||
try:
|
||||
violation_type = violation.get('type', 'red_light_violation')
|
||||
vehicle_id = violation.get('track_id', 'unknown')
|
||||
details = {
|
||||
'timestamp': violation.get('timestamp', ''),
|
||||
'confidence': violation.get('confidence', 1.0),
|
||||
'location': violation.get('location', 'crosswalk')
|
||||
}
|
||||
self.data_publisher.publish_violation_event(violation_type, vehicle_id, details)
|
||||
except Exception as e:
|
||||
print(f"❌ Error publishing violation event: {e}")
|
||||
|
||||
print(f"🚨 Violation processed: {violation['id']} at {violation['timestamp']}")
|
||||
except Exception as e:
|
||||
@@ -678,10 +974,29 @@ class MainWindow(QMainWindow):
|
||||
self.video_file_controller.set_source(file_path)
|
||||
def _handle_video_play(self):
|
||||
print("[VideoDetection] Play clicked")
|
||||
self.video_file_controller.play()
|
||||
# Check if video is paused, if so resume, otherwise start
|
||||
if hasattr(self.video_file_controller, '_paused') and self.video_file_controller._paused:
|
||||
self.video_file_controller.resume()
|
||||
else:
|
||||
self.video_file_controller.play()
|
||||
# Notify VLM insights that video is playing (not paused)
|
||||
print("[MAIN WINDOW DEBUG] Notifying VLM insights: video playing")
|
||||
if hasattr(self, 'config_panel') and hasattr(self.config_panel, 'vlm_insights_widget'):
|
||||
self.config_panel.vlm_insights_widget.on_video_paused(False)
|
||||
print("[MAIN WINDOW DEBUG] VLM insights notified: not paused")
|
||||
else:
|
||||
print("[MAIN WINDOW DEBUG] VLM insights not found for play notification")
|
||||
|
||||
def _handle_video_pause(self):
|
||||
print("[VideoDetection] Pause clicked")
|
||||
self.video_file_controller.pause()
|
||||
# Notify VLM insights that video is paused
|
||||
print("[MAIN WINDOW DEBUG] Notifying VLM insights: video paused")
|
||||
if hasattr(self, 'config_panel') and hasattr(self.config_panel, 'vlm_insights_widget'):
|
||||
self.config_panel.vlm_insights_widget.on_video_paused(True)
|
||||
print("[MAIN WINDOW DEBUG] VLM insights notified: paused")
|
||||
else:
|
||||
print("[MAIN WINDOW DEBUG] VLM insights not found for pause notification")
|
||||
def _handle_video_stop(self):
|
||||
print("[VideoDetection] Stop clicked")
|
||||
self.video_file_controller.stop()
|
||||
@@ -727,24 +1042,368 @@ class MainWindow(QMainWindow):
|
||||
self.statusBar().showMessage(f"Error switching device: {e}", 3000)
|
||||
@Slot(dict)
|
||||
def update_performance_graphs(self, stats):
|
||||
"""Update the performance graphs using the new robust widget logic."""
|
||||
"""Update the performance graphs using the enhanced widget logic."""
|
||||
if not hasattr(self, 'performance_tab'):
|
||||
return
|
||||
print(f"[PERF DEBUG] update_performance_graphs called with: {stats}")
|
||||
|
||||
# Publish performance data to InfluxDB
|
||||
if hasattr(self, 'data_publisher') and self.data_publisher:
|
||||
try:
|
||||
fps = stats.get('fps', 0)
|
||||
inference_time = stats.get('inference_time', 0)
|
||||
cpu_usage = stats.get('cpu_usage', None)
|
||||
gpu_usage = stats.get('gpu_usage', None)
|
||||
|
||||
self.data_publisher.publish_performance_data(fps, inference_time, cpu_usage, gpu_usage)
|
||||
|
||||
# Publish device info periodically (every 10th frame)
|
||||
if hasattr(self, '_device_info_counter'):
|
||||
self._device_info_counter += 1
|
||||
else:
|
||||
self._device_info_counter = 1
|
||||
|
||||
if self._device_info_counter % 10 == 0:
|
||||
self.data_publisher.publish_device_info()
|
||||
except Exception as e:
|
||||
print(f"❌ Error publishing performance data: {e}")
|
||||
|
||||
# Enhanced analytics data with proper structure
|
||||
current_time = time.time()
|
||||
analytics_data = {
|
||||
'real_time_data': {
|
||||
'timestamps': [stats.get('frame_idx', 0)],
|
||||
'timestamps': [current_time],
|
||||
'inference_latency': [stats.get('inference_time', 0)],
|
||||
'fps': [stats.get('fps', 0)],
|
||||
'device_usage': [1 if stats.get('device', 'CPU') == 'GPU' else 0],
|
||||
'resolution_width': [int(stats.get('resolution', '640x360').split('x')[0]) if 'x' in stats.get('resolution', '') else 640],
|
||||
'resolution_height': [int(stats.get('resolution', '640x360').split('x')[1]) if 'x' in stats.get('resolution', '') else 360],
|
||||
'device_switches': [0] if stats.get('is_device_switch', False) else [],
|
||||
'resolution_changes': [0] if stats.get('is_res_change', False) else [],
|
||||
},
|
||||
'latency_statistics': {},
|
||||
'current_metrics': {},
|
||||
'system_metrics': {},
|
||||
'latency_statistics': {
|
||||
'avg': stats.get('avg_inference_time', 0),
|
||||
'max': stats.get('max_inference_time', 0),
|
||||
'min': stats.get('min_inference_time', 0),
|
||||
'spike_count': stats.get('spike_count', 0)
|
||||
},
|
||||
'current_metrics': {
|
||||
'device': stats.get('device', 'CPU'),
|
||||
'resolution': stats.get('resolution', 'Unknown'),
|
||||
'model': stats.get('model_name', stats.get('model', 'Unknown')), # Try model_name first, then model
|
||||
'fps': stats.get('fps', 0),
|
||||
'inference_time': stats.get('inference_time', 0)
|
||||
},
|
||||
'system_metrics': {
|
||||
'cpu_usage': stats.get('cpu_usage', 0),
|
||||
'gpu_usage': stats.get('gpu_usage', 0),
|
||||
'memory_usage': stats.get('memory_usage', 0)
|
||||
}
|
||||
}
|
||||
print(f"[PERF DEBUG] analytics_data for update_performance_data: {analytics_data}")
|
||||
|
||||
print(f"[PERF DEBUG] Enhanced analytics_data: {analytics_data}")
|
||||
|
||||
# Update performance graphs with enhanced data
|
||||
self.performance_tab.update_performance_data(analytics_data)
|
||||
|
||||
def _handle_vlm_analysis(self, frame, prompt):
|
||||
"""Handle VLM analysis requests."""
|
||||
print(f"[MAIN WINDOW DEBUG] _handle_vlm_analysis called")
|
||||
print(f"[MAIN WINDOW DEBUG] Frame type: {type(frame)}, shape: {frame.shape if hasattr(frame, 'shape') else 'N/A'}")
|
||||
print(f"[MAIN WINDOW DEBUG] Prompt: '{prompt}'")
|
||||
|
||||
try:
|
||||
# Check if VLM controller is available
|
||||
if hasattr(self, 'vlm_controller') and self.vlm_controller:
|
||||
print(f"[MAIN WINDOW DEBUG] Using VLM controller for analysis")
|
||||
|
||||
# Connect VLM result to insights widget if not already connected
|
||||
if not hasattr(self, '_vlm_connected'):
|
||||
print(f"[MAIN WINDOW DEBUG] Connecting VLM controller results to insights widget")
|
||||
self.vlm_controller.result_ready.connect(
|
||||
lambda result: self._handle_vlm_result(result),
|
||||
Qt.QueuedConnection
|
||||
)
|
||||
self._vlm_connected = True
|
||||
|
||||
# Process image with VLM controller
|
||||
self.vlm_controller.process_image(frame, prompt)
|
||||
print(f"[MAIN WINDOW DEBUG] VLM controller processing started")
|
||||
|
||||
else:
|
||||
print(f"[MAIN WINDOW DEBUG] VLM controller not available, using mock analysis")
|
||||
# Fallback to mock analysis
|
||||
import cv2
|
||||
import numpy as np
|
||||
result = self._generate_mock_analysis(frame, prompt)
|
||||
print(f"[MAIN WINDOW DEBUG] Mock analysis generated: {len(result)} characters")
|
||||
|
||||
# Send result back to VLM insights widget
|
||||
if hasattr(self.config_panel, 'vlm_insights_widget'):
|
||||
print(f"[MAIN WINDOW DEBUG] Sending mock result to VLM insights widget")
|
||||
self.config_panel.vlm_insights_widget.on_analysis_result(result)
|
||||
print(f"[MAIN WINDOW DEBUG] Mock result sent successfully")
|
||||
else:
|
||||
print(f"[MAIN WINDOW DEBUG] VLM insights widget not found")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[VLM ERROR] Error in analysis: {e}")
|
||||
if hasattr(self.config_panel, 'vlm_insights_widget'):
|
||||
self.config_panel.vlm_insights_widget.on_analysis_result(f"Analysis error: {str(e)}")
|
||||
|
||||
def _handle_vlm_result(self, result):
|
||||
"""Handle VLM controller results."""
|
||||
print(f"[MAIN WINDOW DEBUG] _handle_vlm_result called")
|
||||
print(f"[MAIN WINDOW DEBUG] Result type: {type(result)}")
|
||||
|
||||
try:
|
||||
# Extract answer from result dict
|
||||
if isinstance(result, dict):
|
||||
if 'response' in result:
|
||||
answer = result['response']
|
||||
print(f"[MAIN WINDOW DEBUG] Extracted response: {len(str(answer))} characters")
|
||||
elif 'answer' in result:
|
||||
answer = result['answer']
|
||||
print(f"[MAIN WINDOW DEBUG] Extracted answer: {len(str(answer))} characters")
|
||||
else:
|
||||
answer = str(result)
|
||||
print(f"[MAIN WINDOW DEBUG] Using result as string: {len(answer)} characters")
|
||||
else:
|
||||
answer = str(result)
|
||||
print(f"[MAIN WINDOW DEBUG] Using result as string: {len(answer)} characters")
|
||||
|
||||
# Send result to VLM insights widget
|
||||
if hasattr(self.config_panel, 'vlm_insights_widget'):
|
||||
print(f"[MAIN WINDOW DEBUG] Sending VLM result to insights widget")
|
||||
self.config_panel.vlm_insights_widget.on_analysis_result(answer)
|
||||
print(f"[MAIN WINDOW DEBUG] VLM result sent successfully")
|
||||
else:
|
||||
print(f"[MAIN WINDOW DEBUG] VLM insights widget not found")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[VLM ERROR] Error handling VLM result: {e}")
|
||||
|
||||
def _forward_frame_to_vlm(self, frame, detections, fps):
|
||||
"""Forward frame to VLM insights widget."""
|
||||
print(f"[MAIN WINDOW DEBUG] _forward_frame_to_vlm called")
|
||||
print(f"[MAIN WINDOW DEBUG] Frame type: {type(frame)}, shape: {frame.shape if hasattr(frame, 'shape') else 'N/A'}")
|
||||
print(f"[MAIN WINDOW DEBUG] Detections count: {len(detections) if detections else 0}")
|
||||
print(f"[MAIN WINDOW DEBUG] FPS: {fps}")
|
||||
|
||||
# Publish detection events to InfluxDB
|
||||
if hasattr(self, 'data_publisher') and self.data_publisher and detections:
|
||||
try:
|
||||
# Count vehicles and pedestrians
|
||||
vehicle_count = 0
|
||||
pedestrian_count = 0
|
||||
|
||||
for detection in detections:
|
||||
label = ""
|
||||
if isinstance(detection, dict):
|
||||
label = detection.get('label', '').lower()
|
||||
elif hasattr(detection, 'label'):
|
||||
label = getattr(detection, 'label', '').lower()
|
||||
elif hasattr(detection, 'class_name'):
|
||||
label = getattr(detection, 'class_name', '').lower()
|
||||
elif hasattr(detection, 'cls'):
|
||||
label = str(getattr(detection, 'cls', '')).lower()
|
||||
|
||||
# Debug the label detection
|
||||
if label and label != 'traffic light':
|
||||
print(f"[PUBLISHER DEBUG] Detected object: {label}")
|
||||
|
||||
if label in ['car', 'truck', 'bus', 'motorcycle', 'vehicle']:
|
||||
vehicle_count += 1
|
||||
elif label in ['person', 'pedestrian']:
|
||||
pedestrian_count += 1
|
||||
|
||||
# Also try to get vehicle count from tracked vehicles if available
|
||||
if vehicle_count == 0 and hasattr(self, 'video_file_controller'):
|
||||
try:
|
||||
# Try to get vehicle count from current analysis data
|
||||
analysis_data = getattr(self.video_file_controller, 'get_current_analysis_data', lambda: {})()
|
||||
if isinstance(analysis_data, dict):
|
||||
tracked_vehicles = analysis_data.get('tracked_vehicles', [])
|
||||
if tracked_vehicles:
|
||||
vehicle_count = len(tracked_vehicles)
|
||||
print(f"[PUBLISHER DEBUG] Using tracked vehicle count: {vehicle_count}")
|
||||
except:
|
||||
pass
|
||||
|
||||
self.data_publisher.publish_detection_events(vehicle_count, pedestrian_count)
|
||||
except Exception as e:
|
||||
print(f"❌ Error publishing detection events: {e}")
|
||||
|
||||
try:
|
||||
if hasattr(self.config_panel, 'vlm_insights_widget'):
|
||||
print(f"[MAIN WINDOW DEBUG] Forwarding frame to VLM insights widget")
|
||||
self.config_panel.vlm_insights_widget.set_current_frame(frame)
|
||||
|
||||
# Store detection data for VLM analysis
|
||||
if hasattr(self.config_panel.vlm_insights_widget, 'set_detection_data'):
|
||||
print(f"[MAIN WINDOW DEBUG] Setting detection data for VLM")
|
||||
detection_data = {
|
||||
'detections': detections,
|
||||
'fps': fps,
|
||||
'timestamp': time.time()
|
||||
}
|
||||
# Get additional data from video controller if available
|
||||
if hasattr(self.video_file_controller, 'get_current_analysis_data'):
|
||||
analysis_data = self.video_file_controller.get_current_analysis_data()
|
||||
detection_data.update(analysis_data)
|
||||
|
||||
self.config_panel.vlm_insights_widget.set_detection_data(detection_data)
|
||||
print(f"[MAIN WINDOW DEBUG] Detection data set successfully")
|
||||
|
||||
print(f"[MAIN WINDOW DEBUG] Frame forwarded successfully")
|
||||
else:
|
||||
print(f"[MAIN WINDOW DEBUG] VLM insights widget not found for frame forwarding")
|
||||
except Exception as e:
|
||||
print(f"[MAIN WINDOW DEBUG] Error forwarding frame to VLM: {e}")
|
||||
|
||||
def _forward_frame_to_analytics(self, frame, detections, fps):
|
||||
"""Forward frame data to analytics tab for real-time updates."""
|
||||
try:
|
||||
print(f"[ANALYTICS DEBUG] Forwarding frame data to analytics tab")
|
||||
print(f"[ANALYTICS DEBUG] Detections count: {len(detections) if detections else 0}")
|
||||
|
||||
# Prepare detection data for analytics
|
||||
detection_data = {
|
||||
'detections': detections,
|
||||
'fps': fps,
|
||||
'timestamp': time.time(),
|
||||
'frame_shape': frame.shape if hasattr(frame, 'shape') else None
|
||||
}
|
||||
|
||||
# Get additional analysis data from video controller
|
||||
if hasattr(self.video_file_controller, 'get_current_analysis_data'):
|
||||
analysis_data = self.video_file_controller.get_current_analysis_data()
|
||||
if analysis_data:
|
||||
detection_data.update(analysis_data)
|
||||
print(f"[ANALYTICS DEBUG] Updated with analysis data: {list(analysis_data.keys())}")
|
||||
|
||||
# Forward to analytics tab
|
||||
if hasattr(self.analytics_tab, 'update_detection_data'):
|
||||
self.analytics_tab.update_detection_data(detection_data)
|
||||
print(f"[ANALYTICS DEBUG] Detection data forwarded to analytics tab successfully")
|
||||
else:
|
||||
print(f"[ANALYTICS DEBUG] Analytics tab update_detection_data method not found")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ANALYTICS DEBUG] Error forwarding frame to analytics: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _generate_mock_analysis(self, frame, prompt):
|
||||
"""Generate a mock analysis response based on frame content and prompt."""
|
||||
try:
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
# Analyze frame properties
|
||||
h, w = frame.shape[:2] if frame is not None else (0, 0)
|
||||
|
||||
# Basic image analysis
|
||||
analysis_parts = []
|
||||
|
||||
if "traffic" in prompt.lower():
|
||||
analysis_parts.append("🚦 Traffic Analysis:")
|
||||
analysis_parts.append(f"• Frame resolution: {w}x{h}")
|
||||
analysis_parts.append("• Detected scene: Urban traffic intersection")
|
||||
analysis_parts.append("• Visible elements: Road, potential vehicles")
|
||||
analysis_parts.append("• Traffic flow appears to be moderate")
|
||||
|
||||
elif "safety" in prompt.lower():
|
||||
analysis_parts.append("⚠️ Safety Assessment:")
|
||||
analysis_parts.append("• Monitoring for traffic violations")
|
||||
analysis_parts.append("• Checking lane discipline")
|
||||
analysis_parts.append("• Observing traffic light compliance")
|
||||
analysis_parts.append("• Overall safety level: Monitoring required")
|
||||
|
||||
else:
|
||||
analysis_parts.append("🔍 General Analysis:")
|
||||
analysis_parts.append(f"• Image dimensions: {w}x{h} pixels")
|
||||
analysis_parts.append("• Scene type: Traffic monitoring view")
|
||||
analysis_parts.append("• Quality: Processing frame for analysis")
|
||||
analysis_parts.append(f"• Prompt: {prompt[:100]}...")
|
||||
|
||||
# Add timestamp and disclaimer
|
||||
from datetime import datetime
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
analysis_parts.append(f"\n📝 Analysis completed at {timestamp}")
|
||||
analysis_parts.append("ℹ️ Note: This is a mock analysis. Full AI analysis requires compatible OpenVINO setup.")
|
||||
|
||||
return "\n".join(analysis_parts)
|
||||
|
||||
except Exception as e:
|
||||
return f"Unable to analyze frame: {str(e)}"
|
||||
|
||||
# Smart Intersection Signal Handlers
|
||||
@Slot(bool)
|
||||
def _handle_smart_intersection_enabled(self, enabled):
|
||||
"""Handle smart intersection mode toggle"""
|
||||
print(f"🚦 Smart Intersection mode {'enabled' if enabled else 'disabled'}")
|
||||
|
||||
if self.smart_intersection_controller:
|
||||
self.smart_intersection_controller.set_enabled(enabled)
|
||||
|
||||
# Update status
|
||||
if enabled:
|
||||
self.statusBar().showMessage("Smart Intersection mode activated")
|
||||
else:
|
||||
self.statusBar().showMessage("Standard detection mode")
|
||||
|
||||
@Slot(bool)
|
||||
def _handle_multi_camera_mode(self, enabled):
|
||||
"""Handle multi-camera mode toggle"""
|
||||
print(f"📹 Multi-camera mode {'enabled' if enabled else 'disabled'}")
|
||||
|
||||
if self.smart_intersection_controller:
|
||||
self.smart_intersection_controller.set_multi_camera_mode(enabled)
|
||||
|
||||
@Slot(dict)
|
||||
def _handle_roi_configuration_changed(self, roi_config):
|
||||
"""Handle ROI configuration changes"""
|
||||
print(f"🎯 ROI configuration updated: {len(roi_config.get('rois', []))} regions")
|
||||
|
||||
if self.smart_intersection_controller:
|
||||
self.smart_intersection_controller.update_roi_config(roi_config)
|
||||
|
||||
@Slot(bool)
|
||||
def _handle_scene_analytics_toggle(self, enabled):
|
||||
"""Handle scene analytics toggle"""
|
||||
print(f"📊 Scene analytics {'enabled' if enabled else 'disabled'}")
|
||||
|
||||
if self.smart_intersection_controller:
|
||||
self.smart_intersection_controller.set_scene_analytics(enabled)
|
||||
|
||||
@Slot(dict)
|
||||
def _handle_scene_analytics_update(self, analytics_data):
|
||||
"""Handle scene analytics data updates"""
|
||||
try:
|
||||
# Update video detection tab with smart intersection data
|
||||
smart_stats = {
|
||||
'total_objects': analytics_data.get('total_objects', 0),
|
||||
'active_tracks': analytics_data.get('active_tracks', 0),
|
||||
'roi_events': analytics_data.get('roi_events', 0),
|
||||
'crosswalk_events': analytics_data.get('crosswalk_events', 0),
|
||||
'lane_events': analytics_data.get('lane_events', 0),
|
||||
'safety_events': analytics_data.get('safety_events', 0),
|
||||
'north_objects': analytics_data.get('camera_stats', {}).get('north', 0),
|
||||
'east_objects': analytics_data.get('camera_stats', {}).get('east', 0),
|
||||
'south_objects': analytics_data.get('camera_stats', {}).get('south', 0),
|
||||
'west_objects': analytics_data.get('camera_stats', {}).get('west', 0),
|
||||
'fps': analytics_data.get('fps', 0),
|
||||
'processing_time': analytics_data.get('processing_time_ms', 0),
|
||||
'gpu_usage': analytics_data.get('gpu_usage', 0),
|
||||
'memory_usage': analytics_data.get('memory_usage', 0)
|
||||
}
|
||||
|
||||
# Update both video tabs with stats
|
||||
self.video_detection_only_tab.update_stats(smart_stats)
|
||||
self.smart_intersection_tab.update_stats(smart_stats)
|
||||
|
||||
# Update analytics tab if it has smart intersection support
|
||||
if hasattr(self.analytics_tab, 'update_smart_intersection_analytics'):
|
||||
self.analytics_tab.update_smart_intersection_analytics(analytics_data)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error handling scene analytics update: {e}")
|
||||
|
||||
@@ -5,16 +5,25 @@ Shows when latency spikes occur with different resolutions and devices
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QGroupBox, QTabWidget, QFrame, QSplitter
|
||||
QGroupBox, QTabWidget, QFrame, QSplitter, QScrollArea
|
||||
)
|
||||
from PySide6.QtCore import Qt, QTimer, Signal, Slot
|
||||
from PySide6.QtGui import QPainter, QPen, QBrush, QColor, QFont
|
||||
from PySide6.QtGui import QPainter, QPen, QBrush, QColor, QFont, QLinearGradient
|
||||
import numpy as np
|
||||
from collections import deque
|
||||
from typing import Dict, List, Any
|
||||
import time
|
||||
|
||||
# Try to import psutil for system monitoring, use fallback if not available
|
||||
try:
|
||||
import psutil
|
||||
PSUTIL_AVAILABLE = True
|
||||
except ImportError:
|
||||
PSUTIL_AVAILABLE = False
|
||||
print("⚠️ psutil not available - system monitoring will use fallback values")
|
||||
|
||||
class RealTimeGraph(QWidget):
|
||||
"""Custom widget for drawing real-time graphs"""
|
||||
"""Custom widget for drawing real-time graphs with enhanced styling"""
|
||||
|
||||
def __init__(self, title: str = "Graph", y_label: str = "Value", max_points: int = 300):
|
||||
super().__init__()
|
||||
@@ -29,20 +38,75 @@ class RealTimeGraph(QWidget):
|
||||
self.device_markers = deque(maxlen=max_points) # Mark device changes
|
||||
self.resolution_markers = deque(maxlen=max_points) # Mark resolution changes
|
||||
|
||||
# Graph settings
|
||||
self.margin = 40
|
||||
self.grid_color = QColor(60, 60, 60)
|
||||
self.line_color = QColor(0, 255, 255) # Cyan
|
||||
self.spike_color = QColor(255, 0, 0) # Red for spikes
|
||||
self.cpu_color = QColor(100, 150, 255) # Blue for CPU
|
||||
self.gpu_color = QColor(255, 150, 100) # Orange for GPU
|
||||
# Enhanced styling colors
|
||||
self.bg_color = QColor(18, 18, 18) # Very dark background
|
||||
self.grid_color = QColor(40, 40, 45) # Subtle grid
|
||||
self.line_color = QColor(0, 230, 255) # Bright cyan
|
||||
self.spike_color = QColor(255, 77, 77) # Bright red for spikes
|
||||
self.cpu_color = QColor(120, 180, 255) # Light blue for CPU
|
||||
self.gpu_color = QColor(255, 165, 0) # Orange for GPU
|
||||
self.text_color = QColor(220, 220, 220) # Light gray text
|
||||
self.accent_color = QColor(255, 215, 0) # Gold accent
|
||||
|
||||
# Auto-scaling
|
||||
self.y_min = 0
|
||||
self.y_max = 100
|
||||
self.auto_scale = True
|
||||
|
||||
# Performance counters
|
||||
self.spike_count = 0
|
||||
self.device_switches = 0
|
||||
self.resolution_changes = 0
|
||||
|
||||
self.setMinimumSize(400, 200)
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: #121212;
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 8px;
|
||||
}
|
||||
""")
|
||||
|
||||
def add_data_point(self, x: float, y: float, is_spike: bool = False, device: str = "CPU", is_res_change: bool = False):
|
||||
"""Add a new data point to the graph"""
|
||||
self.x_data.append(x)
|
||||
self.y_data.append(y)
|
||||
self.spike_markers.append(is_spike)
|
||||
self.device_markers.append(device)
|
||||
self.resolution_markers.append(is_res_change)
|
||||
|
||||
# Update counters
|
||||
if is_spike:
|
||||
self.spike_count += 1
|
||||
if len(self.device_markers) > 1 and device != list(self.device_markers)[-2]:
|
||||
self.device_switches += 1
|
||||
if is_res_change:
|
||||
self.resolution_changes += 1
|
||||
|
||||
# Auto-scale Y axis with better algorithm
|
||||
if self.auto_scale and self.y_data:
|
||||
data_max = max(self.y_data)
|
||||
data_min = min(self.y_data)
|
||||
if data_max > data_min:
|
||||
padding = (data_max - data_min) * 0.15
|
||||
self.y_max = data_max + padding
|
||||
self.y_min = max(0, data_min - padding * 0.5)
|
||||
else:
|
||||
self.y_max = data_max + 10 if data_max > 0 else 100
|
||||
self.y_min = 0
|
||||
self.update()
|
||||
|
||||
def clear_data(self):
|
||||
"""Clear the graph data"""
|
||||
self.x_data.clear()
|
||||
self.y_data.clear()
|
||||
self.spike_markers.clear()
|
||||
self.device_markers.clear()
|
||||
self.resolution_markers.clear()
|
||||
self.spike_count = 0
|
||||
self.device_switches = 0
|
||||
self.resolution_changes = 0
|
||||
self.update()
|
||||
|
||||
def add_data_point(self, x: float, y: float, is_spike: bool = False, device: str = "CPU", is_res_change: bool = False):
|
||||
"""Add a new data point to the graph"""
|
||||
@@ -71,133 +135,479 @@ class RealTimeGraph(QWidget):
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""Override paint event to draw the graph"""
|
||||
"""Override paint event to draw the graph with enhanced styling"""
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
width = self.width()
|
||||
height = self.height()
|
||||
graph_width = width - 2 * self.margin
|
||||
graph_height = height - 2 * self.margin
|
||||
margin = 50
|
||||
graph_width = width - 2 * margin
|
||||
graph_height = height - 2 * margin
|
||||
|
||||
# Background
|
||||
painter.fillRect(self.rect(), QColor(30, 30, 30))
|
||||
# Enhanced background with subtle gradient
|
||||
gradient = QLinearGradient(0, 0, 0, height)
|
||||
gradient.setColorAt(0, QColor(25, 25, 30))
|
||||
gradient.setColorAt(1, QColor(15, 15, 20))
|
||||
painter.fillRect(self.rect(), QBrush(gradient))
|
||||
|
||||
# Title
|
||||
painter.setPen(QColor(255, 255, 255))
|
||||
painter.setFont(QFont("Arial", 12, QFont.Bold))
|
||||
painter.drawText(10, 20, self.title)
|
||||
# Title with glow effect
|
||||
painter.setPen(self.accent_color)
|
||||
painter.setFont(QFont("Segoe UI", 13, QFont.Bold))
|
||||
title_rect = painter.fontMetrics().boundingRect(self.title)
|
||||
painter.drawText(15, 25, self.title)
|
||||
|
||||
# Axes
|
||||
painter.setPen(QPen(QColor(200, 200, 200), 2))
|
||||
painter.drawLine(self.margin, self.margin, self.margin, height - self.margin)
|
||||
painter.drawLine(self.margin, height - self.margin, width - self.margin, height - self.margin)
|
||||
# Enhanced axes with better styling
|
||||
painter.setPen(QPen(self.text_color, 2))
|
||||
painter.drawLine(margin, margin, margin, height - margin) # Y-axis
|
||||
painter.drawLine(margin, height - margin, width - margin, height - margin) # X-axis
|
||||
|
||||
# Grid
|
||||
painter.setPen(QPen(self.grid_color, 1))
|
||||
for i in range(5):
|
||||
y = self.margin + (graph_height * i / 4)
|
||||
painter.drawLine(self.margin, y, width - self.margin, y)
|
||||
for i in range(10):
|
||||
x = self.margin + (graph_width * i / 9)
|
||||
painter.drawLine(x, self.margin, x, height - self.margin)
|
||||
# Enhanced grid with subtle styling
|
||||
painter.setPen(QPen(self.grid_color, 1, Qt.DotLine))
|
||||
# Horizontal grid lines
|
||||
for i in range(1, 5):
|
||||
y = margin + (graph_height * i / 4)
|
||||
painter.drawLine(margin + 5, y, width - margin - 5, y)
|
||||
# Vertical grid lines
|
||||
for i in range(1, 10):
|
||||
x = margin + (graph_width * i / 9)
|
||||
painter.drawLine(x, margin + 5, x, height - margin - 5)
|
||||
|
||||
# Y-axis labels
|
||||
painter.setPen(QColor(200, 200, 200))
|
||||
painter.setFont(QFont("Arial", 8))
|
||||
# Enhanced Y-axis labels with better formatting
|
||||
painter.setPen(self.text_color)
|
||||
painter.setFont(QFont("Segoe UI", 9))
|
||||
for i in range(5):
|
||||
y_val = self.y_min + (self.y_max - self.y_min) * (4 - i) / 4
|
||||
y_pos = self.margin + (graph_height * i / 4)
|
||||
painter.drawText(5, y_pos + 5, f"{y_val:.1f}")
|
||||
y_pos = margin + (graph_height * i / 4)
|
||||
if y_val >= 1000:
|
||||
label = f"{y_val/1000:.1f}k"
|
||||
elif y_val >= 1:
|
||||
label = f"{y_val:.1f}"
|
||||
else:
|
||||
label = f"{y_val:.2f}"
|
||||
painter.drawText(5, y_pos + 4, label)
|
||||
|
||||
# X-axis label
|
||||
# Enhanced Y-axis label with rotation
|
||||
painter.save()
|
||||
painter.translate(15, height // 2)
|
||||
painter.setPen(self.text_color)
|
||||
painter.setFont(QFont("Segoe UI", 10))
|
||||
painter.translate(20, height // 2)
|
||||
painter.rotate(-90)
|
||||
painter.drawText(-len(self.y_label) * 3, 0, self.y_label)
|
||||
painter.drawText(-len(self.y_label) * 4, 0, self.y_label)
|
||||
painter.restore()
|
||||
|
||||
# Data points
|
||||
# Enhanced data visualization
|
||||
if len(self.x_data) >= 2 and len(self.y_data) >= 2:
|
||||
points = []
|
||||
spike_points = []
|
||||
device_changes = []
|
||||
res_changes = []
|
||||
|
||||
x_min = min(self.x_data) if self.x_data else 0
|
||||
x_max = max(self.x_data) if self.x_data else 1
|
||||
x_range = x_max - x_min if x_max > x_min else 1
|
||||
|
||||
# Prepare point coordinates
|
||||
for i, (x_val, y_val, is_spike, device, is_res_change) in enumerate(zip(
|
||||
self.x_data, self.y_data, self.spike_markers, self.device_markers, self.resolution_markers
|
||||
)):
|
||||
x_screen = self.margin + (x_val - x_min) / x_range * graph_width
|
||||
y_screen = height - self.margin - (y_val - self.y_min) / (self.y_max - self.y_min) * graph_height
|
||||
x_screen = margin + (x_val - x_min) / x_range * graph_width
|
||||
y_screen = height - margin - (y_val - self.y_min) / (self.y_max - self.y_min) * graph_height
|
||||
points.append((x_screen, y_screen))
|
||||
|
||||
if is_spike:
|
||||
spike_points.append((x_screen, y_screen))
|
||||
if i > 0 and device != list(self.device_markers)[i-1]:
|
||||
device_changes.append((x_screen, y_screen, device))
|
||||
if is_res_change:
|
||||
res_changes.append((x_screen, y_screen))
|
||||
|
||||
# Draw main line with enhanced styling
|
||||
if len(points) >= 2:
|
||||
painter.setPen(QPen(self.line_color, 2))
|
||||
painter.setPen(QPen(self.line_color, 3))
|
||||
for i in range(len(points) - 1):
|
||||
x1, y1 = points[i]
|
||||
x2, y2 = points[i + 1]
|
||||
painter.drawLine(x1, y1, x2, y2)
|
||||
painter.setPen(QPen(self.spike_color, 3))
|
||||
|
||||
# Add subtle glow effect to the line
|
||||
painter.setPen(QPen(QColor(self.line_color.red(), self.line_color.green(), self.line_color.blue(), 60), 6))
|
||||
for i in range(len(points) - 1):
|
||||
x1, y1 = points[i]
|
||||
x2, y2 = points[i + 1]
|
||||
painter.drawLine(x1, y1, x2, y2)
|
||||
|
||||
# Enhanced spike markers
|
||||
painter.setPen(QPen(self.spike_color, 2))
|
||||
painter.setBrush(QBrush(self.spike_color))
|
||||
for x, y in spike_points:
|
||||
painter.drawEllipse(x - 3, y - 3, 6, 6)
|
||||
painter.drawEllipse(x - 4, y - 4, 8, 8)
|
||||
# Add spike indicator line
|
||||
painter.drawLine(x, y - 10, x, y + 10)
|
||||
|
||||
# Enhanced device change indicators
|
||||
for x, y, device in device_changes:
|
||||
color = self.gpu_color if device == "GPU" else self.cpu_color
|
||||
painter.setPen(QPen(color, 2))
|
||||
painter.setBrush(QBrush(color))
|
||||
painter.drawRect(x - 2, self.margin, 4, graph_height)
|
||||
painter.setPen(QPen(color, 3))
|
||||
painter.setBrush(QBrush(QColor(color.red(), color.green(), color.blue(), 100)))
|
||||
painter.drawRect(x - 3, margin, 6, graph_height)
|
||||
|
||||
# Add device label
|
||||
painter.setPen(color)
|
||||
painter.setFont(QFont("Segoe UI", 8, QFont.Bold))
|
||||
painter.drawText(x - 10, margin - 5, device)
|
||||
|
||||
# Enhanced resolution change indicators
|
||||
for x, y in res_changes:
|
||||
painter.setPen(QPen(QColor(255, 167, 38), 2)) # Orange for resolution change
|
||||
painter.drawLine(x, self.margin, x, height - self.margin)
|
||||
painter.setPen(QPen(QColor(255, 193, 7), 2)) # Amber color
|
||||
painter.drawLine(x, margin, x, height - margin)
|
||||
|
||||
# Add resolution change marker
|
||||
painter.setBrush(QBrush(QColor(255, 193, 7)))
|
||||
painter.drawEllipse(x - 3, margin - 5, 6, 6)
|
||||
|
||||
class PerformanceGraphsWidget(QWidget):
|
||||
"""Enhanced performance graphs widget with real-time data visualization"""
|
||||
|
||||
# Define signals for better integration
|
||||
performance_data_updated = Signal(dict)
|
||||
spike_detected = Signal(dict)
|
||||
device_switched = Signal(str)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setup_ui()
|
||||
|
||||
# Enhanced timer setup
|
||||
self.update_timer = QTimer()
|
||||
self.update_timer.timeout.connect(self.update_graphs)
|
||||
self.system_timer = QTimer()
|
||||
self.system_timer.timeout.connect(self.update_system_metrics)
|
||||
|
||||
try:
|
||||
self.update_timer.start(1000)
|
||||
self.update_timer.start(500) # Update graphs every 500ms for smoother animation
|
||||
self.system_timer.start(1000) # Update system metrics every second
|
||||
except Exception as e:
|
||||
print(f"❌ Error starting performance graph timer: {e}")
|
||||
self.start_time = None
|
||||
print(f"❌ Error starting performance graph timers: {e}")
|
||||
|
||||
# Enhanced data tracking
|
||||
self.start_time = time.time() if time else None
|
||||
self.latest_data = {}
|
||||
self.cpu_usage_history = deque(maxlen=300)
|
||||
self.ram_usage_history = deque(maxlen=300) # Add missing ram_usage_history
|
||||
self.frame_counter = 0
|
||||
self.spike_threshold = 100.0 # Default spike threshold in ms
|
||||
self.previous_device = "CPU" # Track device changes
|
||||
|
||||
# Performance statistics
|
||||
self.latency_stats = {
|
||||
'avg': 0.0,
|
||||
'max': 0.0,
|
||||
'min': float('inf'),
|
||||
'spike_count': 0
|
||||
}
|
||||
|
||||
def __del__(self):
|
||||
"""Clean up timers when widget is destroyed"""
|
||||
try:
|
||||
if hasattr(self, 'system_timer') and self.system_timer:
|
||||
self.system_timer.stop()
|
||||
self.system_timer.deleteLater()
|
||||
if hasattr(self, 'update_timer') and self.update_timer:
|
||||
self.update_timer.stop()
|
||||
self.update_timer.deleteLater()
|
||||
except:
|
||||
pass
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Handle widget close event"""
|
||||
try:
|
||||
if hasattr(self, 'system_timer') and self.system_timer:
|
||||
self.system_timer.stop()
|
||||
if hasattr(self, 'update_timer') and self.update_timer:
|
||||
self.update_timer.stop()
|
||||
except:
|
||||
pass
|
||||
super().closeEvent(event)
|
||||
self.ram_usage_history = deque(maxlen=300)
|
||||
self.spike_threshold = 100 # ms threshold for latency spikes
|
||||
self.previous_device = "CPU"
|
||||
self.frame_counter = 0
|
||||
|
||||
# Performance statistics
|
||||
self.latency_stats = {
|
||||
'avg': 0.0,
|
||||
'max': 0.0,
|
||||
'min': float('inf'),
|
||||
'spike_count': 0
|
||||
}
|
||||
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: #121212;
|
||||
color: #ffffff;
|
||||
}
|
||||
QLabel {
|
||||
color: #ffffff;
|
||||
background: transparent;
|
||||
}
|
||||
QFrame {
|
||||
background-color: #1a1a1a;
|
||||
border: 1px solid #333333;
|
||||
border-radius: 8px;
|
||||
margin: 2px;
|
||||
}
|
||||
""")
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
# Create main layout
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(5, 5, 5, 5)
|
||||
main_layout.setSpacing(0)
|
||||
|
||||
# Create scroll area
|
||||
scroll_area = QScrollArea()
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
scroll_area.setStyleSheet("""
|
||||
QScrollArea {
|
||||
border: none;
|
||||
background-color: #121212;
|
||||
}
|
||||
QScrollBar:vertical {
|
||||
background-color: #2C2C2C;
|
||||
width: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
QScrollBar::handle:vertical {
|
||||
background-color: #555555;
|
||||
border-radius: 6px;
|
||||
min-height: 20px;
|
||||
}
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background-color: #777777;
|
||||
}
|
||||
QScrollBar:horizontal {
|
||||
background-color: #2C2C2C;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
QScrollBar::handle:horizontal {
|
||||
background-color: #555555;
|
||||
border-radius: 6px;
|
||||
min-width: 20px;
|
||||
}
|
||||
QScrollBar::handle:horizontal:hover {
|
||||
background-color: #777777;
|
||||
}
|
||||
""")
|
||||
|
||||
# Create scrollable content widget
|
||||
content_widget = QWidget()
|
||||
content_layout = QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(10, 10, 10, 10)
|
||||
content_layout.setSpacing(8)
|
||||
|
||||
# Enhanced title section
|
||||
title_frame = QFrame()
|
||||
title_layout = QVBoxLayout(title_frame)
|
||||
|
||||
title_label = QLabel("🔥 Real-Time Inference Performance & Latency Spike Analysis")
|
||||
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #FFD700; margin: 10px;")
|
||||
layout.addWidget(title_label)
|
||||
title_label.setStyleSheet("""
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #FFD700;
|
||||
margin: 8px;
|
||||
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
||||
stop:0 #FFD700, stop:1 #FFA500);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
""")
|
||||
title_layout.addWidget(title_label)
|
||||
|
||||
# Enhanced system stats
|
||||
stats_layout = QHBoxLayout()
|
||||
|
||||
self.cpu_ram_stats = QLabel("CPU: 0% | RAM: 0%")
|
||||
self.cpu_ram_stats.setStyleSheet("color: #FFD700; font-weight: bold; font-size: 14px; margin: 8px;")
|
||||
layout.addWidget(self.cpu_ram_stats)
|
||||
self.cpu_ram_stats.setStyleSheet("""
|
||||
color: #00FFFF;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
margin: 4px 8px;
|
||||
padding: 4px 8px;
|
||||
background-color: rgba(0, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
""")
|
||||
stats_layout.addWidget(self.cpu_ram_stats)
|
||||
|
||||
# Add current model display
|
||||
self.current_model_stats = QLabel("Model: Loading...")
|
||||
self.current_model_stats.setStyleSheet("""
|
||||
color: #FFD700;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
margin: 4px 8px;
|
||||
padding: 4px 8px;
|
||||
background-color: rgba(255, 215, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
""")
|
||||
stats_layout.addWidget(self.current_model_stats)
|
||||
|
||||
title_layout.addLayout(stats_layout)
|
||||
|
||||
title_frame.setLayout(title_layout)
|
||||
content_layout.addWidget(title_frame)
|
||||
|
||||
# Enhanced splitter for graphs - set minimum sizes to avoid cluttering
|
||||
splitter = QSplitter(Qt.Vertical)
|
||||
# Latency graph
|
||||
splitter.setStyleSheet("""
|
||||
QSplitter::handle {
|
||||
background-color: #333333;
|
||||
height: 3px;
|
||||
}
|
||||
QSplitter::handle:hover {
|
||||
background-color: #555555;
|
||||
}
|
||||
""")
|
||||
|
||||
# Enhanced Latency graph
|
||||
latency_frame = QFrame()
|
||||
latency_frame.setMinimumHeight(250) # Set minimum height to prevent cluttering
|
||||
latency_frame.setStyleSheet("""
|
||||
QFrame {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(30, 30, 35, 255),
|
||||
stop:1 rgba(20, 20, 25, 255));
|
||||
border: 2px solid #00FFFF;
|
||||
border-radius: 10px;
|
||||
}
|
||||
""")
|
||||
latency_layout = QVBoxLayout(latency_frame)
|
||||
|
||||
self.latency_graph = RealTimeGraph(
|
||||
"Inference Latency Over Time",
|
||||
"Latency (ms)",
|
||||
max_points=300
|
||||
)
|
||||
self.latency_graph.setMinimumHeight(200) # Ensure minimum display height
|
||||
latency_layout.addWidget(self.latency_graph)
|
||||
|
||||
latency_info = QHBoxLayout()
|
||||
self.latency_stats = QLabel("Avg: 0ms | Max: 0ms | Spikes: 0")
|
||||
self.latency_stats.setStyleSheet("color: #00FFFF; font-weight: bold;")
|
||||
latency_info.addWidget(self.latency_stats)
|
||||
self.latency_stats_label = QLabel("Avg: 0ms | Max: 0ms | Spikes: 0")
|
||||
self.latency_stats_label.setStyleSheet("""
|
||||
color: #00FFFF;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
background-color: rgba(0, 255, 255, 0.15);
|
||||
border-radius: 4px;
|
||||
margin: 4px;
|
||||
""")
|
||||
latency_info.addWidget(self.latency_stats_label)
|
||||
latency_info.addStretch()
|
||||
latency_layout.addLayout(latency_info)
|
||||
|
||||
latency_frame.setLayout(latency_layout)
|
||||
splitter.addWidget(latency_frame)
|
||||
# FPS graph
|
||||
|
||||
# Enhanced FPS graph
|
||||
fps_frame = QFrame()
|
||||
fps_frame.setMinimumHeight(250) # Set minimum height to prevent cluttering
|
||||
fps_frame.setStyleSheet("""
|
||||
QFrame {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(30, 35, 30, 255),
|
||||
stop:1 rgba(20, 25, 20, 255));
|
||||
border: 2px solid #00FF00;
|
||||
border-radius: 10px;
|
||||
}
|
||||
""")
|
||||
fps_layout = QVBoxLayout(fps_frame)
|
||||
|
||||
self.fps_graph = RealTimeGraph(
|
||||
"FPS & Resolution Impact",
|
||||
"FPS",
|
||||
max_points=300
|
||||
)
|
||||
self.fps_graph.setMinimumHeight(200) # Ensure minimum display height
|
||||
fps_layout.addWidget(self.fps_graph)
|
||||
|
||||
fps_info = QHBoxLayout()
|
||||
self.fps_stats = QLabel("Current FPS: 0 | Resolution: - | Device: -")
|
||||
self.fps_stats.setStyleSheet("""
|
||||
color: #00FF00;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
background-color: rgba(0, 255, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
margin: 4px;
|
||||
""")
|
||||
fps_info.addWidget(self.fps_stats)
|
||||
fps_info.addStretch()
|
||||
fps_layout.addLayout(fps_info)
|
||||
|
||||
fps_frame.setLayout(fps_layout)
|
||||
splitter.addWidget(fps_frame)
|
||||
|
||||
# Enhanced Device switching & resolution changes graph
|
||||
device_frame = QFrame()
|
||||
device_frame.setMinimumHeight(220) # Set minimum height to prevent cluttering
|
||||
device_frame.setStyleSheet("""
|
||||
QFrame {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 rgba(35, 30, 30, 255),
|
||||
stop:1 rgba(25, 20, 20, 255));
|
||||
border: 2px solid #FFB300;
|
||||
border-radius: 10px;
|
||||
}
|
||||
""")
|
||||
device_layout = QVBoxLayout(device_frame)
|
||||
|
||||
self.device_graph = RealTimeGraph(
|
||||
"Device Switching & Resolution Changes",
|
||||
"Events",
|
||||
max_points=300
|
||||
)
|
||||
self.device_graph.setMinimumHeight(170) # Ensure minimum display height
|
||||
device_layout.addWidget(self.device_graph)
|
||||
|
||||
self.device_legend = QLabel(
|
||||
"<span style='color:#FF4444;'>●</span> CPU Spikes: 0 | "
|
||||
"<span style='color:#FFA500;'>●</span> GPU Spikes: 0 | "
|
||||
"<span style='color:#78B4FF;'>●</span> Switches: 0 | "
|
||||
"<span style='color:#FFC107;'>●</span> Res Changes: 0"
|
||||
)
|
||||
self.device_legend.setStyleSheet("""
|
||||
color: #FFB300;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin: 4px 8px;
|
||||
padding: 4px 8px;
|
||||
background-color: rgba(255, 179, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
""")
|
||||
device_layout.addWidget(self.device_legend)
|
||||
|
||||
device_frame.setLayout(device_layout)
|
||||
splitter.addWidget(device_frame)
|
||||
|
||||
# Set splitter proportions with minimum space for each section
|
||||
splitter.setSizes([300, 300, 250]) # Increased minimum sizes
|
||||
splitter.setChildrenCollapsible(False) # Prevent collapsing sections
|
||||
|
||||
content_layout.addWidget(splitter)
|
||||
content_widget.setLayout(content_layout)
|
||||
|
||||
# Set minimum size for content widget to ensure scrolling when needed
|
||||
content_widget.setMinimumSize(400, 850) # Minimum width and height
|
||||
|
||||
# Add content widget to scroll area
|
||||
scroll_area.setWidget(content_widget)
|
||||
|
||||
# Add scroll area to main layout
|
||||
main_layout.addWidget(scroll_area)
|
||||
self.setLayout(main_layout)
|
||||
fps_layout = QVBoxLayout(fps_frame)
|
||||
self.fps_graph = RealTimeGraph(
|
||||
"FPS & Resolution Impact",
|
||||
@@ -222,33 +632,267 @@ class PerformanceGraphsWidget(QWidget):
|
||||
max_points=300
|
||||
)
|
||||
device_layout.addWidget(self.device_graph)
|
||||
self.device_legend = QLabel("<span style='color:#ff4444;'>CPU Spikes</span>: 0 | <span style='color:#ff5722;'>GPU Spikes</span>: 0 | <span style='color:#2196f3;'>Switches</span>: 0 | <span style='color:#ffa726;'>Res Changes</span>: 0")
|
||||
self.device_legend.setStyleSheet("color: #ffb300; font-size: 13px; font-weight: bold; margin: 2px 0 0 8px;")
|
||||
device_layout.addWidget(self.device_legend)
|
||||
device_frame.setLayout(device_layout)
|
||||
splitter.addWidget(device_frame)
|
||||
layout.addWidget(splitter)
|
||||
self.setLayout(layout)
|
||||
# Add scroll area to main layout
|
||||
main_layout.addWidget(scroll_area)
|
||||
self.setLayout(main_layout)
|
||||
|
||||
@Slot()
|
||||
def update_system_metrics(self):
|
||||
"""Update system CPU and RAM usage"""
|
||||
try:
|
||||
# Check if the widget is still valid and not being destroyed
|
||||
if not self or not hasattr(self, 'isVisible') or not self.isVisible():
|
||||
return
|
||||
|
||||
# Check if widgets still exist before updating
|
||||
if not hasattr(self, 'cpu_ram_stats') or not self.cpu_ram_stats:
|
||||
return
|
||||
if not hasattr(self, 'device_graph') or not self.device_graph:
|
||||
return
|
||||
|
||||
# Check if the RealTimeGraph objects are still valid
|
||||
try:
|
||||
if hasattr(self.device_graph, 'add_data_point'):
|
||||
# Test if the object is still valid by accessing a simple property
|
||||
_ = self.device_graph.objectName()
|
||||
else:
|
||||
return
|
||||
except RuntimeError:
|
||||
# Object has been deleted
|
||||
return
|
||||
|
||||
if PSUTIL_AVAILABLE:
|
||||
cpu_percent = psutil.cpu_percent(interval=None)
|
||||
memory = psutil.virtual_memory()
|
||||
ram_percent = memory.percent
|
||||
else:
|
||||
# Fallback values when psutil is not available
|
||||
cpu_percent = 0.0
|
||||
ram_percent = 0.0
|
||||
|
||||
if hasattr(self, 'cpu_usage_history'):
|
||||
self.cpu_usage_history.append(cpu_percent)
|
||||
if hasattr(self, 'ram_usage_history'):
|
||||
self.ram_usage_history.append(ram_percent)
|
||||
|
||||
# Update display
|
||||
try:
|
||||
if PSUTIL_AVAILABLE:
|
||||
self.cpu_ram_stats.setText(f"CPU: {cpu_percent:.1f}% | RAM: {ram_percent:.1f}%")
|
||||
else:
|
||||
self.cpu_ram_stats.setText("CPU: -- | RAM: -- (monitoring unavailable)")
|
||||
except RuntimeError:
|
||||
# Widget has been deleted
|
||||
return
|
||||
|
||||
# Add CPU usage to device graph as background metric
|
||||
try:
|
||||
current_time = time.time() - self.start_time if self.start_time else 0
|
||||
self.device_graph.add_data_point(current_time, cpu_percent, device="System")
|
||||
except RuntimeError:
|
||||
# Graph has been deleted
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error updating system metrics: {e}")
|
||||
# Fallback in case of any error
|
||||
try:
|
||||
if hasattr(self, 'cpu_ram_stats') and self.cpu_ram_stats:
|
||||
self.cpu_ram_stats.setText("CPU: -- | RAM: -- (error)")
|
||||
except:
|
||||
pass
|
||||
|
||||
@Slot()
|
||||
def update_graphs(self):
|
||||
# Placeholder for updating graphs with new data
|
||||
pass
|
||||
"""Update graphs with latest data"""
|
||||
if not self.latest_data:
|
||||
return
|
||||
|
||||
try:
|
||||
chart_data = self.latest_data.get('chart_data', {})
|
||||
latency_stats = self.latest_data.get('latency_stats', {})
|
||||
current_metrics = self.latest_data.get('current_metrics', {})
|
||||
|
||||
if not chart_data.get('timestamps'):
|
||||
return
|
||||
|
||||
# Get the latest data point
|
||||
timestamps = chart_data.get('timestamps', [])
|
||||
if not timestamps:
|
||||
return
|
||||
|
||||
latest_timestamp = timestamps[-1]
|
||||
current_time = time.time() - self.start_time if self.start_time else latest_timestamp
|
||||
|
||||
# Update latency graph
|
||||
if 'inference_latency' in chart_data:
|
||||
latency_values = chart_data['inference_latency']
|
||||
if latency_values:
|
||||
latest_latency = latency_values[-1]
|
||||
is_spike = latest_latency > self.spike_threshold
|
||||
device = current_metrics.get('device', 'CPU')
|
||||
|
||||
self.latency_graph.add_data_point(
|
||||
current_time,
|
||||
latest_latency,
|
||||
is_spike=is_spike,
|
||||
device=device
|
||||
)
|
||||
|
||||
# Update latency statistics
|
||||
self.latency_stats['max'] = max(self.latency_stats['max'], latest_latency)
|
||||
self.latency_stats['min'] = min(self.latency_stats['min'], latest_latency)
|
||||
if is_spike:
|
||||
self.latency_stats['spike_count'] += 1
|
||||
# Emit spike signal
|
||||
self.spike_detected.emit({
|
||||
'latency': latest_latency,
|
||||
'timestamp': current_time,
|
||||
'device': device
|
||||
})
|
||||
|
||||
# Calculate running average
|
||||
if hasattr(self.latency_graph, 'y_data') and self.latency_graph.y_data:
|
||||
self.latency_stats['avg'] = sum(self.latency_graph.y_data) / len(self.latency_graph.y_data)
|
||||
|
||||
# Update FPS graph
|
||||
if 'fps' in chart_data:
|
||||
fps_values = chart_data['fps']
|
||||
if fps_values:
|
||||
latest_fps = fps_values[-1]
|
||||
device = current_metrics.get('device', 'CPU')
|
||||
resolution = current_metrics.get('resolution', 'Unknown')
|
||||
|
||||
# Check for device switch
|
||||
device_switched = device != self.previous_device
|
||||
if device_switched:
|
||||
self.device_switched.emit(device)
|
||||
self.previous_device = device
|
||||
|
||||
self.fps_graph.add_data_point(
|
||||
current_time,
|
||||
latest_fps,
|
||||
device=device,
|
||||
is_res_change=False # Will be set by resolution change detection
|
||||
)
|
||||
|
||||
# Update FPS stats display with model name
|
||||
model_name = current_metrics.get('model', 'Unknown')
|
||||
self.fps_stats.setText(f"Current FPS: {latest_fps:.1f} | Resolution: {resolution} | Device: {device} | Model: {model_name}")
|
||||
|
||||
# Update device switching graph
|
||||
device_usage = chart_data.get('device_usage', [])
|
||||
if device_usage:
|
||||
latest_usage = device_usage[-1]
|
||||
device = current_metrics.get('device', 'CPU')
|
||||
|
||||
self.device_graph.add_data_point(
|
||||
current_time,
|
||||
latest_usage * 100, # Convert to percentage
|
||||
device=device
|
||||
)
|
||||
|
||||
# Update statistics displays
|
||||
self.latency_stats_label.setText(
|
||||
f"Avg: {self.latency_stats['avg']:.1f}ms | "
|
||||
f"Max: {self.latency_stats['max']:.1f}ms | "
|
||||
f"Spikes: {self.latency_stats['spike_count']}"
|
||||
)
|
||||
|
||||
# Update device legend
|
||||
self.device_legend.setText(
|
||||
f"<span style='color:#FF4444;'>●</span> CPU Spikes: {self.latency_graph.spike_count} | "
|
||||
f"<span style='color:#FFA500;'>●</span> GPU Spikes: {self.device_graph.spike_count} | "
|
||||
f"<span style='color:#78B4FF;'>●</span> Switches: {self.device_graph.device_switches} | "
|
||||
f"<span style='color:#FFC107;'>●</span> Res Changes: {self.device_graph.resolution_changes}"
|
||||
)
|
||||
|
||||
# Update current model display
|
||||
model_name = current_metrics.get('model', 'Unknown')
|
||||
device = current_metrics.get('device', 'Unknown')
|
||||
if hasattr(self, 'current_model_stats'):
|
||||
self.current_model_stats.setText(f"Model: {model_name} | Device: {device}")
|
||||
|
||||
self.frame_counter += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error updating performance graphs: {e}")
|
||||
|
||||
def update_performance_data(self, analytics_data: Dict[str, Any]):
|
||||
"""Update graphs with new analytics data, including system metrics"""
|
||||
try:
|
||||
print(f"[PERF DEBUG] update_performance_data called with: {analytics_data}")
|
||||
|
||||
# Initialize start time if not set
|
||||
if self.start_time is None:
|
||||
self.start_time = time.time()
|
||||
|
||||
chart_data = analytics_data.get('real_time_data', {})
|
||||
latency_stats = analytics_data.get('latency_statistics', {})
|
||||
current_metrics = analytics_data.get('current_metrics', {})
|
||||
system_metrics = analytics_data.get('system_metrics', {})
|
||||
|
||||
if not chart_data.get('timestamps'):
|
||||
print("[PERF DEBUG] No timestamps in chart_data")
|
||||
return
|
||||
|
||||
self.latest_data = {
|
||||
'chart_data': chart_data,
|
||||
'latency_stats': latency_stats,
|
||||
'current_metrics': current_metrics,
|
||||
'system_metrics': system_metrics
|
||||
}
|
||||
self.update_graphs() # Immediately update graphs on new data
|
||||
|
||||
# Emit signal for other components
|
||||
self.performance_data_updated.emit(analytics_data)
|
||||
|
||||
# Immediately update graphs on new data
|
||||
self.update_graphs()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error updating performance data: {e}")
|
||||
|
||||
def clear_all_graphs(self):
|
||||
"""Clear all graph data"""
|
||||
try:
|
||||
self.latency_graph.clear_data()
|
||||
self.fps_graph.clear_data()
|
||||
self.device_graph.clear_data()
|
||||
|
||||
# Reset statistics
|
||||
self.latency_stats = {
|
||||
'avg': 0.0,
|
||||
'max': 0.0,
|
||||
'min': float('inf'),
|
||||
'spike_count': 0
|
||||
}
|
||||
|
||||
self.frame_counter = 0
|
||||
self.start_time = time.time()
|
||||
|
||||
# Update displays
|
||||
self.latency_stats_label.setText("Avg: 0ms | Max: 0ms | Spikes: 0")
|
||||
self.fps_stats.setText("Current FPS: 0 | Resolution: - | Device: -")
|
||||
self.device_legend.setText(
|
||||
"<span style='color:#FF4444;'>●</span> CPU Spikes: 0 | "
|
||||
"<span style='color:#FFA500;'>●</span> GPU Spikes: 0 | "
|
||||
"<span style='color:#78B4FF;'>●</span> Switches: 0 | "
|
||||
"<span style='color:#FFC107;'>●</span> Res Changes: 0"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error clearing graphs: {e}")
|
||||
|
||||
def set_spike_threshold(self, threshold: float):
|
||||
"""Set the threshold for detecting latency spikes"""
|
||||
self.spike_threshold = threshold
|
||||
|
||||
def get_performance_summary(self) -> Dict[str, Any]:
|
||||
"""Get a summary of current performance metrics"""
|
||||
return {
|
||||
'latency_stats': self.latency_stats.copy(),
|
||||
'frame_count': self.frame_counter,
|
||||
'cpu_usage': list(self.cpu_usage_history),
|
||||
'ram_usage': list(self.ram_usage_history),
|
||||
'current_device': self.previous_device
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user