1044 lines
39 KiB
Python
1044 lines
39 KiB
Python
from PySide6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox,
|
|
QSlider, QCheckBox, QPushButton, QGroupBox, QFormLayout,
|
|
QSpinBox, QDoubleSpinBox, QTabWidget, QLineEdit, QFileDialog,
|
|
QSpacerItem, QSizePolicy, QScrollArea, QFrame
|
|
)
|
|
from PySide6.QtCore import Qt, Signal, Slot
|
|
from PySide6.QtGui import QFont
|
|
|
|
# Import VLM insights widget
|
|
from ui.vlm_insights_widget import VLMInsightsWidget
|
|
|
|
class ModernConfigPanel(QWidget):
|
|
def get_vlm_widget(self):
|
|
"""Return the VLMInsightsWidget instance for controller integration."""
|
|
return getattr(self, 'vlm_insights_widget', None)
|
|
|
|
def vlm_set_current_frame(self, frame):
|
|
if hasattr(self, 'vlm_insights_widget'):
|
|
self.vlm_insights_widget.set_current_frame(frame)
|
|
|
|
def vlm_set_detection_data(self, detection_data):
|
|
if hasattr(self, 'vlm_insights_widget'):
|
|
self.vlm_insights_widget.set_detection_data(detection_data)
|
|
|
|
def vlm_on_video_paused(self, is_paused):
|
|
if hasattr(self, 'vlm_insights_widget'):
|
|
self.vlm_insights_widget.on_video_paused(is_paused)
|
|
|
|
def vlm_on_analysis_result(self, result):
|
|
if hasattr(self, 'vlm_insights_widget'):
|
|
self.vlm_insights_widget.on_analysis_result(result)
|
|
"""Enhanced side panel with modern dark theme and pill-style tabs."""
|
|
|
|
config_changed = Signal(dict) # Emitted when configuration changes are applied
|
|
theme_toggled = Signal(bool) # Emitted when theme toggle button is clicked (True = dark)
|
|
device_switch_requested = Signal(str)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setObjectName("ModernConfigPanel")
|
|
self.setStyleSheet(self._get_modern_style())
|
|
|
|
# Set minimum and preferred size for the panel
|
|
self.setMinimumSize(380, 600)
|
|
self.setMaximumWidth(500)
|
|
|
|
self.initUI()
|
|
self.dark_theme = True # Start with dark theme
|
|
|
|
def _get_modern_style(self):
|
|
"""Modern dark theme with compact layout"""
|
|
return """
|
|
#ModernConfigPanel {
|
|
background: #121212;
|
|
border: none;
|
|
min-width: 320px;
|
|
max-width: 400px;
|
|
}
|
|
|
|
/* Tab Widget Styling */
|
|
QTabWidget::pane {
|
|
background: #1E1E1E;
|
|
border: 1px solid #2C2C2C;
|
|
border-radius: 8px;
|
|
padding: 8px;
|
|
}
|
|
|
|
QTabBar::tab {
|
|
background: transparent;
|
|
color: #B0B0B0;
|
|
border-radius: 12px;
|
|
padding: 8px 16px;
|
|
margin: 1px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
min-width: 70px;
|
|
}
|
|
|
|
QTabBar::tab:selected {
|
|
background: #007BFF;
|
|
color: #FFFFFF;
|
|
}
|
|
|
|
QTabBar::tab:hover:!selected {
|
|
background: #2C2C2C;
|
|
color: #FFFFFF;
|
|
}
|
|
|
|
/* Section Headers */
|
|
QLabel.section-header {
|
|
font-weight: bold;
|
|
color: #FFFFFF;
|
|
border-bottom: 1px solid #2C2C2C;
|
|
margin-bottom: 8px;
|
|
padding-bottom: 4px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* Regular Labels */
|
|
QLabel {
|
|
color: #FFFFFF;
|
|
font-size: 12px;
|
|
background: transparent;
|
|
}
|
|
|
|
QLabel.secondary {
|
|
color: #B0B0B0;
|
|
}
|
|
|
|
/* Buttons */
|
|
QPushButton {
|
|
background: #007BFF;
|
|
color: #FFFFFF;
|
|
border-radius: 8px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
padding: 10px 16px;
|
|
border: none;
|
|
}
|
|
|
|
QPushButton:hover {
|
|
background: #3399FF;
|
|
}
|
|
|
|
QPushButton.secondary {
|
|
background: #2ECC71;
|
|
}
|
|
|
|
QPushButton.secondary:hover {
|
|
background: #48D187;
|
|
}
|
|
|
|
QPushButton.warning {
|
|
background: #E74C3C;
|
|
}
|
|
|
|
QPushButton.warning:hover {
|
|
background: #FF6B5A;
|
|
}
|
|
|
|
/* Sliders */
|
|
QSlider::groove:horizontal {
|
|
background: #1E1E1E;
|
|
border: 1px solid #00E6E6;
|
|
height: 6px;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
QSlider::handle:horizontal {
|
|
background: #00E6E6;
|
|
border-radius: 8px;
|
|
width: 16px;
|
|
height: 16px;
|
|
margin: -5px 0;
|
|
}
|
|
|
|
QSlider::handle:horizontal:hover {
|
|
background: #00FFFF;
|
|
}
|
|
|
|
/* Combo Boxes */
|
|
QComboBox {
|
|
background: #1E1E1E;
|
|
border: 1px solid #00E6E6;
|
|
border-radius: 6px;
|
|
padding: 6px 12px;
|
|
color: #FFFFFF;
|
|
font-size: 12px;
|
|
min-height: 20px;
|
|
}
|
|
|
|
QComboBox::drop-down {
|
|
border: none;
|
|
width: 20px;
|
|
}
|
|
|
|
QComboBox::down-arrow {
|
|
image: none;
|
|
border-left: 5px solid transparent;
|
|
border-right: 5px solid transparent;
|
|
border-top: 5px solid #00E6E6;
|
|
margin-right: 5px;
|
|
}
|
|
|
|
QComboBox QAbstractItemView {
|
|
background: #1E1E1E;
|
|
border: 1px solid #2C2C2C;
|
|
color: #FFFFFF;
|
|
selection-background-color: #007BFF;
|
|
}
|
|
|
|
/* Checkboxes */
|
|
QCheckBox {
|
|
color: #FFFFFF;
|
|
font-size: 12px;
|
|
spacing: 8px;
|
|
}
|
|
|
|
QCheckBox::indicator {
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 3px;
|
|
border: 1px solid #2C2C2C;
|
|
background: #1E1E1E;
|
|
}
|
|
|
|
QCheckBox::indicator:checked {
|
|
background: #007BFF;
|
|
border: 1px solid #007BFF;
|
|
}
|
|
|
|
QCheckBox::indicator:checked:hover {
|
|
background: #3399FF;
|
|
}
|
|
|
|
/* Spin Boxes */
|
|
QSpinBox, QDoubleSpinBox {
|
|
background: #1E1E1E;
|
|
border: 1px solid #2C2C2C;
|
|
border-radius: 6px;
|
|
color: #FFFFFF;
|
|
padding: 6px 8px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
QSpinBox::up-button, QDoubleSpinBox::up-button {
|
|
background: #2C2C2C;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
QSpinBox::down-button, QDoubleSpinBox::down-button {
|
|
background: #2C2C2C;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
/* Group Boxes */
|
|
QGroupBox {
|
|
border: 1px solid #2C2C2C;
|
|
border-radius: 8px;
|
|
margin-top: 16px;
|
|
background: transparent;
|
|
font-weight: bold;
|
|
color: #FFFFFF;
|
|
font-size: 13px;
|
|
padding-top: 10px;
|
|
}
|
|
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 12px;
|
|
top: 0px;
|
|
padding: 0 8px;
|
|
background: #121212;
|
|
}
|
|
|
|
/* Scroll Area */
|
|
QScrollArea {
|
|
background: transparent;
|
|
border: none;
|
|
}
|
|
|
|
QScrollArea QWidget {
|
|
background: transparent;
|
|
}
|
|
|
|
/* Scroll Bars */
|
|
QScrollBar:vertical {
|
|
background: #1E1E1E;
|
|
width: 8px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
QScrollBar::handle:vertical {
|
|
background: #2C2C2C;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
QScrollBar::handle:vertical:hover {
|
|
background: #007BFF;
|
|
}
|
|
"""
|
|
|
|
def initUI(self):
|
|
"""Initialize the modern UI"""
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(16, 16, 16, 16)
|
|
layout.setSpacing(0)
|
|
|
|
# Create tab widget with pill-style tabs
|
|
self.tabs = QTabWidget()
|
|
self.tabs.setTabPosition(QTabWidget.North)
|
|
|
|
# Create tabs
|
|
self._create_detection_tab()
|
|
self._create_ai_insights_tab()
|
|
self._create_display_tab()
|
|
self._create_violations_tab()
|
|
|
|
|
|
layout.addWidget(self.tabs)
|
|
|
|
def _create_detection_tab(self):
|
|
"""Create advanced detection settings tab with dynamic model selection"""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setSpacing(24)
|
|
layout.setContentsMargins(0, 16, 0, 16)
|
|
|
|
# System Info Section
|
|
system_group = QGroupBox("System Info")
|
|
system_layout = QVBoxLayout(system_group)
|
|
system_layout.setSpacing(6) # Reduced spacing
|
|
|
|
# Auto-detected device info
|
|
device_info_layout = QHBoxLayout()
|
|
device_info_label = QLabel("Current Device:")
|
|
device_info_label.setStyleSheet("color: #B0B0B0; font-size: 11px;")
|
|
|
|
self.current_device_label = QLabel("AUTO")
|
|
self.current_device_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #00E6E6;
|
|
font-size: 11px;
|
|
font-weight: bold;
|
|
background: #1E1E1E;
|
|
border: 1px solid #00E6E6;
|
|
border-radius: 3px;
|
|
padding: 2px 6px;
|
|
}
|
|
""")
|
|
|
|
device_info_layout.addWidget(device_info_label)
|
|
device_info_layout.addWidget(self.current_device_label)
|
|
device_info_layout.addStretch()
|
|
|
|
# Device selector
|
|
device_selector_layout = QHBoxLayout()
|
|
device_selector_label = QLabel("Override Device:")
|
|
device_selector_label.setStyleSheet("color: #FFFFFF; font-size: 12px;")
|
|
|
|
self.device_combo = QComboBox()
|
|
self.device_combo.addItems(["AUTO", "CPU", "GPU", "NPU"])
|
|
self.device_combo.setCurrentText("AUTO")
|
|
self.device_combo.currentTextChanged.connect(self._on_device_changed)
|
|
|
|
device_selector_layout.addWidget(device_selector_label)
|
|
device_selector_layout.addWidget(self.device_combo)
|
|
device_selector_layout.addStretch()
|
|
|
|
system_layout.addLayout(device_info_layout)
|
|
system_layout.addLayout(device_selector_layout)
|
|
|
|
# Model Settings Section (Enhanced)
|
|
model_group = QGroupBox("Model Settings (Dynamic Selection)")
|
|
model_layout = QVBoxLayout(model_group)
|
|
model_layout.setSpacing(6) # Reduced spacing
|
|
|
|
# Auto-selection explanation
|
|
auto_info = QLabel("📊 Auto-select based on device:")
|
|
auto_info.setStyleSheet("color: #03DAC5; font-weight: bold; font-size: 12px;")
|
|
model_layout.addWidget(auto_info)
|
|
|
|
# Device-model mapping info
|
|
mapping_layout = QVBoxLayout()
|
|
mapping_layout.setSpacing(2) # Tight spacing
|
|
cpu_mapping = QLabel("• CPU → YOLOv11n (lightweight)")
|
|
gpu_mapping = QLabel("• GPU → YOLOv11x (heavyweight)")
|
|
npu_mapping = QLabel("• NPU → YOLOv11n (optimized)")
|
|
|
|
for label in [cpu_mapping, gpu_mapping, npu_mapping]:
|
|
label.setStyleSheet("color: #B0B0B0; font-size: 10px; margin-left: 12px;")
|
|
mapping_layout.addWidget(label)
|
|
model_layout.addLayout(mapping_layout)
|
|
|
|
# Auto-selected model info
|
|
model_info_layout = QHBoxLayout()
|
|
model_info_label = QLabel("Auto-Selected Model:")
|
|
model_info_label.setStyleSheet("color: #B0B0B0; font-size: 12px;")
|
|
|
|
self.auto_model_label = QLabel("YOLOv11n (CPU Optimized)")
|
|
self.auto_model_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #FFD700;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
background: #1E1E1E;
|
|
border: 1px solid #FFD700;
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
}
|
|
""")
|
|
|
|
model_info_layout.addWidget(model_info_label)
|
|
model_info_layout.addWidget(self.auto_model_label)
|
|
model_info_layout.addStretch()
|
|
|
|
# Manual model selector dropdown
|
|
manual_model_layout = QHBoxLayout()
|
|
manual_model_label = QLabel("Manual Override:")
|
|
manual_model_label.setStyleSheet("color: #FFFFFF; font-size: 12px;")
|
|
|
|
self.model_combo = QComboBox()
|
|
self.model_combo.addItems(["AUTO", "YOLOv11n", "YOLOv11s", "YOLOv11m", "YOLOv11l", "YOLOv11x"])
|
|
self.model_combo.setCurrentText("YOLOv11x") # Default to YOLOv11x instead of AUTO
|
|
self.model_combo.currentTextChanged.connect(self._on_model_changed)
|
|
|
|
manual_model_layout.addWidget(manual_model_label)
|
|
manual_model_layout.addWidget(self.model_combo)
|
|
manual_model_layout.addStretch()
|
|
|
|
# Quick-switch buttons with glow highlight
|
|
quick_switch_label = QLabel("🚀 Quick-switch buttons:")
|
|
quick_switch_label.setStyleSheet("color: #03DAC5; font-weight: bold; font-size: 11px; margin-top: 4px;")
|
|
|
|
quick_switch_layout = QHBoxLayout()
|
|
quick_switch_layout.setSpacing(6) # Reduced spacing
|
|
|
|
self.lightweight_btn = QPushButton("Lightweight\n(YOLOv11n)")
|
|
self.lightweight_btn.setObjectName("quickSwitchLight")
|
|
self.lightweight_btn.clicked.connect(lambda: self._quick_switch_model("YOLOv11n"))
|
|
|
|
self.heavyweight_btn = QPushButton("High-Accuracy\n(YOLOv11x)")
|
|
self.heavyweight_btn.setObjectName("quickSwitchHeavy")
|
|
self.heavyweight_btn.clicked.connect(lambda: self._quick_switch_model("YOLOv11x"))
|
|
|
|
quick_switch_layout.addWidget(self.lightweight_btn)
|
|
quick_switch_layout.addWidget(self.heavyweight_btn)
|
|
|
|
model_layout.addLayout(model_info_layout)
|
|
model_layout.addLayout(manual_model_layout)
|
|
model_layout.addWidget(quick_switch_label)
|
|
model_layout.addLayout(quick_switch_layout)
|
|
|
|
layout.addWidget(system_group)
|
|
layout.addWidget(model_group)
|
|
layout.addStretch() # Add custom styling for enhanced elements
|
|
self._add_detection_tab_styles()
|
|
|
|
self.tabs.addTab(widget, "Detection")
|
|
|
|
# Auto-detect system at startup
|
|
self._auto_detect_system()
|
|
|
|
def _add_detection_tab_styles(self):
|
|
"""Add custom styles for enhanced detection tab elements"""
|
|
additional_style = """
|
|
QPushButton[objectName="quickSwitchLight"] {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #2196F3, stop:1 #1976D2);
|
|
color: #FFFFFF;
|
|
border-radius: 8px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
padding: 6px 8px;
|
|
border: 1px solid transparent;
|
|
text-align: center;
|
|
max-height: 40px;
|
|
}
|
|
|
|
QPushButton[objectName="quickSwitchLight"]:hover {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #42A5F5, stop:1 #1E88E5);
|
|
border: 1px solid #03DAC5;
|
|
}
|
|
|
|
QPushButton[objectName="quickSwitchLight"]:pressed {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #1565C0, stop:1 #0D47A1);
|
|
}
|
|
|
|
QPushButton[objectName="quickSwitchHeavy"] {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4CAF50, stop:1 #388E3C);
|
|
color: #FFFFFF;
|
|
border-radius: 8px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
padding: 6px 8px;
|
|
border: 1px solid transparent;
|
|
text-align: center;
|
|
max-height: 40px;
|
|
}
|
|
|
|
QPushButton[objectName="quickSwitchHeavy"]:hover {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #66BB6A, stop:1 #43A047);
|
|
border: 1px solid #03DAC5;
|
|
}
|
|
|
|
QPushButton[objectName="quickSwitchHeavy"]:pressed {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #2E7D32, stop:1 #1B5E20);
|
|
}
|
|
|
|
QPushButton[objectName="activeModelLight"] {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #03DAC5, stop:1 #018786);
|
|
color: #121212;
|
|
border: 3px solid #00FFFF;
|
|
box-shadow: 0 0 20px rgba(3, 218, 197, 0.8);
|
|
animation: glow 2s ease-in-out infinite alternate;
|
|
}
|
|
|
|
QPushButton[objectName="activeModelHeavy"] {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #03DAC5, stop:1 #018786);
|
|
color: #121212;
|
|
border: 3px solid #00FFFF;
|
|
box-shadow: 0 0 20px rgba(3, 218, 197, 0.8);
|
|
animation: glow 2s ease-in-out infinite alternate;
|
|
}
|
|
|
|
QComboBox {
|
|
background: #232323;
|
|
color: #FFFFFF;
|
|
border: 1px solid #424242;
|
|
border-radius: 6px;
|
|
padding: 6px 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
QComboBox:hover {
|
|
border: 1px solid #03DAC5;
|
|
}
|
|
|
|
QComboBox::drop-down {
|
|
border: none;
|
|
background: #232323;
|
|
width: 20px;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
QComboBox::down-arrow {
|
|
image: none;
|
|
border: 2px solid #FFFFFF;
|
|
border-top: none;
|
|
border-right: none;
|
|
width: 6px;
|
|
height: 6px;
|
|
transform: rotate(-45deg);
|
|
margin-right: 8px;
|
|
}
|
|
|
|
QComboBox QAbstractItemView {
|
|
background: #232323;
|
|
border: 1px solid #424242;
|
|
border-radius: 6px;
|
|
selection-background-color: #03DAC5;
|
|
color: #FFFFFF;
|
|
}
|
|
"""
|
|
current_style = self.styleSheet()
|
|
self.setStyleSheet(current_style + additional_style)
|
|
|
|
def _auto_detect_system(self):
|
|
"""Auto-detect system capabilities and set defaults"""
|
|
try:
|
|
# Try to detect GPU using OpenVINO
|
|
try:
|
|
import openvino as ov
|
|
core = ov.Core()
|
|
available_devices = core.available_devices
|
|
|
|
if 'GPU' in available_devices:
|
|
detected_device = "GPU"
|
|
recommended_model = "YOLOv11x (GPU Optimized)"
|
|
self.auto_model_label.setText(recommended_model)
|
|
self.auto_model_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #00FF00;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
background: #1E1E1E;
|
|
border: 1px solid #00FF00;
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
}
|
|
""")
|
|
print(f"[CONFIG PANEL] GPU detected via OpenVINO: {available_devices}")
|
|
else:
|
|
detected_device = "CPU"
|
|
recommended_model = "YOLOv11n (CPU Optimized)"
|
|
print(f"[CONFIG PANEL] Only CPU available: {available_devices}")
|
|
except ImportError:
|
|
# Fallback: Try nvidia-smi for NVIDIA GPUs
|
|
try:
|
|
import subprocess
|
|
result = subprocess.run(['nvidia-smi'], capture_output=True, text=True, shell=True)
|
|
if result.returncode == 0:
|
|
detected_device = "GPU"
|
|
recommended_model = "YOLOv11x (GPU Optimized)"
|
|
self.auto_model_label.setText(recommended_model)
|
|
self.auto_model_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #00FF00;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
background: #1E1E1E;
|
|
border: 1px solid #00FF00;
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
}
|
|
""")
|
|
print("[CONFIG PANEL] GPU detected via nvidia-smi")
|
|
else:
|
|
detected_device = "CPU"
|
|
recommended_model = "YOLOv11n (CPU Optimized)"
|
|
print("[CONFIG PANEL] nvidia-smi failed, defaulting to CPU")
|
|
except Exception as e:
|
|
detected_device = "CPU"
|
|
recommended_model = "YOLOv11n (CPU Optimized)"
|
|
print(f"[CONFIG PANEL] nvidia-smi exception: {e}")
|
|
|
|
except Exception as e:
|
|
detected_device = "CPU"
|
|
recommended_model = "YOLOv11n (CPU Optimized)"
|
|
print(f"[CONFIG PANEL] Detection failed: {e}")
|
|
|
|
self.current_device_label.setText(detected_device)
|
|
self.auto_model_label.setText(recommended_model)
|
|
|
|
# Update device combo to show detected device
|
|
if detected_device in ["CPU", "GPU"]:
|
|
# Set as current
|
|
self.device_combo.setCurrentText(detected_device)
|
|
|
|
# Highlight the appropriate quick switch button and update model preview
|
|
self._highlight_active_model("AUTO")
|
|
|
|
# Model preview removed - no longer updating preview
|
|
|
|
print(f"[CONFIG PANEL] ✅ Auto-detection complete: {detected_device} -> {recommended_model}")
|
|
|
|
def _on_device_changed(self, device):
|
|
"""Handle device selection change"""
|
|
if device == "AUTO":
|
|
self._auto_detect_system()
|
|
else:
|
|
self.current_device_label.setText(device)
|
|
if device == "GPU":
|
|
self.auto_model_label.setText("YOLOv11x (GPU Optimized)")
|
|
self.auto_model_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #00FF00;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
background: #1E1E1E;
|
|
border: 1px solid #00FF00;
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
}
|
|
""")
|
|
# Model preview removed
|
|
self._highlight_active_model("YOLOv11x")
|
|
else:
|
|
self.auto_model_label.setText("YOLOv11n (CPU Optimized)")
|
|
self.auto_model_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #FFD700;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
background: #1E1E1E;
|
|
border: 1px solid #FFD700;
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
}
|
|
""")
|
|
# Model preview removed
|
|
self._highlight_active_model("YOLOv11n")
|
|
self.device_switch_requested.emit(device)
|
|
|
|
def _on_model_changed(self, model):
|
|
"""Handle manual model selection change"""
|
|
if model != "AUTO":
|
|
self.auto_model_label.setText(f"{model} (Manual Override)")
|
|
self.auto_model_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #FF6B5A;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
background: #1E1E1E;
|
|
border: 1px solid #FF6B5A;
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
}
|
|
""")
|
|
else:
|
|
self.auto_model_label.setText("Auto-Select Model")
|
|
self.auto_model_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #00E6E6;
|
|
font-size: 12px;
|
|
font-weight: normal;
|
|
background: transparent;
|
|
border: none;
|
|
padding: 0px;
|
|
}
|
|
""")
|
|
|
|
# Apply configuration immediately when model changes
|
|
print(f"🔧 Model changed to: {model}, applying config...")
|
|
print(f"🔧 Current model combo text: {self.model_combo.currentText()}")
|
|
print(f"🔧 Current device combo text: {self.device_combo.currentText()}")
|
|
self.apply_config()
|
|
self._highlight_active_model(model)
|
|
|
|
def _quick_switch_model(self, model):
|
|
"""Handle quick switch button clicks"""
|
|
self.model_combo.setCurrentText(model)
|
|
self._on_model_changed(model)
|
|
|
|
def _highlight_active_model(self, model):
|
|
"""Highlight the currently active model button with glow effect"""
|
|
# Reset both buttons
|
|
self.lightweight_btn.setObjectName("quickSwitchLight")
|
|
self.heavyweight_btn.setObjectName("quickSwitchHeavy")
|
|
|
|
# Highlight active button with special glow styling
|
|
if model in ["YOLOv11n", "AUTO"] and "YOLOv11n" in self.auto_model_label.text():
|
|
self.lightweight_btn.setObjectName("activeModelLight")
|
|
elif model in ["YOLOv11x"] or ("YOLOv11x" in self.auto_model_label.text() and model == "AUTO"):
|
|
self.heavyweight_btn.setObjectName("activeModelHeavy")
|
|
|
|
# Refresh styles
|
|
self._add_detection_tab_styles()
|
|
|
|
|
|
def _create_display_tab(self):
|
|
"""Create display settings tab"""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setSpacing(20)
|
|
layout.setContentsMargins(0, 16, 0, 16)
|
|
|
|
# Display Options
|
|
display_group = QGroupBox("Display Options")
|
|
display_layout = QVBoxLayout(display_group)
|
|
display_layout.setSpacing(12)
|
|
|
|
self.show_boxes = QCheckBox("Show Bounding Boxes")
|
|
self.show_boxes.setChecked(True)
|
|
|
|
self.show_labels = QCheckBox("Show Class Labels")
|
|
self.show_labels.setChecked(True)
|
|
|
|
self.show_confidence = QCheckBox("Show Confidence Scores")
|
|
self.show_confidence.setChecked(True)
|
|
|
|
self.show_fps = QCheckBox("Show FPS Counter")
|
|
self.show_fps.setChecked(True)
|
|
|
|
display_layout.addWidget(self.show_boxes)
|
|
display_layout.addWidget(self.show_labels)
|
|
display_layout.addWidget(self.show_confidence)
|
|
display_layout.addWidget(self.show_fps)
|
|
|
|
# Visual Settings
|
|
visual_group = QGroupBox("Visual Settings")
|
|
visual_layout = QVBoxLayout(visual_group)
|
|
visual_layout.setSpacing(16)
|
|
|
|
# Box thickness
|
|
thickness_label = QLabel("Bounding Box Thickness")
|
|
self.thickness_slider = QSlider(Qt.Horizontal)
|
|
self.thickness_slider.setRange(1, 5)
|
|
self.thickness_slider.setValue(2)
|
|
self.thickness_value = QLabel("2")
|
|
self.thickness_value.setObjectName("secondary")
|
|
self.thickness_slider.valueChanged.connect(
|
|
lambda v: self.thickness_value.setText(str(v))
|
|
)
|
|
|
|
thickness_layout = QHBoxLayout()
|
|
thickness_layout.addWidget(thickness_label)
|
|
thickness_layout.addStretch()
|
|
thickness_layout.addWidget(self.thickness_value)
|
|
|
|
# Font size
|
|
font_label = QLabel("Label Font Size")
|
|
self.font_slider = QSlider(Qt.Horizontal)
|
|
self.font_slider.setRange(10, 24)
|
|
self.font_slider.setValue(14)
|
|
self.font_value = QLabel("14")
|
|
self.font_value.setObjectName("secondary")
|
|
self.font_slider.valueChanged.connect(
|
|
lambda v: self.font_value.setText(str(v))
|
|
)
|
|
|
|
font_layout = QHBoxLayout()
|
|
font_layout.addWidget(font_label)
|
|
font_layout.addStretch()
|
|
font_layout.addWidget(self.font_value)
|
|
|
|
visual_layout.addLayout(thickness_layout)
|
|
visual_layout.addWidget(self.thickness_slider)
|
|
visual_layout.addLayout(font_layout)
|
|
visual_layout.addWidget(self.font_slider)
|
|
|
|
layout.addWidget(display_group)
|
|
layout.addWidget(visual_group)
|
|
layout.addStretch()
|
|
|
|
self.tabs.addTab(widget, "Display")
|
|
|
|
def _create_violations_tab(self):
|
|
"""Create violations settings tab"""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setSpacing(20)
|
|
layout.setContentsMargins(0, 16, 0, 16)
|
|
|
|
# Violation Detection
|
|
violations_group = QGroupBox("Violation Detection")
|
|
violations_layout = QVBoxLayout(violations_group)
|
|
violations_layout.setSpacing(12)
|
|
|
|
self.red_light_detection = QCheckBox("Red Light Violations")
|
|
self.red_light_detection.setChecked(True)
|
|
|
|
self.speed_violations = QCheckBox("Speed Violations")
|
|
self.speed_violations.setChecked(False)
|
|
|
|
self.wrong_way_detection = QCheckBox("Wrong Way Detection")
|
|
self.wrong_way_detection.setChecked(False)
|
|
|
|
self.crosswalk_violations = QCheckBox("Crosswalk Violations")
|
|
self.crosswalk_violations.setChecked(False)
|
|
|
|
violations_layout.addWidget(self.red_light_detection)
|
|
violations_layout.addWidget(self.speed_violations)
|
|
violations_layout.addWidget(self.wrong_way_detection)
|
|
violations_layout.addWidget(self.crosswalk_violations)
|
|
|
|
# Alert Settings
|
|
alerts_group = QGroupBox("Alert Settings")
|
|
alerts_layout = QVBoxLayout(alerts_group)
|
|
alerts_layout.setSpacing(12)
|
|
|
|
self.sound_alerts = QCheckBox("Sound Alerts")
|
|
self.sound_alerts.setChecked(True)
|
|
|
|
self.email_notifications = QCheckBox("Email Notifications")
|
|
self.email_notifications.setChecked(False)
|
|
|
|
self.auto_screenshot = QCheckBox("Auto Screenshot on Violation")
|
|
self.auto_screenshot.setChecked(True)
|
|
|
|
alerts_layout.addWidget(self.sound_alerts)
|
|
alerts_layout.addWidget(self.email_notifications)
|
|
alerts_layout.addWidget(self.auto_screenshot)
|
|
|
|
layout.addWidget(violations_group)
|
|
layout.addWidget(alerts_group)
|
|
layout.addStretch()
|
|
|
|
self.tabs.addTab(widget, "Violations")
|
|
|
|
def _create_ai_insights_tab(self):
|
|
"""Create AI insights tab with VLMInsightsWidget"""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setSpacing(20)
|
|
layout.setContentsMargins(0, 16, 0, 16)
|
|
|
|
# Add the VLM Insights Widget
|
|
self.vlm_insights_widget = VLMInsightsWidget()
|
|
layout.addWidget(self.vlm_insights_widget)
|
|
layout.addStretch()
|
|
self.tabs.addTab(widget, "AI Insights")
|
|
|
|
def get_config(self):
|
|
"""Get current configuration as dictionary"""
|
|
return {
|
|
'device': self.device_combo.currentText(),
|
|
'confidence_threshold': 0.5, # Default value
|
|
'iou_threshold': 0.45, # Default value
|
|
'model': self.model_combo.currentText(),
|
|
'input_size': int(self.input_size_combo.currentText()),
|
|
'show_boxes': self.show_boxes.isChecked(),
|
|
'show_labels': self.show_labels.isChecked(),
|
|
'show_confidence': self.show_confidence.isChecked(),
|
|
'show_fps': self.show_fps.isChecked(),
|
|
'box_thickness': self.thickness_slider.value(),
|
|
'font_size': self.font_slider.value(),
|
|
'red_light_detection': self.red_light_detection.isChecked(),
|
|
'speed_violations': self.speed_violations.isChecked(),
|
|
'wrong_way_detection': self.wrong_way_detection.isChecked(),
|
|
'crosswalk_violations': self.crosswalk_violations.isChecked(),
|
|
'sound_alerts': self.sound_alerts.isChecked(),
|
|
'email_notifications': self.email_notifications.isChecked(),
|
|
'auto_screenshot': self.auto_screenshot.isChecked(),
|
|
'enable_vlm': self.enable_vlm.isChecked(),
|
|
'traffic_analysis': self.traffic_analysis.isChecked(),
|
|
'anomaly_detection': self.anomaly_detection.isChecked(),
|
|
'crowd_analysis': self.crowd_analysis.isChecked(),
|
|
'frame_skip': self.frame_skip_slider.value(),
|
|
'batch_size': self.batch_size_spin.value()
|
|
}
|
|
|
|
def set_config(self, config):
|
|
"""Set configuration from dictionary"""
|
|
try:
|
|
# Handle nested config structure
|
|
detection_config = config.get('detection', {})
|
|
display_config = config.get('display', {})
|
|
violations_config = config.get('violations', {})
|
|
|
|
# Detection settings
|
|
if 'device' in detection_config:
|
|
device = detection_config['device']
|
|
self.device_combo.setCurrentText(device)
|
|
print(f"🔧 Config Panel: Set device to {device}")
|
|
|
|
if 'model' in detection_config:
|
|
model = detection_config['model']
|
|
# Convert model format if needed (yolo11n -> YOLOv11n)
|
|
if model and model.lower() != 'auto':
|
|
if 'yolo11' in model.lower():
|
|
if '11n' in model.lower():
|
|
model = 'YOLOv11n'
|
|
elif '11x' in model.lower():
|
|
model = 'YOLOv11x'
|
|
elif '11s' in model.lower():
|
|
model = 'YOLOv11s'
|
|
elif '11m' in model.lower():
|
|
model = 'YOLOv11m'
|
|
elif '11l' in model.lower():
|
|
model = 'YOLOv11l'
|
|
# Try to find and set the model in combo box
|
|
index = self.model_combo.findText(model)
|
|
if index >= 0:
|
|
self.model_combo.setCurrentIndex(index)
|
|
print(f"🔧 Config Panel: Set model to {model}")
|
|
else:
|
|
print(f"⚠️ Config Panel: Model {model} not found in combo box")
|
|
|
|
# Skip confidence and IOU threshold settings (removed from UI)
|
|
|
|
# Display settings
|
|
if 'show_confidence' in display_config:
|
|
self.show_confidence.setChecked(display_config['show_confidence'])
|
|
if 'show_labels' in display_config:
|
|
self.show_labels.setChecked(display_config['show_labels'])
|
|
if 'show_performance' in display_config:
|
|
self.show_fps.setChecked(display_config['show_performance'])
|
|
|
|
# Violations settings
|
|
if 'enable_red_light' in violations_config:
|
|
self.red_light_detection.setChecked(violations_config['enable_red_light'])
|
|
|
|
print("✅ Config Panel: Configuration loaded successfully")
|
|
except Exception as e:
|
|
print(f"❌ Error setting config in panel: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
@Slot()
|
|
def apply_config(self):
|
|
"""Apply current configuration"""
|
|
config = self.get_config()
|
|
print(f"🔧 Config Panel: Applying config: {config}")
|
|
self.config_changed.emit(config)
|
|
|
|
@Slot()
|
|
def reset_config(self):
|
|
"""Reset configuration to defaults"""
|
|
try:
|
|
# Reset to default values
|
|
self.device_combo.setCurrentText("CPU")
|
|
# Skip confidence and IOU sliders (removed from UI)
|
|
self.model_combo.setCurrentIndex(0)
|
|
self.input_size_combo.setCurrentText("640")
|
|
|
|
# Display settings
|
|
self.show_boxes.setChecked(True)
|
|
self.show_labels.setChecked(True)
|
|
self.show_confidence.setChecked(True)
|
|
self.show_fps.setChecked(True)
|
|
self.thickness_slider.setValue(2)
|
|
self.font_slider.setValue(14)
|
|
|
|
# Violations settings
|
|
self.red_light_detection.setChecked(True)
|
|
self.speed_violations.setChecked(False)
|
|
self.wrong_way_detection.setChecked(False)
|
|
self.crosswalk_violations.setChecked(False)
|
|
|
|
# Alert settings
|
|
self.sound_alerts.setChecked(True)
|
|
self.email_notifications.setChecked(False)
|
|
self.auto_screenshot.setChecked(True)
|
|
|
|
# AI settings
|
|
self.enable_vlm.setChecked(False)
|
|
self.traffic_analysis.setChecked(True)
|
|
self.anomaly_detection.setChecked(False)
|
|
self.crowd_analysis.setChecked(False)
|
|
self.frame_skip_slider.setValue(0)
|
|
self.batch_size_spin.setValue(1)
|
|
|
|
print("Configuration reset to defaults")
|
|
except Exception as e:
|
|
print(f"Error resetting config: {e}")
|
|
|
|
@Slot(dict)
|
|
def update_devices_info(self, device_info):
|
|
"""Update device information in the config panel"""
|
|
try:
|
|
# Update device combo with available devices
|
|
available_devices = device_info.get('available_devices', ['CPU'])
|
|
current_device = self.device_combo.currentText()
|
|
|
|
# Clear and repopulate device combo
|
|
self.device_combo.clear()
|
|
self.device_combo.addItems(available_devices)
|
|
|
|
# Restore previous selection if available
|
|
if current_device in available_devices:
|
|
self.device_combo.setCurrentText(current_device)
|
|
else:
|
|
# Default to first available device
|
|
if available_devices:
|
|
self.device_combo.setCurrentText(available_devices[0])
|
|
|
|
print(f"[CONFIG PANEL] Updated available devices: {available_devices}")
|
|
except Exception as e:
|
|
print(f"[CONFIG PANEL] Error updating device info: {e}")
|
|
|
|
@Slot(str)
|
|
def update_status(self, status_message):
|
|
"""Update status message (placeholder for compatibility)"""
|
|
print(f"[CONFIG PANEL] Status: {status_message}")
|
|
|
|
@Slot(dict)
|
|
def update_model_info(self, model_info):
|
|
"""Update model information in the config panel"""
|
|
try:
|
|
# Update model combo if models are provided
|
|
if 'available_models' in model_info:
|
|
current_model = self.model_combo.currentText()
|
|
self.model_combo.clear()
|
|
self.model_combo.addItems(model_info['available_models'])
|
|
|
|
# Restore previous selection if available
|
|
if current_model in model_info['available_models']:
|
|
self.model_combo.setCurrentText(current_model)
|
|
|
|
print(f"[CONFIG PANEL] Updated model info: {model_info}")
|
|
except Exception as e:
|
|
print(f"[CONFIG PANEL] Error updating model info: {e}")
|