cleanup and files added
This commit is contained in:
Binary file not shown.
374
qt_app_pyside1/ui/tabs/live_monitoring_tab.py
Normal file
374
qt_app_pyside1/ui/tabs/live_monitoring_tab.py
Normal file
@@ -0,0 +1,374 @@
|
||||
"""
|
||||
Live Monitoring Tab - Real-time traffic monitoring with multi-camera grid view
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
|
||||
QLabel, QPushButton, QFrame, QSplitter, QScrollArea,
|
||||
QGroupBox, QComboBox, QSpinBox, QCheckBox,
|
||||
QProgressBar, QSlider)
|
||||
from PySide6.QtCore import Qt, Signal, QTimer, pyqtSignal
|
||||
from PySide6.QtGui import QFont, QPixmap, QPainter, QColor
|
||||
|
||||
from ..widgets.video_display_widget import VideoDisplayWidget
|
||||
from ..widgets.camera_control_panel import CameraControlPanel
|
||||
from ..widgets.detection_overlay_widget import DetectionOverlayWidget
|
||||
from ..widgets.statistics_panel import StatisticsPanel
|
||||
|
||||
class LiveMonitoringTab(QWidget):
|
||||
"""
|
||||
Live Monitoring Tab with real-time multi-camera grid view
|
||||
|
||||
Features:
|
||||
- Multi-camera grid layout (1x1, 2x2, 3x3, 4x4)
|
||||
- Real-time video feeds with detection overlays
|
||||
- Camera control panels for each feed
|
||||
- Live statistics and alerts
|
||||
- Recording controls
|
||||
- Full-screen mode for individual cameras
|
||||
"""
|
||||
|
||||
# Signals
|
||||
camera_status_changed = Signal(str, str) # camera_id, status
|
||||
recording_started = Signal(str) # camera_id
|
||||
recording_stopped = Signal(str) # camera_id
|
||||
fullscreen_requested = Signal(str) # camera_id
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# Initialize properties
|
||||
self.camera_feeds = {}
|
||||
self.grid_size = (2, 2)
|
||||
self.recording_status = {}
|
||||
|
||||
self._setup_ui()
|
||||
self._setup_connections()
|
||||
|
||||
# Timer for real-time updates
|
||||
self.update_timer = QTimer()
|
||||
self.update_timer.timeout.connect(self._update_displays)
|
||||
self.update_timer.start(100) # Update every 100ms for smooth video
|
||||
|
||||
print("✅ Live Monitoring Tab initialized")
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup the live monitoring UI"""
|
||||
|
||||
# Main layout
|
||||
main_layout = QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
main_layout.setSpacing(10)
|
||||
|
||||
# Left panel - Camera grid and controls
|
||||
left_panel = self._create_left_panel()
|
||||
main_layout.addWidget(left_panel, 3) # 75% width
|
||||
|
||||
# Right panel - Statistics and controls
|
||||
right_panel = self._create_right_panel()
|
||||
main_layout.addWidget(right_panel, 1) # 25% width
|
||||
|
||||
def _create_left_panel(self):
|
||||
"""Create the left panel with camera grid"""
|
||||
panel = QFrame()
|
||||
panel.setObjectName("leftPanel")
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# Header with grid controls
|
||||
header = self._create_grid_header()
|
||||
layout.addWidget(header)
|
||||
|
||||
# Camera grid container
|
||||
self.grid_container = QScrollArea()
|
||||
self.grid_container.setWidgetResizable(True)
|
||||
self.grid_container.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.grid_container.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
|
||||
# Create initial grid
|
||||
self.camera_grid_widget = QWidget()
|
||||
self.camera_grid_layout = QGridLayout(self.camera_grid_widget)
|
||||
self.camera_grid_layout.setSpacing(5)
|
||||
|
||||
self._setup_camera_grid()
|
||||
|
||||
self.grid_container.setWidget(self.camera_grid_widget)
|
||||
layout.addWidget(self.grid_container)
|
||||
|
||||
return panel
|
||||
|
||||
def _create_grid_header(self):
|
||||
"""Create the grid control header"""
|
||||
header = QFrame()
|
||||
header.setFixedHeight(50)
|
||||
layout = QHBoxLayout(header)
|
||||
|
||||
# Grid size selector
|
||||
layout.addWidget(QLabel("Grid Layout:"))
|
||||
|
||||
self.grid_selector = QComboBox()
|
||||
self.grid_selector.addItems(["1×1", "2×2", "3×3", "4×4"])
|
||||
self.grid_selector.setCurrentText("2×2")
|
||||
self.grid_selector.currentTextChanged.connect(self._change_grid_size)
|
||||
layout.addWidget(self.grid_selector)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Global recording controls
|
||||
self.record_all_button = QPushButton("🔴 Record All")
|
||||
self.record_all_button.clicked.connect(self._toggle_record_all)
|
||||
layout.addWidget(self.record_all_button)
|
||||
|
||||
# Snapshot all button
|
||||
snapshot_button = QPushButton("📸 Snapshot All")
|
||||
snapshot_button.clicked.connect(self._snapshot_all)
|
||||
layout.addWidget(snapshot_button)
|
||||
|
||||
# Full screen toggle
|
||||
fullscreen_button = QPushButton("⛶ Fullscreen")
|
||||
fullscreen_button.clicked.connect(self._toggle_fullscreen)
|
||||
layout.addWidget(fullscreen_button)
|
||||
|
||||
return header
|
||||
|
||||
def _create_right_panel(self):
|
||||
"""Create the right panel with statistics and controls"""
|
||||
panel = QFrame()
|
||||
panel.setObjectName("rightPanel")
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# Live statistics
|
||||
stats_group = QGroupBox("Live Statistics")
|
||||
stats_layout = QVBoxLayout(stats_group)
|
||||
|
||||
self.stats_panel = StatisticsPanel()
|
||||
stats_layout.addWidget(self.stats_panel)
|
||||
|
||||
layout.addWidget(stats_group)
|
||||
|
||||
# Camera controls
|
||||
controls_group = QGroupBox("Camera Controls")
|
||||
controls_layout = QVBoxLayout(controls_group)
|
||||
|
||||
# Active camera selector
|
||||
controls_layout.addWidget(QLabel("Active Camera:"))
|
||||
self.active_camera_combo = QComboBox()
|
||||
self.active_camera_combo.currentTextChanged.connect(self._select_active_camera)
|
||||
controls_layout.addWidget(self.active_camera_combo)
|
||||
|
||||
# Camera control panel
|
||||
self.camera_controls = CameraControlPanel()
|
||||
controls_layout.addWidget(self.camera_controls)
|
||||
|
||||
layout.addWidget(controls_group)
|
||||
|
||||
# Detection settings
|
||||
detection_group = QGroupBox("Detection Settings")
|
||||
detection_layout = QVBoxLayout(detection_group)
|
||||
|
||||
# Confidence threshold
|
||||
detection_layout.addWidget(QLabel("Confidence Threshold:"))
|
||||
self.confidence_slider = QSlider(Qt.Horizontal)
|
||||
self.confidence_slider.setRange(1, 100)
|
||||
self.confidence_slider.setValue(50)
|
||||
self.confidence_slider.valueChanged.connect(self._update_confidence)
|
||||
detection_layout.addWidget(self.confidence_slider)
|
||||
|
||||
self.confidence_label = QLabel("50%")
|
||||
detection_layout.addWidget(self.confidence_label)
|
||||
|
||||
# Detection toggles
|
||||
self.show_boxes_cb = QCheckBox("Show Bounding Boxes")
|
||||
self.show_boxes_cb.setChecked(True)
|
||||
detection_layout.addWidget(self.show_boxes_cb)
|
||||
|
||||
self.show_tracks_cb = QCheckBox("Show Tracking IDs")
|
||||
self.show_tracks_cb.setChecked(True)
|
||||
detection_layout.addWidget(self.show_tracks_cb)
|
||||
|
||||
self.show_speed_cb = QCheckBox("Show Speed Estimates")
|
||||
self.show_speed_cb.setChecked(False)
|
||||
detection_layout.addWidget(self.show_speed_cb)
|
||||
|
||||
layout.addWidget(detection_group)
|
||||
|
||||
# Alert panel
|
||||
alerts_group = QGroupBox("Live Alerts")
|
||||
alerts_layout = QVBoxLayout(alerts_group)
|
||||
|
||||
self.alerts_scroll = QScrollArea()
|
||||
self.alerts_scroll.setMaximumHeight(150)
|
||||
self.alerts_widget = QWidget()
|
||||
self.alerts_layout = QVBoxLayout(self.alerts_widget)
|
||||
self.alerts_scroll.setWidget(self.alerts_widget)
|
||||
alerts_layout.addWidget(self.alerts_scroll)
|
||||
|
||||
layout.addWidget(alerts_group)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
return panel
|
||||
|
||||
def _setup_camera_grid(self):
|
||||
"""Setup the camera grid based on current grid size"""
|
||||
|
||||
# Clear existing grid
|
||||
for i in reversed(range(self.camera_grid_layout.count())):
|
||||
child = self.camera_grid_layout.itemAt(i).widget()
|
||||
if child:
|
||||
child.setParent(None)
|
||||
|
||||
rows, cols = self.grid_size
|
||||
|
||||
# Create camera display widgets
|
||||
for row in range(rows):
|
||||
for col in range(cols):
|
||||
camera_id = f"camera_{row}_{col}"
|
||||
|
||||
# Create video display widget
|
||||
video_widget = VideoDisplayWidget(camera_id)
|
||||
video_widget.setMinimumSize(320, 240)
|
||||
video_widget.double_clicked.connect(lambda cid=camera_id: self._camera_double_clicked(cid))
|
||||
|
||||
self.camera_feeds[camera_id] = video_widget
|
||||
self.camera_grid_layout.addWidget(video_widget, row, col)
|
||||
|
||||
# Add to active camera selector
|
||||
self.active_camera_combo.addItem(f"Camera {row+1}-{col+1}", camera_id)
|
||||
|
||||
def _setup_connections(self):
|
||||
"""Setup signal connections"""
|
||||
|
||||
# Detection overlay connections
|
||||
if hasattr(self, 'show_boxes_cb'):
|
||||
self.show_boxes_cb.toggled.connect(self._update_overlay_settings)
|
||||
if hasattr(self, 'show_tracks_cb'):
|
||||
self.show_tracks_cb.toggled.connect(self._update_overlay_settings)
|
||||
if hasattr(self, 'show_speed_cb'):
|
||||
self.show_speed_cb.toggled.connect(self._update_overlay_settings)
|
||||
|
||||
def _change_grid_size(self, size_text):
|
||||
"""Change the camera grid size"""
|
||||
size_map = {
|
||||
"1×1": (1, 1),
|
||||
"2×2": (2, 2),
|
||||
"3×3": (3, 3),
|
||||
"4×4": (4, 4)
|
||||
}
|
||||
|
||||
if size_text in size_map:
|
||||
self.grid_size = size_map[size_text]
|
||||
|
||||
# Clear active camera combo
|
||||
self.active_camera_combo.clear()
|
||||
|
||||
# Recreate grid
|
||||
self._setup_camera_grid()
|
||||
|
||||
print(f"📺 Grid size changed to {size_text}")
|
||||
|
||||
def _toggle_record_all(self):
|
||||
"""Toggle recording for all cameras"""
|
||||
recording = self.record_all_button.text().startswith("🔴")
|
||||
|
||||
if recording:
|
||||
# Start recording all
|
||||
self.record_all_button.setText("⏹️ Stop All")
|
||||
for camera_id in self.camera_feeds:
|
||||
self.recording_status[camera_id] = True
|
||||
self.recording_started.emit(camera_id)
|
||||
else:
|
||||
# Stop recording all
|
||||
self.record_all_button.setText("🔴 Record All")
|
||||
for camera_id in self.camera_feeds:
|
||||
self.recording_status[camera_id] = False
|
||||
self.recording_stopped.emit(camera_id)
|
||||
|
||||
# Update individual camera displays
|
||||
self._update_recording_indicators()
|
||||
|
||||
def _snapshot_all(self):
|
||||
"""Take snapshots of all camera feeds"""
|
||||
for camera_id, widget in self.camera_feeds.items():
|
||||
widget.take_snapshot()
|
||||
print("📸 Snapshots taken for all cameras")
|
||||
|
||||
def _toggle_fullscreen(self):
|
||||
"""Toggle fullscreen mode for active camera"""
|
||||
current_camera = self.active_camera_combo.currentData()
|
||||
if current_camera:
|
||||
self.fullscreen_requested.emit(current_camera)
|
||||
|
||||
def _select_active_camera(self, camera_text):
|
||||
"""Select the active camera for controls"""
|
||||
camera_id = self.active_camera_combo.currentData()
|
||||
if camera_id and camera_id in self.camera_feeds:
|
||||
# Update camera controls for selected camera
|
||||
self.camera_controls.set_active_camera(camera_id)
|
||||
print(f"📹 Active camera: {camera_text}")
|
||||
|
||||
def _camera_double_clicked(self, camera_id):
|
||||
"""Handle camera double-click for fullscreen"""
|
||||
self.fullscreen_requested.emit(camera_id)
|
||||
|
||||
def _update_confidence(self, value):
|
||||
"""Update confidence threshold"""
|
||||
self.confidence_label.setText(f"{value}%")
|
||||
|
||||
# Update all video widgets
|
||||
for widget in self.camera_feeds.values():
|
||||
widget.set_confidence_threshold(value / 100.0)
|
||||
|
||||
def _update_overlay_settings(self):
|
||||
"""Update detection overlay settings"""
|
||||
settings = {
|
||||
'show_boxes': self.show_boxes_cb.isChecked(),
|
||||
'show_tracks': self.show_tracks_cb.isChecked(),
|
||||
'show_speed': self.show_speed_cb.isChecked()
|
||||
}
|
||||
|
||||
# Apply to all video widgets
|
||||
for widget in self.camera_feeds.values():
|
||||
widget.update_overlay_settings(settings)
|
||||
|
||||
def _update_displays(self):
|
||||
"""Update all video displays (called by timer)"""
|
||||
# This will be connected to actual video streams
|
||||
pass
|
||||
|
||||
def _update_recording_indicators(self):
|
||||
"""Update recording indicators on camera widgets"""
|
||||
for camera_id, widget in self.camera_feeds.items():
|
||||
is_recording = self.recording_status.get(camera_id, False)
|
||||
widget.set_recording_indicator(is_recording)
|
||||
|
||||
def add_alert(self, message, level="info"):
|
||||
"""Add a new alert to the alerts panel"""
|
||||
from ..widgets.alert_widget import AlertWidget
|
||||
|
||||
alert = AlertWidget(message, level)
|
||||
self.alerts_layout.addWidget(alert)
|
||||
|
||||
# Remove old alerts if too many
|
||||
if self.alerts_layout.count() > 10:
|
||||
old_alert = self.alerts_layout.itemAt(0).widget()
|
||||
if old_alert:
|
||||
old_alert.setParent(None)
|
||||
|
||||
# Scroll to bottom
|
||||
self.alerts_scroll.verticalScrollBar().setValue(
|
||||
self.alerts_scroll.verticalScrollBar().maximum()
|
||||
)
|
||||
|
||||
def update_statistics(self, stats_data):
|
||||
"""Update the statistics panel"""
|
||||
if hasattr(self, 'stats_panel'):
|
||||
self.stats_panel.update_data(stats_data)
|
||||
|
||||
def set_camera_feed(self, camera_id, frame):
|
||||
"""Set video frame for a specific camera"""
|
||||
if camera_id in self.camera_feeds:
|
||||
self.camera_feeds[camera_id].set_frame(frame)
|
||||
|
||||
def add_detections(self, camera_id, detections):
|
||||
"""Add detection results to a camera feed"""
|
||||
if camera_id in self.camera_feeds:
|
||||
self.camera_feeds[camera_id].add_detections(detections)
|
||||
748
qt_app_pyside1/ui/tabs/smart_intersection_tab.py
Normal file
748
qt_app_pyside1/ui/tabs/smart_intersection_tab.py
Normal file
@@ -0,0 +1,748 @@
|
||||
"""
|
||||
Smart Intersection Tab - IoT integration and traffic control management
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
|
||||
QGroupBox, QLabel, QPushButton, QComboBox,
|
||||
QSlider, QProgressBar, QFrame, QSplitter,
|
||||
QScrollArea, QCheckBox, QSpinBox, QTabWidget,
|
||||
QTableWidget, QTableWidgetItem, QTextEdit,
|
||||
QDateTimeEdit, QHeaderView)
|
||||
from PySide6.QtCore import Qt, Signal, QTimer, QDateTime
|
||||
from PySide6.QtGui import QFont, QColor, QPainter, QPen, QBrush
|
||||
import json
|
||||
|
||||
class TrafficLightController(QWidget):
|
||||
"""Traffic light control widget"""
|
||||
|
||||
light_changed = Signal(str, str) # intersection_id, new_state
|
||||
|
||||
def __init__(self, intersection_id="intersection_1", parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.intersection_id = intersection_id
|
||||
self.current_state = "auto"
|
||||
self.current_phase = "green_ns" # north-south green
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup traffic light control UI"""
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Header
|
||||
header = QLabel(f"🚦 {self.intersection_id.replace('_', ' ').title()}")
|
||||
header.setFont(QFont("Segoe UI", 10, QFont.Bold))
|
||||
header.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(header)
|
||||
|
||||
# Traffic light visualization
|
||||
lights_frame = QFrame()
|
||||
lights_frame.setFixedSize(120, 200)
|
||||
lights_frame.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #2c3e50;
|
||||
border: 2px solid #34495e;
|
||||
border-radius: 8px;
|
||||
}
|
||||
""")
|
||||
|
||||
lights_layout = QVBoxLayout(lights_frame)
|
||||
lights_layout.setSpacing(5)
|
||||
lights_layout.setContentsMargins(10, 10, 10, 10)
|
||||
|
||||
# North-South lights
|
||||
ns_label = QLabel("N-S")
|
||||
ns_label.setStyleSheet("color: white; font-size: 8pt;")
|
||||
ns_label.setAlignment(Qt.AlignCenter)
|
||||
lights_layout.addWidget(ns_label)
|
||||
|
||||
self.ns_red = self._create_light_indicator("red", False)
|
||||
self.ns_yellow = self._create_light_indicator("yellow", False)
|
||||
self.ns_green = self._create_light_indicator("green", True)
|
||||
|
||||
lights_layout.addWidget(self.ns_red)
|
||||
lights_layout.addWidget(self.ns_yellow)
|
||||
lights_layout.addWidget(self.ns_green)
|
||||
|
||||
# East-West lights
|
||||
ew_label = QLabel("E-W")
|
||||
ew_label.setStyleSheet("color: white; font-size: 8pt;")
|
||||
ew_label.setAlignment(Qt.AlignCenter)
|
||||
lights_layout.addWidget(ew_label)
|
||||
|
||||
self.ew_red = self._create_light_indicator("red", True)
|
||||
self.ew_yellow = self._create_light_indicator("yellow", False)
|
||||
self.ew_green = self._create_light_indicator("green", False)
|
||||
|
||||
lights_layout.addWidget(self.ew_red)
|
||||
lights_layout.addWidget(self.ew_yellow)
|
||||
lights_layout.addWidget(self.ew_green)
|
||||
|
||||
layout.addWidget(lights_frame, 0, Qt.AlignCenter)
|
||||
|
||||
# Control buttons
|
||||
controls_layout = QVBoxLayout()
|
||||
|
||||
self.auto_btn = QPushButton("🤖 Auto")
|
||||
self.auto_btn.setCheckable(True)
|
||||
self.auto_btn.setChecked(True)
|
||||
self.auto_btn.clicked.connect(lambda: self._set_mode("auto"))
|
||||
controls_layout.addWidget(self.auto_btn)
|
||||
|
||||
self.manual_btn = QPushButton("👤 Manual")
|
||||
self.manual_btn.setCheckable(True)
|
||||
self.manual_btn.clicked.connect(lambda: self._set_mode("manual"))
|
||||
controls_layout.addWidget(self.manual_btn)
|
||||
|
||||
self.emergency_btn = QPushButton("🚨 Emergency")
|
||||
self.emergency_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
""")
|
||||
self.emergency_btn.clicked.connect(lambda: self._set_mode("emergency"))
|
||||
controls_layout.addWidget(self.emergency_btn)
|
||||
|
||||
layout.addLayout(controls_layout)
|
||||
|
||||
# Status
|
||||
self.status_label = QLabel("Status: Auto Mode")
|
||||
self.status_label.setStyleSheet("font-size: 8pt; color: #27ae60;")
|
||||
self.status_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
def _create_light_indicator(self, color, active=False):
|
||||
"""Create a traffic light indicator"""
|
||||
light = QLabel()
|
||||
light.setFixedSize(20, 20)
|
||||
light.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
background-color: {'#' + color if active else '#2c3e50'};
|
||||
border: 1px solid #34495e;
|
||||
border-radius: 10px;
|
||||
opacity: {'1.0' if active else '0.3'};
|
||||
}}
|
||||
""")
|
||||
return light
|
||||
|
||||
def _set_mode(self, mode):
|
||||
"""Set traffic light control mode"""
|
||||
self.current_state = mode
|
||||
|
||||
# Update button states
|
||||
self.auto_btn.setChecked(mode == "auto")
|
||||
self.manual_btn.setChecked(mode == "manual")
|
||||
|
||||
# Update status
|
||||
status_colors = {
|
||||
"auto": "#27ae60",
|
||||
"manual": "#f39c12",
|
||||
"emergency": "#e74c3c"
|
||||
}
|
||||
|
||||
self.status_label.setText(f"Status: {mode.title()} Mode")
|
||||
self.status_label.setStyleSheet(f"font-size: 8pt; color: {status_colors.get(mode, '#95a5a6')};")
|
||||
|
||||
# Emit signal
|
||||
self.light_changed.emit(self.intersection_id, mode)
|
||||
|
||||
print(f"🚦 {self.intersection_id} set to {mode} mode")
|
||||
|
||||
class IoTDeviceWidget(QFrame):
|
||||
"""IoT device status widget"""
|
||||
|
||||
def __init__(self, device_id, device_type, status="online", parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.device_id = device_id
|
||||
self.device_type = device_type
|
||||
self.status = status
|
||||
|
||||
self.setFixedSize(150, 80)
|
||||
self._setup_ui()
|
||||
self._apply_style()
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup IoT device widget UI"""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(8, 6, 8, 6)
|
||||
|
||||
# Device header
|
||||
header_layout = QHBoxLayout()
|
||||
|
||||
# Device icon
|
||||
icons = {
|
||||
"camera": "📷",
|
||||
"sensor": "📡",
|
||||
"controller": "🎛️",
|
||||
"display": "📺",
|
||||
"gateway": "🌐"
|
||||
}
|
||||
|
||||
icon_label = QLabel(icons.get(self.device_type, "📟"))
|
||||
icon_label.setFont(QFont("Arial", 14))
|
||||
header_layout.addWidget(icon_label)
|
||||
|
||||
# Device name
|
||||
name_label = QLabel(self.device_id.replace('_', ' ').title())
|
||||
name_label.setFont(QFont("Segoe UI", 8, QFont.Bold))
|
||||
header_layout.addWidget(name_label)
|
||||
|
||||
header_layout.addStretch()
|
||||
|
||||
# Status indicator
|
||||
self.status_indicator = QLabel("●")
|
||||
self.status_indicator.setFont(QFont("Arial", 10))
|
||||
header_layout.addWidget(self.status_indicator)
|
||||
|
||||
layout.addLayout(header_layout)
|
||||
|
||||
# Device info
|
||||
info_layout = QVBoxLayout()
|
||||
|
||||
type_label = QLabel(f"Type: {self.device_type.title()}")
|
||||
type_label.setFont(QFont("Segoe UI", 7))
|
||||
info_layout.addWidget(type_label)
|
||||
|
||||
self.status_label = QLabel(f"Status: {self.status.title()}")
|
||||
self.status_label.setFont(QFont("Segoe UI", 7))
|
||||
info_layout.addWidget(self.status_label)
|
||||
|
||||
layout.addLayout(info_layout)
|
||||
|
||||
self._update_status_display()
|
||||
|
||||
def _apply_style(self):
|
||||
"""Apply device widget styling"""
|
||||
self.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: white;
|
||||
border: 1px solid #e1e8ed;
|
||||
border-radius: 6px;
|
||||
margin: 2px;
|
||||
}
|
||||
QFrame:hover {
|
||||
border-color: #3498db;
|
||||
}
|
||||
""")
|
||||
|
||||
def _update_status_display(self):
|
||||
"""Update status indicator colors"""
|
||||
colors = {
|
||||
"online": "#27ae60",
|
||||
"offline": "#e74c3c",
|
||||
"warning": "#f39c12",
|
||||
"error": "#c0392b"
|
||||
}
|
||||
|
||||
color = colors.get(self.status, "#95a5a6")
|
||||
self.status_indicator.setStyleSheet(f"color: {color};")
|
||||
self.status_label.setText(f"Status: {self.status.title()}")
|
||||
|
||||
def set_status(self, status):
|
||||
"""Update device status"""
|
||||
self.status = status
|
||||
self._update_status_display()
|
||||
|
||||
class SmartIntersectionTab(QWidget):
|
||||
"""
|
||||
Smart Intersection Tab for IoT integration and traffic control
|
||||
|
||||
Features:
|
||||
- Traffic light control and monitoring
|
||||
- IoT device management
|
||||
- Real-time traffic flow optimization
|
||||
- Emergency response coordination
|
||||
- Data analytics and reporting
|
||||
- System integration dashboard
|
||||
"""
|
||||
|
||||
# Signals
|
||||
traffic_control_changed = Signal(dict)
|
||||
iot_device_updated = Signal(str, str)
|
||||
emergency_activated = Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.iot_devices = {}
|
||||
self.traffic_controllers = {}
|
||||
self.intersection_data = {}
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
# Timer for real-time updates
|
||||
self.update_timer = QTimer()
|
||||
self.update_timer.timeout.connect(self._update_intersection_data)
|
||||
self.update_timer.start(2000) # Update every 2 seconds
|
||||
|
||||
print("🌉 Smart Intersection Tab initialized")
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup the smart intersection UI"""
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Header with system overview
|
||||
header = self._create_header()
|
||||
layout.addWidget(header)
|
||||
|
||||
# Main content splitter
|
||||
main_splitter = QSplitter(Qt.Horizontal)
|
||||
layout.addWidget(main_splitter)
|
||||
|
||||
# Left panel - Traffic control
|
||||
left_panel = self._create_traffic_control_panel()
|
||||
main_splitter.addWidget(left_panel)
|
||||
|
||||
# Right panel - IoT devices and analytics
|
||||
right_panel = self._create_iot_panel()
|
||||
main_splitter.addWidget(right_panel)
|
||||
|
||||
# Set splitter proportions
|
||||
main_splitter.setSizes([500, 500])
|
||||
|
||||
def _create_header(self):
|
||||
"""Create header with intersection overview"""
|
||||
header = QFrame()
|
||||
header.setFixedHeight(80)
|
||||
header.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #16a085;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
""")
|
||||
|
||||
layout = QHBoxLayout(header)
|
||||
layout.setContentsMargins(20, 10, 20, 10)
|
||||
|
||||
# Title section
|
||||
title_layout = QVBoxLayout()
|
||||
|
||||
title = QLabel("🌉 Smart Intersection Control Center")
|
||||
title.setFont(QFont("Segoe UI", 16, QFont.Bold))
|
||||
title.setStyleSheet("color: white;")
|
||||
title_layout.addWidget(title)
|
||||
|
||||
subtitle = QLabel("Real-time traffic optimization and IoT device management")
|
||||
subtitle.setFont(QFont("Segoe UI", 9))
|
||||
subtitle.setStyleSheet("color: #ecf0f1;")
|
||||
title_layout.addWidget(subtitle)
|
||||
|
||||
layout.addLayout(title_layout)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Statistics cards
|
||||
stats_layout = QHBoxLayout()
|
||||
|
||||
# Active intersections
|
||||
intersections_card = self._create_header_card("Active Intersections", "4", "#27ae60")
|
||||
stats_layout.addWidget(intersections_card)
|
||||
|
||||
# IoT devices
|
||||
devices_card = self._create_header_card("IoT Devices", "12", "#3498db")
|
||||
stats_layout.addWidget(devices_card)
|
||||
|
||||
# Traffic efficiency
|
||||
efficiency_card = self._create_header_card("Traffic Efficiency", "87%", "#f39c12")
|
||||
stats_layout.addWidget(efficiency_card)
|
||||
|
||||
layout.addLayout(stats_layout)
|
||||
|
||||
return header
|
||||
|
||||
def _create_header_card(self, title, value, color):
|
||||
"""Create a header statistics card"""
|
||||
card = QFrame()
|
||||
card.setFixedSize(120, 50)
|
||||
card.setStyleSheet(f"""
|
||||
QFrame {{
|
||||
background-color: {color};
|
||||
border-radius: 6px;
|
||||
margin: 2px;
|
||||
}}
|
||||
""")
|
||||
|
||||
layout = QVBoxLayout(card)
|
||||
layout.setContentsMargins(8, 4, 8, 4)
|
||||
|
||||
value_label = QLabel(value)
|
||||
value_label.setFont(QFont("Segoe UI", 14, QFont.Bold))
|
||||
value_label.setStyleSheet("color: white;")
|
||||
value_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(value_label)
|
||||
|
||||
title_label = QLabel(title)
|
||||
title_label.setFont(QFont("Segoe UI", 7))
|
||||
title_label.setStyleSheet("color: white;")
|
||||
title_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(title_label)
|
||||
|
||||
return card
|
||||
|
||||
def _create_traffic_control_panel(self):
|
||||
"""Create traffic control panel"""
|
||||
panel = QFrame()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# Traffic control section
|
||||
control_group = QGroupBox("Traffic Light Control")
|
||||
control_layout = QVBoxLayout(control_group)
|
||||
|
||||
# Intersection controllers grid
|
||||
controllers_scroll = QScrollArea()
|
||||
controllers_widget = QWidget()
|
||||
controllers_layout = QGridLayout(controllers_widget)
|
||||
|
||||
# Create traffic light controllers
|
||||
intersections = [
|
||||
"main_oak", "5th_pine", "broadway_2nd", "market_1st"
|
||||
]
|
||||
|
||||
for i, intersection_id in enumerate(intersections):
|
||||
controller = TrafficLightController(intersection_id)
|
||||
controller.light_changed.connect(self._on_traffic_control_changed)
|
||||
self.traffic_controllers[intersection_id] = controller
|
||||
controllers_layout.addWidget(controller, i // 2, i % 2)
|
||||
|
||||
controllers_scroll.setWidget(controllers_widget)
|
||||
controllers_scroll.setMaximumHeight(450)
|
||||
control_layout.addWidget(controllers_scroll)
|
||||
|
||||
layout.addWidget(control_group)
|
||||
|
||||
# Global controls
|
||||
global_controls = self._create_global_controls()
|
||||
layout.addWidget(global_controls)
|
||||
|
||||
return panel
|
||||
|
||||
def _create_global_controls(self):
|
||||
"""Create global traffic control section"""
|
||||
section = QGroupBox("Global Controls")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# Emergency controls
|
||||
emergency_layout = QHBoxLayout()
|
||||
|
||||
emergency_all_btn = QPushButton("🚨 Emergency All")
|
||||
emergency_all_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
padding: 8px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
""")
|
||||
emergency_all_btn.clicked.connect(self._emergency_all_intersections)
|
||||
emergency_layout.addWidget(emergency_all_btn)
|
||||
|
||||
clear_emergency_btn = QPushButton("✅ Clear Emergency")
|
||||
clear_emergency_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
padding: 8px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #229954;
|
||||
}
|
||||
""")
|
||||
clear_emergency_btn.clicked.connect(self._clear_emergency_all)
|
||||
emergency_layout.addWidget(clear_emergency_btn)
|
||||
|
||||
layout.addLayout(emergency_layout)
|
||||
|
||||
# Traffic optimization
|
||||
optimization_layout = QHBoxLayout()
|
||||
|
||||
self.adaptive_cb = QCheckBox("Adaptive Traffic Control")
|
||||
self.adaptive_cb.setChecked(True)
|
||||
self.adaptive_cb.toggled.connect(self._toggle_adaptive_control)
|
||||
optimization_layout.addWidget(self.adaptive_cb)
|
||||
|
||||
optimize_btn = QPushButton("🔄 Optimize Flow")
|
||||
optimize_btn.clicked.connect(self._optimize_traffic_flow)
|
||||
optimization_layout.addWidget(optimize_btn)
|
||||
|
||||
layout.addLayout(optimization_layout)
|
||||
|
||||
return section
|
||||
|
||||
def _create_iot_panel(self):
|
||||
"""Create IoT devices and analytics panel"""
|
||||
panel = QFrame()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# IoT devices section
|
||||
iot_group = QGroupBox("IoT Device Network")
|
||||
iot_layout = QVBoxLayout(iot_group)
|
||||
|
||||
# Device grid
|
||||
devices_scroll = QScrollArea()
|
||||
devices_widget = QWidget()
|
||||
self.devices_layout = QGridLayout(devices_widget)
|
||||
|
||||
# Create sample IoT devices
|
||||
devices_data = [
|
||||
("camera_001", "camera", "online"),
|
||||
("sensor_002", "sensor", "online"),
|
||||
("controller_003", "controller", "online"),
|
||||
("display_004", "display", "warning"),
|
||||
("gateway_005", "gateway", "online"),
|
||||
("sensor_006", "sensor", "offline"),
|
||||
("camera_007", "camera", "online"),
|
||||
("controller_008", "controller", "error"),
|
||||
]
|
||||
|
||||
for i, (device_id, device_type, status) in enumerate(devices_data):
|
||||
device_widget = IoTDeviceWidget(device_id, device_type, status)
|
||||
self.iot_devices[device_id] = device_widget
|
||||
self.devices_layout.addWidget(device_widget, i // 3, i % 3)
|
||||
|
||||
devices_scroll.setWidget(devices_widget)
|
||||
devices_scroll.setMaximumHeight(200)
|
||||
iot_layout.addWidget(devices_scroll)
|
||||
|
||||
# Device controls
|
||||
device_controls = QHBoxLayout()
|
||||
|
||||
refresh_devices_btn = QPushButton("🔄 Refresh")
|
||||
refresh_devices_btn.clicked.connect(self._refresh_devices)
|
||||
device_controls.addWidget(refresh_devices_btn)
|
||||
|
||||
add_device_btn = QPushButton("➕ Add Device")
|
||||
add_device_btn.clicked.connect(self._add_device)
|
||||
device_controls.addWidget(add_device_btn)
|
||||
|
||||
device_controls.addStretch()
|
||||
|
||||
iot_layout.addLayout(device_controls)
|
||||
|
||||
layout.addWidget(iot_group)
|
||||
|
||||
# Analytics section
|
||||
analytics_group = self._create_analytics_section()
|
||||
layout.addWidget(analytics_group)
|
||||
|
||||
return panel
|
||||
|
||||
def _create_analytics_section(self):
|
||||
"""Create analytics and reporting section"""
|
||||
section = QGroupBox("Traffic Analytics")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# Analytics tabs
|
||||
analytics_tabs = QTabWidget()
|
||||
|
||||
# Real-time tab
|
||||
realtime_tab = QWidget()
|
||||
realtime_layout = QVBoxLayout(realtime_tab)
|
||||
|
||||
# Real-time metrics
|
||||
metrics_layout = QGridLayout()
|
||||
|
||||
metrics_layout.addWidget(QLabel("Current Traffic Volume:"), 0, 0)
|
||||
self.volume_label = QLabel("Medium")
|
||||
metrics_layout.addWidget(self.volume_label, 0, 1)
|
||||
|
||||
metrics_layout.addWidget(QLabel("Average Wait Time:"), 1, 0)
|
||||
self.wait_time_label = QLabel("45 seconds")
|
||||
metrics_layout.addWidget(self.wait_time_label, 1, 1)
|
||||
|
||||
metrics_layout.addWidget(QLabel("Traffic Efficiency:"), 2, 0)
|
||||
self.efficiency_label = QLabel("87%")
|
||||
metrics_layout.addWidget(self.efficiency_label, 2, 1)
|
||||
|
||||
metrics_layout.addWidget(QLabel("Active Violations:"), 3, 0)
|
||||
self.violations_label = QLabel("3")
|
||||
metrics_layout.addWidget(self.violations_label, 3, 1)
|
||||
|
||||
realtime_layout.addLayout(metrics_layout)
|
||||
|
||||
# Traffic flow visualization (placeholder)
|
||||
flow_frame = QFrame()
|
||||
flow_frame.setFixedHeight(100)
|
||||
flow_frame.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #ecf0f1;
|
||||
border: 1px solid #bdc3c7;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
|
||||
flow_layout = QVBoxLayout(flow_frame)
|
||||
flow_label = QLabel("Traffic Flow Visualization\n(Real-time heatmap)")
|
||||
flow_label.setAlignment(Qt.AlignCenter)
|
||||
flow_label.setStyleSheet("color: #7f8c8d;")
|
||||
flow_layout.addWidget(flow_label)
|
||||
|
||||
realtime_layout.addWidget(flow_frame)
|
||||
|
||||
analytics_tabs.addTab(realtime_tab, "📊 Real-time")
|
||||
|
||||
# Historical tab
|
||||
historical_tab = QWidget()
|
||||
historical_layout = QVBoxLayout(historical_tab)
|
||||
|
||||
# Time range selector
|
||||
range_layout = QHBoxLayout()
|
||||
range_layout.addWidget(QLabel("Time Range:"))
|
||||
|
||||
time_range_combo = QComboBox()
|
||||
time_range_combo.addItems(["Last Hour", "Last 24 Hours", "Last Week", "Last Month"])
|
||||
range_layout.addWidget(time_range_combo)
|
||||
|
||||
range_layout.addStretch()
|
||||
|
||||
export_btn = QPushButton("📤 Export Report")
|
||||
export_btn.clicked.connect(self._export_analytics)
|
||||
range_layout.addWidget(export_btn)
|
||||
|
||||
historical_layout.addLayout(range_layout)
|
||||
|
||||
# Historical data display (placeholder)
|
||||
historical_frame = QFrame()
|
||||
historical_frame.setFixedHeight(120)
|
||||
historical_frame.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #ecf0f1;
|
||||
border: 1px solid #bdc3c7;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
|
||||
historical_layout_frame = QVBoxLayout(historical_frame)
|
||||
historical_label = QLabel("Historical Traffic Patterns\n(Charts and trends)")
|
||||
historical_label.setAlignment(Qt.AlignCenter)
|
||||
historical_label.setStyleSheet("color: #7f8c8d;")
|
||||
historical_layout_frame.addWidget(historical_label)
|
||||
|
||||
historical_layout.addWidget(historical_frame)
|
||||
|
||||
analytics_tabs.addTab(historical_tab, "📈 Historical")
|
||||
|
||||
layout.addWidget(analytics_tabs)
|
||||
|
||||
return section
|
||||
|
||||
def _on_traffic_control_changed(self, intersection_id, mode):
|
||||
"""Handle traffic control changes"""
|
||||
control_data = {
|
||||
'intersection': intersection_id,
|
||||
'mode': mode,
|
||||
'timestamp': QDateTime.currentDateTime().toString()
|
||||
}
|
||||
|
||||
self.traffic_control_changed.emit(control_data)
|
||||
print(f"🌉 Traffic control changed: {intersection_id} -> {mode}")
|
||||
|
||||
def _emergency_all_intersections(self):
|
||||
"""Activate emergency mode for all intersections"""
|
||||
for controller in self.traffic_controllers.values():
|
||||
controller._set_mode("emergency")
|
||||
|
||||
self.emergency_activated.emit("all_intersections")
|
||||
print("🌉 Emergency mode activated for all intersections")
|
||||
|
||||
def _clear_emergency_all(self):
|
||||
"""Clear emergency mode for all intersections"""
|
||||
for controller in self.traffic_controllers.values():
|
||||
controller._set_mode("auto")
|
||||
|
||||
print("🌉 Emergency mode cleared for all intersections")
|
||||
|
||||
def _toggle_adaptive_control(self, enabled):
|
||||
"""Toggle adaptive traffic control"""
|
||||
status = "enabled" if enabled else "disabled"
|
||||
print(f"🌉 Adaptive traffic control {status}")
|
||||
|
||||
def _optimize_traffic_flow(self):
|
||||
"""Optimize traffic flow patterns"""
|
||||
print("🌉 Optimizing traffic flow patterns")
|
||||
|
||||
# Simulate optimization results
|
||||
self.efficiency_label.setText("92%")
|
||||
self.wait_time_label.setText("38 seconds")
|
||||
|
||||
def _refresh_devices(self):
|
||||
"""Refresh IoT device status"""
|
||||
import random
|
||||
|
||||
statuses = ["online", "offline", "warning", "error"]
|
||||
|
||||
for device in self.iot_devices.values():
|
||||
# Randomly update some device statuses
|
||||
if random.random() < 0.3: # 30% chance to change status
|
||||
new_status = random.choice(statuses)
|
||||
device.set_status(new_status)
|
||||
|
||||
print("🌉 IoT devices refreshed")
|
||||
|
||||
def _add_device(self):
|
||||
"""Add new IoT device"""
|
||||
# Simulate adding a new device
|
||||
device_count = len(self.iot_devices) + 1
|
||||
device_id = f"device_{device_count:03d}"
|
||||
device_type = "sensor"
|
||||
|
||||
device_widget = IoTDeviceWidget(device_id, device_type, "online")
|
||||
self.iot_devices[device_id] = device_widget
|
||||
|
||||
# Add to grid
|
||||
row = (len(self.iot_devices) - 1) // 3
|
||||
col = (len(self.iot_devices) - 1) % 3
|
||||
self.devices_layout.addWidget(device_widget, row, col)
|
||||
|
||||
print(f"🌉 Added new IoT device: {device_id}")
|
||||
|
||||
def _export_analytics(self):
|
||||
"""Export traffic analytics"""
|
||||
print("🌉 Exporting traffic analytics")
|
||||
|
||||
def _update_intersection_data(self):
|
||||
"""Update real-time intersection data"""
|
||||
import random
|
||||
|
||||
# Simulate real-time data updates
|
||||
volumes = ["Low", "Medium", "High"]
|
||||
self.volume_label.setText(random.choice(volumes))
|
||||
|
||||
wait_time = random.randint(30, 60)
|
||||
self.wait_time_label.setText(f"{wait_time} seconds")
|
||||
|
||||
efficiency = random.randint(80, 95)
|
||||
self.efficiency_label.setText(f"{efficiency}%")
|
||||
|
||||
violations = random.randint(0, 5)
|
||||
self.violations_label.setText(str(violations))
|
||||
|
||||
def set_intersection_mode(self, intersection_id, mode):
|
||||
"""Set mode for specific intersection"""
|
||||
if intersection_id in self.traffic_controllers:
|
||||
self.traffic_controllers[intersection_id]._set_mode(mode)
|
||||
|
||||
def get_system_status(self):
|
||||
"""Get current system status"""
|
||||
online_devices = sum(1 for device in self.iot_devices.values()
|
||||
if device.status == "online")
|
||||
total_devices = len(self.iot_devices)
|
||||
|
||||
return {
|
||||
'intersections': len(self.traffic_controllers),
|
||||
'devices_online': online_devices,
|
||||
'devices_total': total_devices,
|
||||
'traffic_efficiency': self.efficiency_label.text(),
|
||||
'adaptive_control': self.adaptive_cb.isChecked()
|
||||
}
|
||||
702
qt_app_pyside1/ui/tabs/system_performance_tab.py
Normal file
702
qt_app_pyside1/ui/tabs/system_performance_tab.py
Normal file
@@ -0,0 +1,702 @@
|
||||
"""
|
||||
System Performance Tab - Grafana-like performance monitoring dashboard
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
|
||||
QGroupBox, QLabel, QPushButton, QComboBox,
|
||||
QSlider, QProgressBar, QFrame, QSplitter,
|
||||
QScrollArea, QCheckBox, QSpinBox, QTabWidget)
|
||||
from PySide6.QtCore import Qt, Signal, QTimer, QDateTime
|
||||
from PySide6.QtGui import QFont, QColor, QPainter, QPen, QBrush
|
||||
import random
|
||||
import math
|
||||
|
||||
class MetricCard(QFrame):
|
||||
"""Individual metric display card"""
|
||||
|
||||
def __init__(self, title, value, unit="", trend=None, color="#3498db", parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.title = title
|
||||
self.value = value
|
||||
self.unit = unit
|
||||
self.trend = trend
|
||||
self.color = color
|
||||
|
||||
self.setFixedSize(200, 120)
|
||||
self._setup_ui()
|
||||
self._apply_style()
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup metric card UI"""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(15, 10, 15, 10)
|
||||
|
||||
# Title
|
||||
title_label = QLabel(self.title)
|
||||
title_label.setFont(QFont("Segoe UI", 9))
|
||||
title_label.setStyleSheet("color: #7f8c8d; font-weight: 500;")
|
||||
layout.addWidget(title_label)
|
||||
|
||||
# Value with unit
|
||||
value_layout = QHBoxLayout()
|
||||
|
||||
value_label = QLabel(str(self.value))
|
||||
value_label.setFont(QFont("Segoe UI", 20, QFont.Bold))
|
||||
value_label.setStyleSheet(f"color: {self.color};")
|
||||
value_layout.addWidget(value_label)
|
||||
|
||||
if self.unit:
|
||||
unit_label = QLabel(self.unit)
|
||||
unit_label.setFont(QFont("Segoe UI", 10))
|
||||
unit_label.setStyleSheet("color: #95a5a6;")
|
||||
unit_label.setAlignment(Qt.AlignBottom)
|
||||
value_layout.addWidget(unit_label)
|
||||
|
||||
value_layout.addStretch()
|
||||
layout.addLayout(value_layout)
|
||||
|
||||
# Trend indicator
|
||||
if self.trend is not None:
|
||||
trend_label = QLabel(f"{'↗' if self.trend > 0 else '↘' if self.trend < 0 else '→'} {abs(self.trend):.1f}%")
|
||||
trend_color = "#27ae60" if self.trend > 0 else "#e74c3c" if self.trend < 0 else "#95a5a6"
|
||||
trend_label.setStyleSheet(f"color: {trend_color}; font-size: 8pt;")
|
||||
layout.addWidget(trend_label)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
def _apply_style(self):
|
||||
"""Apply card styling"""
|
||||
self.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: white;
|
||||
border: 1px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
margin: 5px;
|
||||
}
|
||||
QFrame:hover {
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
""")
|
||||
|
||||
def update_value(self, value, trend=None):
|
||||
"""Update metric value"""
|
||||
self.value = value
|
||||
self.trend = trend
|
||||
# Re-setup UI to reflect changes
|
||||
# Clear and rebuild
|
||||
for i in reversed(range(self.layout().count())):
|
||||
child = self.layout().itemAt(i).widget()
|
||||
if child:
|
||||
child.setParent(None)
|
||||
self._setup_ui()
|
||||
|
||||
class SimpleChart(QWidget):
|
||||
"""Simple line chart widget"""
|
||||
|
||||
def __init__(self, title="Chart", width=300, height=200, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.title = title
|
||||
self.data_points = []
|
||||
self.max_points = 50
|
||||
self.color = QColor(52, 152, 219)
|
||||
|
||||
self.setFixedSize(width, height)
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: white;
|
||||
border: 1px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
}
|
||||
""")
|
||||
|
||||
def add_data_point(self, value):
|
||||
"""Add a new data point"""
|
||||
self.data_points.append(value)
|
||||
if len(self.data_points) > self.max_points:
|
||||
self.data_points.pop(0)
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""Custom paint event for chart"""
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
|
||||
# Draw background
|
||||
painter.fillRect(self.rect(), QColor(255, 255, 255))
|
||||
|
||||
# Draw title
|
||||
painter.setPen(QColor(44, 62, 80))
|
||||
painter.setFont(QFont("Segoe UI", 10, QFont.Bold))
|
||||
painter.drawText(10, 20, self.title)
|
||||
|
||||
if len(self.data_points) < 2:
|
||||
return
|
||||
|
||||
# Calculate chart area
|
||||
chart_rect = self.rect().adjusted(20, 30, -20, -20)
|
||||
|
||||
# Find min and max values
|
||||
min_val = min(self.data_points)
|
||||
max_val = max(self.data_points)
|
||||
if min_val == max_val:
|
||||
max_val = min_val + 1
|
||||
|
||||
# Draw grid lines
|
||||
painter.setPen(QPen(QColor(236, 240, 241), 1))
|
||||
for i in range(5):
|
||||
y = chart_rect.top() + (chart_rect.height() * i / 4)
|
||||
painter.drawLine(chart_rect.left(), y, chart_rect.right(), y)
|
||||
|
||||
# Draw data line
|
||||
painter.setPen(QPen(self.color, 2))
|
||||
for i in range(1, len(self.data_points)):
|
||||
x1 = chart_rect.left() + (chart_rect.width() * (i-1) / (len(self.data_points)-1))
|
||||
y1 = chart_rect.bottom() - (chart_rect.height() * (self.data_points[i-1] - min_val) / (max_val - min_val))
|
||||
|
||||
x2 = chart_rect.left() + (chart_rect.width() * i / (len(self.data_points)-1))
|
||||
y2 = chart_rect.bottom() - (chart_rect.height() * (self.data_points[i] - min_val) / (max_val - min_val))
|
||||
|
||||
painter.drawLine(x1, y1, x2, y2)
|
||||
|
||||
class SystemPerformanceTab(QWidget):
|
||||
"""
|
||||
System Performance Tab with Grafana-like monitoring dashboard
|
||||
|
||||
Features:
|
||||
- Real-time performance metrics
|
||||
- Interactive charts and graphs
|
||||
- System resource monitoring
|
||||
- Performance alerts and thresholds
|
||||
- Historical data analysis
|
||||
- Export capabilities
|
||||
"""
|
||||
|
||||
# Signals
|
||||
performance_alert = Signal(str, str) # alert_type, message
|
||||
threshold_exceeded = Signal(str, float) # metric_name, value
|
||||
export_requested = Signal(str) # export_type
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.performance_data = {}
|
||||
self.alerts_enabled = True
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
# Timer for real-time updates
|
||||
self.update_timer = QTimer()
|
||||
self.update_timer.timeout.connect(self._update_metrics)
|
||||
self.update_timer.start(1000) # Update every second
|
||||
|
||||
print("🔥 System Performance Tab initialized")
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup the performance monitoring UI"""
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Header with controls
|
||||
header = self._create_header()
|
||||
layout.addWidget(header)
|
||||
|
||||
# Main content tabs
|
||||
content_tabs = self._create_content_tabs()
|
||||
layout.addWidget(content_tabs)
|
||||
|
||||
def _create_header(self):
|
||||
"""Create header with system overview and controls"""
|
||||
header = QFrame()
|
||||
header.setFixedHeight(60)
|
||||
header.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #2c3e50;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
""")
|
||||
|
||||
layout = QHBoxLayout(header)
|
||||
layout.setContentsMargins(20, 10, 20, 10)
|
||||
|
||||
# System status
|
||||
status_layout = QVBoxLayout()
|
||||
|
||||
system_label = QLabel("🔥 System Performance Monitor")
|
||||
system_label.setFont(QFont("Segoe UI", 14, QFont.Bold))
|
||||
system_label.setStyleSheet("color: white;")
|
||||
status_layout.addWidget(system_label)
|
||||
|
||||
self.system_health_label = QLabel("System Health: Optimal")
|
||||
self.system_health_label.setFont(QFont("Segoe UI", 9))
|
||||
self.system_health_label.setStyleSheet("color: #27ae60;")
|
||||
status_layout.addWidget(self.system_health_label)
|
||||
|
||||
layout.addLayout(status_layout)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Quick metrics
|
||||
quick_metrics = QHBoxLayout()
|
||||
|
||||
# CPU usage
|
||||
cpu_layout = QVBoxLayout()
|
||||
cpu_layout.addWidget(QLabel("CPU"))
|
||||
self.cpu_progress = QProgressBar()
|
||||
self.cpu_progress.setRange(0, 100)
|
||||
self.cpu_progress.setFixedWidth(80)
|
||||
cpu_layout.addWidget(self.cpu_progress)
|
||||
quick_metrics.addLayout(cpu_layout)
|
||||
|
||||
# Memory usage
|
||||
memory_layout = QVBoxLayout()
|
||||
memory_layout.addWidget(QLabel("Memory"))
|
||||
self.memory_progress = QProgressBar()
|
||||
self.memory_progress.setRange(0, 100)
|
||||
self.memory_progress.setFixedWidth(80)
|
||||
memory_layout.addWidget(self.memory_progress)
|
||||
quick_metrics.addLayout(memory_layout)
|
||||
|
||||
# GPU usage
|
||||
gpu_layout = QVBoxLayout()
|
||||
gpu_layout.addWidget(QLabel("GPU"))
|
||||
self.gpu_progress = QProgressBar()
|
||||
self.gpu_progress.setRange(0, 100)
|
||||
self.gpu_progress.setFixedWidth(80)
|
||||
gpu_layout.addWidget(self.gpu_progress)
|
||||
quick_metrics.addLayout(gpu_layout)
|
||||
|
||||
# Style progress bars
|
||||
progress_style = """
|
||||
QProgressBar {
|
||||
border: 1px solid #34495e;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 8pt;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #3498db;
|
||||
border-radius: 2px;
|
||||
}
|
||||
QLabel {
|
||||
color: white;
|
||||
font-size: 8pt;
|
||||
text-align: center;
|
||||
}
|
||||
"""
|
||||
for widget in [self.cpu_progress, self.memory_progress, self.gpu_progress]:
|
||||
widget.setStyleSheet(progress_style)
|
||||
|
||||
layout.addLayout(quick_metrics)
|
||||
|
||||
# Control buttons
|
||||
controls_layout = QVBoxLayout()
|
||||
|
||||
alerts_btn = QPushButton("🔔 Alerts")
|
||||
alerts_btn.setFixedSize(80, 25)
|
||||
alerts_btn.setCheckable(True)
|
||||
alerts_btn.setChecked(True)
|
||||
alerts_btn.toggled.connect(self._toggle_alerts)
|
||||
controls_layout.addWidget(alerts_btn)
|
||||
|
||||
export_btn = QPushButton("📊 Export")
|
||||
export_btn.setFixedSize(80, 25)
|
||||
export_btn.clicked.connect(self._export_performance_data)
|
||||
controls_layout.addWidget(export_btn)
|
||||
|
||||
# Style buttons
|
||||
button_style = """
|
||||
QPushButton {
|
||||
background-color: #34495e;
|
||||
color: white;
|
||||
border: 1px solid #34495e;
|
||||
border-radius: 4px;
|
||||
font-size: 8pt;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #4a6741;
|
||||
}
|
||||
QPushButton:checked {
|
||||
background-color: #27ae60;
|
||||
}
|
||||
"""
|
||||
alerts_btn.setStyleSheet(button_style)
|
||||
export_btn.setStyleSheet(button_style)
|
||||
|
||||
layout.addLayout(controls_layout)
|
||||
|
||||
return header
|
||||
|
||||
def _create_content_tabs(self):
|
||||
"""Create main content tabs"""
|
||||
tabs = QTabWidget()
|
||||
|
||||
# Overview tab
|
||||
overview_tab = self._create_overview_tab()
|
||||
tabs.addTab(overview_tab, "📊 Overview")
|
||||
|
||||
# Detailed metrics tab
|
||||
metrics_tab = self._create_metrics_tab()
|
||||
tabs.addTab(metrics_tab, "📈 Detailed Metrics")
|
||||
|
||||
# Alerts tab
|
||||
alerts_tab = self._create_alerts_tab()
|
||||
tabs.addTab(alerts_tab, "🚨 Alerts")
|
||||
|
||||
# Settings tab
|
||||
settings_tab = self._create_settings_tab()
|
||||
tabs.addTab(settings_tab, "⚙️ Settings")
|
||||
|
||||
return tabs
|
||||
|
||||
def _create_overview_tab(self):
|
||||
"""Create overview dashboard tab"""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
# Metric cards grid
|
||||
cards_scroll = QScrollArea()
|
||||
cards_widget = QWidget()
|
||||
cards_layout = QGridLayout(cards_widget)
|
||||
|
||||
# Create metric cards
|
||||
self.metric_cards = {}
|
||||
|
||||
cards_data = [
|
||||
("FPS", 0, "fps", None, "#3498db"),
|
||||
("Processing Time", 0, "ms", None, "#e74c3c"),
|
||||
("CPU Usage", 0, "%", None, "#f39c12"),
|
||||
("Memory Usage", 0, "%", None, "#9b59b6"),
|
||||
("GPU Usage", 0, "%", None, "#1abc9c"),
|
||||
("Network I/O", 0, "MB/s", None, "#34495e"),
|
||||
("Disk I/O", 0, "MB/s", None, "#95a5a6"),
|
||||
("Temperature", 0, "°C", None, "#e67e22"),
|
||||
]
|
||||
|
||||
for i, (title, value, unit, trend, color) in enumerate(cards_data):
|
||||
card = MetricCard(title, value, unit, trend, color)
|
||||
self.metric_cards[title.lower().replace(" ", "_")] = card
|
||||
cards_layout.addWidget(card, i // 4, i % 4)
|
||||
|
||||
cards_scroll.setWidget(cards_widget)
|
||||
cards_scroll.setMaximumHeight(280)
|
||||
layout.addWidget(cards_scroll)
|
||||
|
||||
# Charts section
|
||||
charts_layout = QHBoxLayout()
|
||||
|
||||
# FPS chart
|
||||
self.fps_chart = SimpleChart("FPS Over Time", 400, 200)
|
||||
self.fps_chart.color = QColor(52, 152, 219)
|
||||
charts_layout.addWidget(self.fps_chart)
|
||||
|
||||
# CPU/Memory chart
|
||||
self.resource_chart = SimpleChart("Resource Usage", 400, 200)
|
||||
self.resource_chart.color = QColor(243, 156, 18)
|
||||
charts_layout.addWidget(self.resource_chart)
|
||||
|
||||
layout.addLayout(charts_layout)
|
||||
|
||||
return tab
|
||||
|
||||
def _create_metrics_tab(self):
|
||||
"""Create detailed metrics tab"""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
# Metrics filter
|
||||
filter_layout = QHBoxLayout()
|
||||
|
||||
filter_layout.addWidget(QLabel("Category:"))
|
||||
category_combo = QComboBox()
|
||||
category_combo.addItems(["All", "Performance", "Resources", "Network", "Storage"])
|
||||
filter_layout.addWidget(category_combo)
|
||||
|
||||
filter_layout.addWidget(QLabel("Time Range:"))
|
||||
time_combo = QComboBox()
|
||||
time_combo.addItems(["Last 5 minutes", "Last hour", "Last 24 hours", "Last week"])
|
||||
filter_layout.addWidget(time_combo)
|
||||
|
||||
filter_layout.addStretch()
|
||||
|
||||
refresh_btn = QPushButton("🔄 Refresh")
|
||||
refresh_btn.clicked.connect(self._refresh_metrics)
|
||||
filter_layout.addWidget(refresh_btn)
|
||||
|
||||
layout.addLayout(filter_layout)
|
||||
|
||||
# Detailed charts area
|
||||
charts_scroll = QScrollArea()
|
||||
charts_widget = QWidget()
|
||||
charts_layout = QVBoxLayout(charts_widget)
|
||||
|
||||
# Create multiple detailed charts
|
||||
chart_titles = [
|
||||
"Frame Processing Pipeline",
|
||||
"Detection Performance",
|
||||
"Memory Allocation",
|
||||
"GPU Utilization",
|
||||
"Network Throughput",
|
||||
"Disk I/O Performance"
|
||||
]
|
||||
|
||||
self.detailed_charts = {}
|
||||
for title in chart_titles:
|
||||
chart = SimpleChart(title, 700, 150)
|
||||
self.detailed_charts[title.lower().replace(" ", "_")] = chart
|
||||
charts_layout.addWidget(chart)
|
||||
|
||||
charts_scroll.setWidget(charts_widget)
|
||||
layout.addWidget(charts_scroll)
|
||||
|
||||
return tab
|
||||
|
||||
def _create_alerts_tab(self):
|
||||
"""Create alerts configuration tab"""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
# Alert thresholds
|
||||
thresholds_group = QGroupBox("Alert Thresholds")
|
||||
thresholds_layout = QGridLayout(thresholds_group)
|
||||
|
||||
thresholds = [
|
||||
("CPU Usage", 80, "%"),
|
||||
("Memory Usage", 85, "%"),
|
||||
("GPU Usage", 90, "%"),
|
||||
("FPS Drop", 15, "fps"),
|
||||
("Processing Time", 100, "ms"),
|
||||
("Temperature", 75, "°C")
|
||||
]
|
||||
|
||||
self.threshold_controls = {}
|
||||
for i, (metric, default_value, unit) in enumerate(thresholds):
|
||||
# Label
|
||||
thresholds_layout.addWidget(QLabel(f"{metric}:"), i, 0)
|
||||
|
||||
# Slider
|
||||
slider = QSlider(Qt.Horizontal)
|
||||
slider.setRange(1, 100)
|
||||
slider.setValue(default_value)
|
||||
thresholds_layout.addWidget(slider, i, 1)
|
||||
|
||||
# Value label
|
||||
value_label = QLabel(f"{default_value} {unit}")
|
||||
thresholds_layout.addWidget(value_label, i, 2)
|
||||
|
||||
# Connect slider to label
|
||||
slider.valueChanged.connect(
|
||||
lambda v, label=value_label, u=unit: label.setText(f"{v} {u}")
|
||||
)
|
||||
|
||||
self.threshold_controls[metric.lower().replace(" ", "_")] = slider
|
||||
|
||||
layout.addWidget(thresholds_group)
|
||||
|
||||
# Alert history
|
||||
history_group = QGroupBox("Recent Alerts")
|
||||
history_layout = QVBoxLayout(history_group)
|
||||
|
||||
self.alerts_text = QLabel("No recent alerts")
|
||||
self.alerts_text.setStyleSheet("""
|
||||
QLabel {
|
||||
background-color: #ecf0f1;
|
||||
border: 1px solid #bdc3c7;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
min-height: 200px;
|
||||
}
|
||||
""")
|
||||
self.alerts_text.setAlignment(Qt.AlignTop)
|
||||
self.alerts_text.setWordWrap(True)
|
||||
history_layout.addWidget(self.alerts_text)
|
||||
|
||||
layout.addWidget(history_group)
|
||||
|
||||
return tab
|
||||
|
||||
def _create_settings_tab(self):
|
||||
"""Create performance settings tab"""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
# Update intervals
|
||||
intervals_group = QGroupBox("Update Intervals")
|
||||
intervals_layout = QGridLayout(intervals_group)
|
||||
|
||||
intervals_layout.addWidget(QLabel("Metrics Update:"), 0, 0)
|
||||
metrics_interval = QSpinBox()
|
||||
metrics_interval.setRange(500, 10000)
|
||||
metrics_interval.setValue(1000)
|
||||
metrics_interval.setSuffix(" ms")
|
||||
intervals_layout.addWidget(metrics_interval, 0, 1)
|
||||
|
||||
intervals_layout.addWidget(QLabel("Charts Update:"), 1, 0)
|
||||
charts_interval = QSpinBox()
|
||||
charts_interval.setRange(1000, 30000)
|
||||
charts_interval.setValue(5000)
|
||||
charts_interval.setSuffix(" ms")
|
||||
intervals_layout.addWidget(charts_interval, 1, 1)
|
||||
|
||||
layout.addWidget(intervals_group)
|
||||
|
||||
# Display options
|
||||
display_group = QGroupBox("Display Options")
|
||||
display_layout = QVBoxLayout(display_group)
|
||||
|
||||
self.show_grid_cb = QCheckBox("Show Chart Grid Lines")
|
||||
self.show_grid_cb.setChecked(True)
|
||||
display_layout.addWidget(self.show_grid_cb)
|
||||
|
||||
self.smooth_charts_cb = QCheckBox("Smooth Chart Animations")
|
||||
self.smooth_charts_cb.setChecked(True)
|
||||
display_layout.addWidget(self.smooth_charts_cb)
|
||||
|
||||
self.auto_scale_cb = QCheckBox("Auto-scale Chart Axes")
|
||||
self.auto_scale_cb.setChecked(True)
|
||||
display_layout.addWidget(self.auto_scale_cb)
|
||||
|
||||
layout.addWidget(display_group)
|
||||
|
||||
# Performance optimization
|
||||
optimization_group = QGroupBox("Performance Optimization")
|
||||
optimization_layout = QVBoxLayout(optimization_group)
|
||||
|
||||
self.reduce_quality_cb = QCheckBox("Reduce Chart Quality for Better Performance")
|
||||
optimization_layout.addWidget(self.reduce_quality_cb)
|
||||
|
||||
self.limit_history_cb = QCheckBox("Limit Historical Data (50 points)")
|
||||
self.limit_history_cb.setChecked(True)
|
||||
optimization_layout.addWidget(self.limit_history_cb)
|
||||
|
||||
layout.addWidget(optimization_group)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
return tab
|
||||
|
||||
def _update_metrics(self):
|
||||
"""Update all performance metrics (called by timer)"""
|
||||
# Simulate real metrics (replace with actual system monitoring)
|
||||
import random
|
||||
import time
|
||||
|
||||
# Generate simulated metrics
|
||||
current_time = time.time()
|
||||
metrics = {
|
||||
'fps': random.uniform(25, 30),
|
||||
'processing_time': random.uniform(20, 50),
|
||||
'cpu_usage': random.uniform(30, 70),
|
||||
'memory_usage': random.uniform(40, 80),
|
||||
'gpu_usage': random.uniform(50, 90),
|
||||
'network_io': random.uniform(5, 25),
|
||||
'disk_io': random.uniform(1, 10),
|
||||
'temperature': random.uniform(45, 65)
|
||||
}
|
||||
|
||||
# Update metric cards
|
||||
for metric_name, value in metrics.items():
|
||||
if metric_name in self.metric_cards:
|
||||
# Calculate trend (simplified)
|
||||
old_value = getattr(self, f'_last_{metric_name}', value)
|
||||
trend = ((value - old_value) / old_value * 100) if old_value != 0 else 0
|
||||
|
||||
self.metric_cards[metric_name].update_value(f"{value:.1f}", trend)
|
||||
setattr(self, f'_last_{metric_name}', value)
|
||||
|
||||
# Update progress bars in header
|
||||
self.cpu_progress.setValue(int(metrics['cpu_usage']))
|
||||
self.memory_progress.setValue(int(metrics['memory_usage']))
|
||||
self.gpu_progress.setValue(int(metrics['gpu_usage']))
|
||||
|
||||
# Update system health
|
||||
avg_usage = (metrics['cpu_usage'] + metrics['memory_usage'] + metrics['gpu_usage']) / 3
|
||||
if avg_usage < 50:
|
||||
health_status = "Optimal"
|
||||
health_color = "#27ae60"
|
||||
elif avg_usage < 75:
|
||||
health_status = "Good"
|
||||
health_color = "#f39c12"
|
||||
else:
|
||||
health_status = "High Load"
|
||||
health_color = "#e74c3c"
|
||||
|
||||
self.system_health_label.setText(f"System Health: {health_status}")
|
||||
self.system_health_label.setStyleSheet(f"color: {health_color};")
|
||||
|
||||
# Update charts
|
||||
self.fps_chart.add_data_point(metrics['fps'])
|
||||
self.resource_chart.add_data_point(metrics['cpu_usage'])
|
||||
|
||||
# Update detailed charts
|
||||
for chart_name, chart in self.detailed_charts.items():
|
||||
# Use different metrics for different charts
|
||||
if 'processing' in chart_name:
|
||||
chart.add_data_point(metrics['processing_time'])
|
||||
elif 'memory' in chart_name:
|
||||
chart.add_data_point(metrics['memory_usage'])
|
||||
elif 'gpu' in chart_name:
|
||||
chart.add_data_point(metrics['gpu_usage'])
|
||||
else:
|
||||
chart.add_data_point(random.uniform(10, 90))
|
||||
|
||||
# Check for threshold alerts
|
||||
if self.alerts_enabled:
|
||||
self._check_thresholds(metrics)
|
||||
|
||||
def _check_thresholds(self, metrics):
|
||||
"""Check if any metrics exceed thresholds"""
|
||||
thresholds = {
|
||||
'cpu_usage': 80,
|
||||
'memory_usage': 85,
|
||||
'gpu_usage': 90,
|
||||
'processing_time': 100,
|
||||
'temperature': 75
|
||||
}
|
||||
|
||||
for metric, value in metrics.items():
|
||||
if metric in thresholds and value > thresholds[metric]:
|
||||
alert_msg = f"{metric.replace('_', ' ').title()} is high: {value:.1f}"
|
||||
self.performance_alert.emit("warning", alert_msg)
|
||||
|
||||
def _toggle_alerts(self, enabled):
|
||||
"""Toggle performance alerts"""
|
||||
self.alerts_enabled = enabled
|
||||
status = "enabled" if enabled else "disabled"
|
||||
print(f"🔥 Performance alerts {status}")
|
||||
|
||||
def _export_performance_data(self):
|
||||
"""Export performance data"""
|
||||
self.export_requested.emit("performance_metrics")
|
||||
print("🔥 Performance data exported")
|
||||
|
||||
def _refresh_metrics(self):
|
||||
"""Refresh all metrics manually"""
|
||||
print("🔥 Refreshing performance metrics")
|
||||
# Force immediate update
|
||||
self._update_metrics()
|
||||
|
||||
def add_custom_metric(self, name, value, unit=""):
|
||||
"""Add a custom performance metric"""
|
||||
if hasattr(self, 'metric_cards'):
|
||||
# Add new metric card if space available
|
||||
print(f"🔥 Added custom metric: {name} = {value} {unit}")
|
||||
|
||||
def set_alert_threshold(self, metric_name, threshold_value):
|
||||
"""Set alert threshold for a specific metric"""
|
||||
if metric_name in self.threshold_controls:
|
||||
self.threshold_controls[metric_name].setValue(int(threshold_value))
|
||||
print(f"🔥 Alert threshold set: {metric_name} = {threshold_value}")
|
||||
|
||||
def get_performance_summary(self):
|
||||
"""Get current performance summary"""
|
||||
return {
|
||||
'status': self.system_health_label.text(),
|
||||
'cpu': self.cpu_progress.value(),
|
||||
'memory': self.memory_progress.value(),
|
||||
'gpu': self.gpu_progress.value(),
|
||||
'alerts_enabled': self.alerts_enabled
|
||||
}
|
||||
400
qt_app_pyside1/ui/tabs/video_analysis_tab.py
Normal file
400
qt_app_pyside1/ui/tabs/video_analysis_tab.py
Normal file
@@ -0,0 +1,400 @@
|
||||
"""
|
||||
Video Analysis Tab - Advanced video analysis with ROI configuration
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QSplitter,
|
||||
QGroupBox, QLabel, QPushButton, QComboBox,
|
||||
QListWidget, QTextEdit, QSlider, QCheckBox,
|
||||
QFrame, QGridLayout, QSpinBox, QProgressBar)
|
||||
from PySide6.QtCore import Qt, Signal, QTimer
|
||||
from PySide6.QtGui import QFont, QPixmap
|
||||
|
||||
class VideoAnalysisTab(QWidget):
|
||||
"""
|
||||
Video Analysis Tab with ROI configuration and advanced analytics
|
||||
|
||||
Features:
|
||||
- ROI (Region of Interest) drawing and management
|
||||
- Traffic pattern analysis
|
||||
- Speed measurement zones
|
||||
- Counting lines configuration
|
||||
- Heatmap visualization
|
||||
- Advanced analytics reporting
|
||||
"""
|
||||
|
||||
# Signals
|
||||
roi_changed = Signal(dict)
|
||||
analysis_started = Signal(str)
|
||||
export_requested = Signal(str, str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.roi_data = {}
|
||||
self.analysis_results = {}
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
print("🎬 Video Analysis Tab initialized")
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup the video analysis UI"""
|
||||
# Main splitter
|
||||
main_splitter = QSplitter(Qt.Horizontal)
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.layout.addWidget(main_splitter)
|
||||
|
||||
# Left panel - Video and ROI editor
|
||||
left_panel = self._create_left_panel()
|
||||
main_splitter.addWidget(left_panel)
|
||||
|
||||
# Right panel - Analysis controls and results
|
||||
right_panel = self._create_right_panel()
|
||||
main_splitter.addWidget(right_panel)
|
||||
|
||||
# Set splitter proportions
|
||||
main_splitter.setSizes([800, 400])
|
||||
|
||||
def _create_left_panel(self):
|
||||
"""Create left panel with video analysis view"""
|
||||
panel = QFrame()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# Header with controls
|
||||
header = self._create_analysis_header()
|
||||
layout.addWidget(header)
|
||||
|
||||
# Main analysis view (placeholder for now)
|
||||
self.analysis_view = QFrame()
|
||||
self.analysis_view.setMinimumSize(600, 400)
|
||||
self.analysis_view.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #2c3e50;
|
||||
border: 2px solid #34495e;
|
||||
border-radius: 8px;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.analysis_view, 1)
|
||||
|
||||
# ROI tools
|
||||
roi_tools = self._create_roi_tools()
|
||||
layout.addWidget(roi_tools)
|
||||
|
||||
return panel
|
||||
|
||||
def _create_analysis_header(self):
|
||||
"""Create analysis header with controls"""
|
||||
header = QFrame()
|
||||
header.setFixedHeight(50)
|
||||
layout = QHBoxLayout(header)
|
||||
|
||||
# Source selection
|
||||
layout.addWidget(QLabel("Source:"))
|
||||
self.source_combo = QComboBox()
|
||||
self.source_combo.addItems(["Live Camera 1", "Live Camera 2", "Recorded Video", "Import Video"])
|
||||
layout.addWidget(self.source_combo)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Analysis controls
|
||||
self.start_analysis_btn = QPushButton("▶️ Start Analysis")
|
||||
self.start_analysis_btn.clicked.connect(self._start_analysis)
|
||||
layout.addWidget(self.start_analysis_btn)
|
||||
|
||||
self.stop_analysis_btn = QPushButton("⏹️ Stop")
|
||||
self.stop_analysis_btn.setEnabled(False)
|
||||
self.stop_analysis_btn.clicked.connect(self._stop_analysis)
|
||||
layout.addWidget(self.stop_analysis_btn)
|
||||
|
||||
# Export button
|
||||
export_btn = QPushButton("📊 Export Results")
|
||||
export_btn.clicked.connect(self._export_results)
|
||||
layout.addWidget(export_btn)
|
||||
|
||||
return header
|
||||
|
||||
def _create_roi_tools(self):
|
||||
"""Create ROI drawing tools"""
|
||||
tools = QGroupBox("ROI Tools")
|
||||
tools.setFixedHeight(80)
|
||||
layout = QHBoxLayout(tools)
|
||||
|
||||
# ROI type selection
|
||||
layout.addWidget(QLabel("ROI Type:"))
|
||||
self.roi_type_combo = QComboBox()
|
||||
self.roi_type_combo.addItems([
|
||||
"Traffic Count Zone",
|
||||
"Speed Measurement Zone",
|
||||
"Restricted Area",
|
||||
"Parking Detection",
|
||||
"Crosswalk Zone"
|
||||
])
|
||||
layout.addWidget(self.roi_type_combo)
|
||||
|
||||
# ROI drawing buttons
|
||||
draw_rect_btn = QPushButton("📐 Rectangle")
|
||||
draw_rect_btn.clicked.connect(lambda: self._set_draw_mode("rectangle"))
|
||||
layout.addWidget(draw_rect_btn)
|
||||
|
||||
draw_poly_btn = QPushButton("🔗 Polygon")
|
||||
draw_poly_btn.clicked.connect(lambda: self._set_draw_mode("polygon"))
|
||||
layout.addWidget(draw_poly_btn)
|
||||
|
||||
draw_line_btn = QPushButton("📏 Line")
|
||||
draw_line_btn.clicked.connect(lambda: self._set_draw_mode("line"))
|
||||
layout.addWidget(draw_line_btn)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Clear ROI
|
||||
clear_btn = QPushButton("🗑️ Clear All")
|
||||
clear_btn.clicked.connect(self._clear_all_roi)
|
||||
layout.addWidget(clear_btn)
|
||||
|
||||
return tools
|
||||
|
||||
def _create_right_panel(self):
|
||||
"""Create right panel with controls and results"""
|
||||
panel = QFrame()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# ROI list
|
||||
roi_section = self._create_roi_section()
|
||||
layout.addWidget(roi_section)
|
||||
|
||||
# Analysis settings
|
||||
settings_section = self._create_settings_section()
|
||||
layout.addWidget(settings_section)
|
||||
|
||||
# Results section
|
||||
results_section = self._create_results_section()
|
||||
layout.addWidget(results_section)
|
||||
|
||||
return panel
|
||||
|
||||
def _create_roi_section(self):
|
||||
"""Create ROI management section"""
|
||||
section = QGroupBox("ROI Management")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# ROI list
|
||||
self.roi_list = QListWidget()
|
||||
self.roi_list.setMaximumHeight(120)
|
||||
layout.addWidget(self.roi_list)
|
||||
|
||||
# ROI controls
|
||||
roi_controls = QHBoxLayout()
|
||||
|
||||
edit_btn = QPushButton("✏️ Edit")
|
||||
edit_btn.clicked.connect(self._edit_selected_roi)
|
||||
roi_controls.addWidget(edit_btn)
|
||||
|
||||
delete_btn = QPushButton("🗑️ Delete")
|
||||
delete_btn.clicked.connect(self._delete_selected_roi)
|
||||
roi_controls.addWidget(delete_btn)
|
||||
|
||||
duplicate_btn = QPushButton("📋 Copy")
|
||||
duplicate_btn.clicked.connect(self._duplicate_selected_roi)
|
||||
roi_controls.addWidget(duplicate_btn)
|
||||
|
||||
layout.addLayout(roi_controls)
|
||||
|
||||
return section
|
||||
|
||||
def _create_settings_section(self):
|
||||
"""Create analysis settings section"""
|
||||
section = QGroupBox("Analysis Settings")
|
||||
layout = QGridLayout(section)
|
||||
|
||||
# Detection sensitivity
|
||||
layout.addWidget(QLabel("Sensitivity:"), 0, 0)
|
||||
self.sensitivity_slider = QSlider(Qt.Horizontal)
|
||||
self.sensitivity_slider.setRange(1, 10)
|
||||
self.sensitivity_slider.setValue(5)
|
||||
layout.addWidget(self.sensitivity_slider, 0, 1)
|
||||
|
||||
self.sensitivity_label = QLabel("5")
|
||||
layout.addWidget(self.sensitivity_label, 0, 2)
|
||||
|
||||
# Minimum object size
|
||||
layout.addWidget(QLabel("Min Size:"), 1, 0)
|
||||
self.min_size_spin = QSpinBox()
|
||||
self.min_size_spin.setRange(10, 1000)
|
||||
self.min_size_spin.setValue(50)
|
||||
self.min_size_spin.setSuffix(" px")
|
||||
layout.addWidget(self.min_size_spin, 1, 1, 1, 2)
|
||||
|
||||
# Analysis options
|
||||
self.track_objects_cb = QCheckBox("Track Objects")
|
||||
self.track_objects_cb.setChecked(True)
|
||||
layout.addWidget(self.track_objects_cb, 2, 0, 1, 3)
|
||||
|
||||
self.speed_analysis_cb = QCheckBox("Speed Analysis")
|
||||
self.speed_analysis_cb.setChecked(True)
|
||||
layout.addWidget(self.speed_analysis_cb, 3, 0, 1, 3)
|
||||
|
||||
self.direction_analysis_cb = QCheckBox("Direction Analysis")
|
||||
self.direction_analysis_cb.setChecked(False)
|
||||
layout.addWidget(self.direction_analysis_cb, 4, 0, 1, 3)
|
||||
|
||||
# Connect sensitivity slider to label
|
||||
self.sensitivity_slider.valueChanged.connect(
|
||||
lambda v: self.sensitivity_label.setText(str(v))
|
||||
)
|
||||
|
||||
return section
|
||||
|
||||
def _create_results_section(self):
|
||||
"""Create analysis results section"""
|
||||
section = QGroupBox("Analysis Results")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# Results summary
|
||||
summary_layout = QGridLayout()
|
||||
|
||||
summary_layout.addWidget(QLabel("Objects Detected:"), 0, 0)
|
||||
self.objects_count_label = QLabel("0")
|
||||
summary_layout.addWidget(self.objects_count_label, 0, 1)
|
||||
|
||||
summary_layout.addWidget(QLabel("Avg Speed:"), 1, 0)
|
||||
self.avg_speed_label = QLabel("0.0 km/h")
|
||||
summary_layout.addWidget(self.avg_speed_label, 1, 1)
|
||||
|
||||
summary_layout.addWidget(QLabel("Violations:"), 2, 0)
|
||||
self.violations_count_label = QLabel("0")
|
||||
summary_layout.addWidget(self.violations_count_label, 2, 1)
|
||||
|
||||
layout.addLayout(summary_layout)
|
||||
|
||||
# Progress bar for analysis
|
||||
self.analysis_progress = QProgressBar()
|
||||
self.analysis_progress.setVisible(False)
|
||||
layout.addWidget(self.analysis_progress)
|
||||
|
||||
# Detailed results
|
||||
self.results_text = QTextEdit()
|
||||
self.results_text.setMaximumHeight(150)
|
||||
self.results_text.setPlaceholderText("Analysis results will appear here...")
|
||||
layout.addWidget(self.results_text)
|
||||
|
||||
# Export options
|
||||
export_layout = QHBoxLayout()
|
||||
|
||||
export_csv_btn = QPushButton("📄 Export CSV")
|
||||
export_csv_btn.clicked.connect(lambda: self._export_data("csv"))
|
||||
export_layout.addWidget(export_csv_btn)
|
||||
|
||||
export_json_btn = QPushButton("📋 Export JSON")
|
||||
export_json_btn.clicked.connect(lambda: self._export_data("json"))
|
||||
export_layout.addWidget(export_json_btn)
|
||||
|
||||
layout.addLayout(export_layout)
|
||||
|
||||
return section
|
||||
|
||||
def _set_draw_mode(self, mode):
|
||||
"""Set ROI drawing mode"""
|
||||
print(f"🎬 Draw mode set to: {mode}")
|
||||
# Implementation for setting drawing mode
|
||||
|
||||
def _clear_all_roi(self):
|
||||
"""Clear all ROI regions"""
|
||||
self.roi_list.clear()
|
||||
self.roi_data.clear()
|
||||
self.roi_changed.emit(self.roi_data)
|
||||
print("🎬 All ROI regions cleared")
|
||||
|
||||
def _edit_selected_roi(self):
|
||||
"""Edit the selected ROI"""
|
||||
current_item = self.roi_list.currentItem()
|
||||
if current_item:
|
||||
roi_name = current_item.text()
|
||||
print(f"🎬 Editing ROI: {roi_name}")
|
||||
|
||||
def _delete_selected_roi(self):
|
||||
"""Delete the selected ROI"""
|
||||
current_item = self.roi_list.currentItem()
|
||||
if current_item:
|
||||
roi_name = current_item.text()
|
||||
self.roi_list.takeItem(self.roi_list.row(current_item))
|
||||
if roi_name in self.roi_data:
|
||||
del self.roi_data[roi_name]
|
||||
self.roi_changed.emit(self.roi_data)
|
||||
print(f"🎬 Deleted ROI: {roi_name}")
|
||||
|
||||
def _duplicate_selected_roi(self):
|
||||
"""Duplicate the selected ROI"""
|
||||
current_item = self.roi_list.currentItem()
|
||||
if current_item:
|
||||
roi_name = current_item.text()
|
||||
print(f"🎬 Duplicating ROI: {roi_name}")
|
||||
|
||||
def _start_analysis(self):
|
||||
"""Start video analysis"""
|
||||
self.start_analysis_btn.setEnabled(False)
|
||||
self.stop_analysis_btn.setEnabled(True)
|
||||
self.analysis_progress.setVisible(True)
|
||||
self.analysis_progress.setRange(0, 0) # Indeterminate progress
|
||||
|
||||
source = self.source_combo.currentText()
|
||||
self.analysis_started.emit(source)
|
||||
|
||||
# Add sample result
|
||||
self.results_text.append(f"Started analysis on: {source}")
|
||||
print(f"🎬 Started analysis on: {source}")
|
||||
|
||||
def _stop_analysis(self):
|
||||
"""Stop video analysis"""
|
||||
self.start_analysis_btn.setEnabled(True)
|
||||
self.stop_analysis_btn.setEnabled(False)
|
||||
self.analysis_progress.setVisible(False)
|
||||
|
||||
self.results_text.append("Analysis stopped by user")
|
||||
print("🎬 Analysis stopped")
|
||||
|
||||
def _export_results(self):
|
||||
"""Export analysis results"""
|
||||
print("🎬 Exporting analysis results")
|
||||
self.export_requested.emit("analysis_results", "pdf")
|
||||
|
||||
def _export_data(self, format_type):
|
||||
"""Export data in specified format"""
|
||||
print(f"🎬 Exporting data as {format_type.upper()}")
|
||||
self.export_requested.emit("analysis_data", format_type)
|
||||
|
||||
def add_roi(self, roi_name, roi_type, coordinates):
|
||||
"""Add a new ROI region"""
|
||||
self.roi_data[roi_name] = {
|
||||
'type': roi_type,
|
||||
'coordinates': coordinates
|
||||
}
|
||||
|
||||
# Add to list
|
||||
self.roi_list.addItem(f"{roi_name} ({roi_type})")
|
||||
|
||||
# Emit change signal
|
||||
self.roi_changed.emit(self.roi_data)
|
||||
|
||||
print(f"🎬 Added ROI: {roi_name} ({roi_type})")
|
||||
|
||||
def update_analysis_results(self, results):
|
||||
"""Update analysis results display"""
|
||||
self.analysis_results.update(results)
|
||||
|
||||
# Update summary labels
|
||||
self.objects_count_label.setText(str(results.get('objects_detected', 0)))
|
||||
self.avg_speed_label.setText(f"{results.get('average_speed', 0.0):.1f} km/h")
|
||||
self.violations_count_label.setText(str(results.get('violations', 0)))
|
||||
|
||||
# Add to results text
|
||||
if 'message' in results:
|
||||
self.results_text.append(results['message'])
|
||||
|
||||
def set_analysis_progress(self, value):
|
||||
"""Set analysis progress"""
|
||||
if self.analysis_progress.isVisible():
|
||||
if value < 0:
|
||||
self.analysis_progress.setRange(0, 0) # Indeterminate
|
||||
else:
|
||||
self.analysis_progress.setRange(0, 100)
|
||||
self.analysis_progress.setValue(value)
|
||||
735
qt_app_pyside1/ui/tabs/violations_tab.py
Normal file
735
qt_app_pyside1/ui/tabs/violations_tab.py
Normal file
@@ -0,0 +1,735 @@
|
||||
"""
|
||||
Violations Tab - Traffic violation detection and evidence management dashboard
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QSplitter,
|
||||
QTableWidget, QTableWidgetItem, QHeaderView,
|
||||
QGroupBox, QLabel, QPushButton, QComboBox,
|
||||
QDateEdit, QLineEdit, QTextEdit, QFrame,
|
||||
QCheckBox, QSpinBox, QProgressBar, QTabWidget)
|
||||
from PySide6.QtCore import Qt, Signal, QDateTime, QDate, QTimer
|
||||
from PySide6.QtGui import QFont, QColor, QPixmap, QIcon
|
||||
|
||||
class ViolationItem:
|
||||
"""Data class for violation items"""
|
||||
|
||||
def __init__(self, violation_id, violation_type, timestamp, location,
|
||||
vehicle_id, evidence_path=None, status="pending"):
|
||||
self.violation_id = violation_id
|
||||
self.violation_type = violation_type
|
||||
self.timestamp = timestamp
|
||||
self.location = location
|
||||
self.vehicle_id = vehicle_id
|
||||
self.evidence_path = evidence_path
|
||||
self.status = status
|
||||
self.confidence = 0.95
|
||||
self.reviewed = False
|
||||
|
||||
class ViolationsTab(QWidget):
|
||||
"""
|
||||
Traffic Violations Dashboard with evidence management
|
||||
|
||||
Features:
|
||||
- Real-time violation detection alerts
|
||||
- Evidence gallery with images/videos
|
||||
- Violation classification and filtering
|
||||
- Manual review and acknowledgment
|
||||
- Export capabilities for reporting
|
||||
- Statistics and trends analysis
|
||||
"""
|
||||
|
||||
# Signals
|
||||
violation_acknowledged = Signal(str)
|
||||
violation_exported = Signal(list)
|
||||
evidence_viewed = Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.violations_data = []
|
||||
self.filtered_violations = []
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
# Sample data for demonstration
|
||||
self._add_sample_violations()
|
||||
|
||||
print("🚨 Violations Tab initialized")
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup the violations dashboard UI"""
|
||||
# Main layout
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Header with summary stats
|
||||
header = self._create_header()
|
||||
layout.addWidget(header)
|
||||
|
||||
# Main content splitter
|
||||
main_splitter = QSplitter(Qt.Horizontal)
|
||||
layout.addWidget(main_splitter)
|
||||
|
||||
# Left panel - Violations list and filters
|
||||
left_panel = self._create_left_panel()
|
||||
main_splitter.addWidget(left_panel)
|
||||
|
||||
# Right panel - Evidence and details
|
||||
right_panel = self._create_right_panel()
|
||||
main_splitter.addWidget(right_panel)
|
||||
|
||||
# Set splitter proportions
|
||||
main_splitter.setSizes([600, 400])
|
||||
|
||||
def _create_header(self):
|
||||
"""Create header with violation statistics"""
|
||||
header = QFrame()
|
||||
header.setFixedHeight(80)
|
||||
header.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #34495e;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
""")
|
||||
|
||||
layout = QHBoxLayout(header)
|
||||
layout.setContentsMargins(20, 10, 20, 10)
|
||||
|
||||
# Statistics cards
|
||||
stats = [
|
||||
("Total Violations", "127", "#e74c3c"),
|
||||
("Pending Review", "23", "#f39c12"),
|
||||
("Acknowledged", "104", "#27ae60"),
|
||||
("Today's Count", "8", "#3498db")
|
||||
]
|
||||
|
||||
for title, value, color in stats:
|
||||
card = self._create_stat_card(title, value, color)
|
||||
layout.addWidget(card)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Quick actions
|
||||
actions_layout = QVBoxLayout()
|
||||
|
||||
export_btn = QPushButton("📊 Export Report")
|
||||
export_btn.setFixedSize(120, 30)
|
||||
export_btn.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
background-color: #2980b9;
|
||||
}}
|
||||
""")
|
||||
export_btn.clicked.connect(self._export_violations)
|
||||
actions_layout.addWidget(export_btn)
|
||||
|
||||
clear_btn = QPushButton("🗑️ Clear Old")
|
||||
clear_btn.setFixedSize(120, 30)
|
||||
clear_btn.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
background-color: #7f8c8d;
|
||||
}}
|
||||
""")
|
||||
clear_btn.clicked.connect(self._clear_old_violations)
|
||||
actions_layout.addWidget(clear_btn)
|
||||
|
||||
layout.addLayout(actions_layout)
|
||||
|
||||
return header
|
||||
|
||||
def _create_stat_card(self, title, value, color):
|
||||
"""Create a statistics card"""
|
||||
card = QFrame()
|
||||
card.setFixedSize(140, 50)
|
||||
card.setStyleSheet(f"""
|
||||
QFrame {{
|
||||
background-color: {color};
|
||||
border-radius: 6px;
|
||||
margin: 5px;
|
||||
}}
|
||||
""")
|
||||
|
||||
layout = QVBoxLayout(card)
|
||||
layout.setContentsMargins(10, 5, 10, 5)
|
||||
|
||||
# Value
|
||||
value_label = QLabel(value)
|
||||
value_label.setFont(QFont("Segoe UI", 16, QFont.Bold))
|
||||
value_label.setStyleSheet("color: white;")
|
||||
value_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(value_label)
|
||||
|
||||
# Title
|
||||
title_label = QLabel(title)
|
||||
title_label.setFont(QFont("Segoe UI", 8))
|
||||
title_label.setStyleSheet("color: white;")
|
||||
title_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(title_label)
|
||||
|
||||
return card
|
||||
|
||||
def _create_left_panel(self):
|
||||
"""Create left panel with violations list and filters"""
|
||||
panel = QFrame()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# Filters section
|
||||
filters = self._create_filters()
|
||||
layout.addWidget(filters)
|
||||
|
||||
# Violations table
|
||||
table_group = QGroupBox("Violations List")
|
||||
table_layout = QVBoxLayout(table_group)
|
||||
|
||||
self.violations_table = QTableWidget()
|
||||
self.violations_table.setColumnCount(7)
|
||||
self.violations_table.setHorizontalHeaderLabels([
|
||||
"ID", "Type", "Time", "Location", "Vehicle", "Status", "Actions"
|
||||
])
|
||||
|
||||
# Configure table
|
||||
header = self.violations_table.horizontalHeader()
|
||||
header.setSectionResizeMode(QHeaderView.Stretch)
|
||||
self.violations_table.setSelectionBehavior(QTableWidget.SelectRows)
|
||||
self.violations_table.setAlternatingRowColors(True)
|
||||
|
||||
# Connect selection change
|
||||
self.violations_table.itemSelectionChanged.connect(self._on_violation_selected)
|
||||
|
||||
table_layout.addWidget(self.violations_table)
|
||||
layout.addWidget(table_group)
|
||||
|
||||
return panel
|
||||
|
||||
def _create_filters(self):
|
||||
"""Create violation filters section"""
|
||||
filters = QGroupBox("Filters")
|
||||
filters.setFixedHeight(120)
|
||||
layout = QVBoxLayout(filters)
|
||||
|
||||
# First row of filters
|
||||
row1 = QHBoxLayout()
|
||||
|
||||
# Violation type filter
|
||||
row1.addWidget(QLabel("Type:"))
|
||||
self.type_filter = QComboBox()
|
||||
self.type_filter.addItems([
|
||||
"All Types",
|
||||
"Red Light Violation",
|
||||
"Speed Violation",
|
||||
"Wrong Direction",
|
||||
"Illegal Turn",
|
||||
"Lane Violation"
|
||||
])
|
||||
self.type_filter.currentTextChanged.connect(self._apply_filters)
|
||||
row1.addWidget(self.type_filter)
|
||||
|
||||
# Status filter
|
||||
row1.addWidget(QLabel("Status:"))
|
||||
self.status_filter = QComboBox()
|
||||
self.status_filter.addItems(["All Status", "Pending", "Acknowledged", "Dismissed"])
|
||||
self.status_filter.currentTextChanged.connect(self._apply_filters)
|
||||
row1.addWidget(self.status_filter)
|
||||
|
||||
layout.addLayout(row1)
|
||||
|
||||
# Second row of filters
|
||||
row2 = QHBoxLayout()
|
||||
|
||||
# Date range
|
||||
row2.addWidget(QLabel("From:"))
|
||||
self.date_from = QDateEdit()
|
||||
self.date_from.setDate(QDate.currentDate().addDays(-7))
|
||||
self.date_from.dateChanged.connect(self._apply_filters)
|
||||
row2.addWidget(self.date_from)
|
||||
|
||||
row2.addWidget(QLabel("To:"))
|
||||
self.date_to = QDateEdit()
|
||||
self.date_to.setDate(QDate.currentDate())
|
||||
self.date_to.dateChanged.connect(self._apply_filters)
|
||||
row2.addWidget(self.date_to)
|
||||
|
||||
# Search
|
||||
self.search_input = QLineEdit()
|
||||
self.search_input.setPlaceholderText("Search violations...")
|
||||
self.search_input.textChanged.connect(self._apply_filters)
|
||||
row2.addWidget(self.search_input)
|
||||
|
||||
layout.addLayout(row2)
|
||||
|
||||
# Filter controls
|
||||
row3 = QHBoxLayout()
|
||||
|
||||
self.show_acknowledged_cb = QCheckBox("Show Acknowledged")
|
||||
self.show_acknowledged_cb.setChecked(True)
|
||||
self.show_acknowledged_cb.toggled.connect(self._apply_filters)
|
||||
row3.addWidget(self.show_acknowledged_cb)
|
||||
|
||||
row3.addStretch()
|
||||
|
||||
clear_filters_btn = QPushButton("Clear Filters")
|
||||
clear_filters_btn.clicked.connect(self._clear_filters)
|
||||
row3.addWidget(clear_filters_btn)
|
||||
|
||||
layout.addLayout(row3)
|
||||
|
||||
return filters
|
||||
|
||||
def _create_right_panel(self):
|
||||
"""Create right panel with evidence and details"""
|
||||
panel = QFrame()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# Evidence section
|
||||
evidence_section = self._create_evidence_section()
|
||||
layout.addWidget(evidence_section)
|
||||
|
||||
# Details section
|
||||
details_section = self._create_details_section()
|
||||
layout.addWidget(details_section)
|
||||
|
||||
# Actions section
|
||||
actions_section = self._create_actions_section()
|
||||
layout.addWidget(actions_section)
|
||||
|
||||
return panel
|
||||
|
||||
def _create_evidence_section(self):
|
||||
"""Create evidence viewing section"""
|
||||
section = QGroupBox("Evidence")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# Evidence tabs
|
||||
self.evidence_tabs = QTabWidget()
|
||||
|
||||
# Image evidence tab
|
||||
image_tab = QWidget()
|
||||
image_layout = QVBoxLayout(image_tab)
|
||||
|
||||
self.evidence_image = QLabel("Select a violation to view evidence")
|
||||
self.evidence_image.setMinimumSize(300, 200)
|
||||
self.evidence_image.setAlignment(Qt.AlignCenter)
|
||||
self.evidence_image.setStyleSheet("""
|
||||
QLabel {
|
||||
border: 2px dashed #bdc3c7;
|
||||
border-radius: 8px;
|
||||
background-color: #ecf0f1;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
""")
|
||||
image_layout.addWidget(self.evidence_image)
|
||||
|
||||
# Image controls
|
||||
image_controls = QHBoxLayout()
|
||||
|
||||
zoom_in_btn = QPushButton("🔍+")
|
||||
zoom_in_btn.setFixedSize(30, 30)
|
||||
image_controls.addWidget(zoom_in_btn)
|
||||
|
||||
zoom_out_btn = QPushButton("🔍-")
|
||||
zoom_out_btn.setFixedSize(30, 30)
|
||||
image_controls.addWidget(zoom_out_btn)
|
||||
|
||||
image_controls.addStretch()
|
||||
|
||||
save_evidence_btn = QPushButton("💾 Save")
|
||||
save_evidence_btn.clicked.connect(self._save_evidence)
|
||||
image_controls.addWidget(save_evidence_btn)
|
||||
|
||||
image_layout.addLayout(image_controls)
|
||||
|
||||
self.evidence_tabs.addTab(image_tab, "📷 Image")
|
||||
|
||||
# Video evidence tab
|
||||
video_tab = QWidget()
|
||||
video_layout = QVBoxLayout(video_tab)
|
||||
|
||||
video_placeholder = QLabel("Video evidence player\n(Feature coming soon)")
|
||||
video_placeholder.setMinimumSize(300, 200)
|
||||
video_placeholder.setAlignment(Qt.AlignCenter)
|
||||
video_placeholder.setStyleSheet("""
|
||||
QLabel {
|
||||
border: 2px dashed #bdc3c7;
|
||||
border-radius: 8px;
|
||||
background-color: #ecf0f1;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
""")
|
||||
video_layout.addWidget(video_placeholder)
|
||||
|
||||
self.evidence_tabs.addTab(video_tab, "🎬 Video")
|
||||
|
||||
layout.addWidget(self.evidence_tabs)
|
||||
|
||||
return section
|
||||
|
||||
def _create_details_section(self):
|
||||
"""Create violation details section"""
|
||||
section = QGroupBox("Violation Details")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# Details text area
|
||||
self.details_text = QTextEdit()
|
||||
self.details_text.setMaximumHeight(120)
|
||||
self.details_text.setPlaceholderText("Select a violation to view details...")
|
||||
layout.addWidget(self.details_text)
|
||||
|
||||
# Metadata grid
|
||||
metadata_layout = QHBoxLayout()
|
||||
|
||||
# Left column
|
||||
left_metadata = QVBoxLayout()
|
||||
self.confidence_label = QLabel("Confidence: --")
|
||||
self.camera_label = QLabel("Camera: --")
|
||||
self.coordinates_label = QLabel("Coordinates: --")
|
||||
|
||||
left_metadata.addWidget(self.confidence_label)
|
||||
left_metadata.addWidget(self.camera_label)
|
||||
left_metadata.addWidget(self.coordinates_label)
|
||||
|
||||
metadata_layout.addLayout(left_metadata)
|
||||
|
||||
# Right column
|
||||
right_metadata = QVBoxLayout()
|
||||
self.weather_label = QLabel("Weather: --")
|
||||
self.visibility_label = QLabel("Visibility: --")
|
||||
self.reviewed_by_label = QLabel("Reviewed by: --")
|
||||
|
||||
right_metadata.addWidget(self.weather_label)
|
||||
right_metadata.addWidget(self.visibility_label)
|
||||
right_metadata.addWidget(self.reviewed_by_label)
|
||||
|
||||
metadata_layout.addLayout(right_metadata)
|
||||
|
||||
layout.addLayout(metadata_layout)
|
||||
|
||||
return section
|
||||
|
||||
def _create_actions_section(self):
|
||||
"""Create violation actions section"""
|
||||
section = QGroupBox("Actions")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# Primary actions
|
||||
primary_actions = QHBoxLayout()
|
||||
|
||||
self.acknowledge_btn = QPushButton("✅ Acknowledge")
|
||||
self.acknowledge_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #229954;
|
||||
}
|
||||
""")
|
||||
self.acknowledge_btn.clicked.connect(self._acknowledge_violation)
|
||||
self.acknowledge_btn.setEnabled(False)
|
||||
primary_actions.addWidget(self.acknowledge_btn)
|
||||
|
||||
self.dismiss_btn = QPushButton("❌ Dismiss")
|
||||
self.dismiss_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
""")
|
||||
self.dismiss_btn.clicked.connect(self._dismiss_violation)
|
||||
self.dismiss_btn.setEnabled(False)
|
||||
primary_actions.addWidget(self.dismiss_btn)
|
||||
|
||||
layout.addLayout(primary_actions)
|
||||
|
||||
# Secondary actions
|
||||
secondary_actions = QHBoxLayout()
|
||||
|
||||
flag_btn = QPushButton("🏃 Flag for Review")
|
||||
flag_btn.clicked.connect(self._flag_violation)
|
||||
secondary_actions.addWidget(flag_btn)
|
||||
|
||||
notes_btn = QPushButton("📝 Add Notes")
|
||||
notes_btn.clicked.connect(self._add_notes)
|
||||
secondary_actions.addWidget(notes_btn)
|
||||
|
||||
layout.addLayout(secondary_actions)
|
||||
|
||||
# Bulk actions
|
||||
bulk_actions = QHBoxLayout()
|
||||
|
||||
select_all_btn = QPushButton("Select All")
|
||||
select_all_btn.clicked.connect(self._select_all_violations)
|
||||
bulk_actions.addWidget(select_all_btn)
|
||||
|
||||
bulk_acknowledge_btn = QPushButton("Bulk Acknowledge")
|
||||
bulk_acknowledge_btn.clicked.connect(self._bulk_acknowledge)
|
||||
bulk_actions.addWidget(bulk_acknowledge_btn)
|
||||
|
||||
layout.addLayout(bulk_actions)
|
||||
|
||||
return section
|
||||
|
||||
def _add_sample_violations(self):
|
||||
"""Add sample violation data for demonstration"""
|
||||
sample_violations = [
|
||||
ViolationItem("V001", "Red Light Violation", QDateTime.currentDateTime().addSecs(-3600),
|
||||
"Main St & Oak Ave", "ABC123", "evidence_001.jpg", "pending"),
|
||||
ViolationItem("V002", "Speed Violation", QDateTime.currentDateTime().addSecs(-7200),
|
||||
"Highway 101", "XYZ789", "evidence_002.jpg", "acknowledged"),
|
||||
ViolationItem("V003", "Wrong Direction", QDateTime.currentDateTime().addSecs(-10800),
|
||||
"5th St & Pine St", "DEF456", "evidence_003.jpg", "pending"),
|
||||
ViolationItem("V004", "Illegal Turn", QDateTime.currentDateTime().addSecs(-14400),
|
||||
"Market St", "GHI321", "evidence_004.jpg", "dismissed"),
|
||||
ViolationItem("V005", "Lane Violation", QDateTime.currentDateTime().addSecs(-18000),
|
||||
"Broadway & 2nd St", "JKL654", "evidence_005.jpg", "pending"),
|
||||
]
|
||||
|
||||
self.violations_data = sample_violations
|
||||
self._populate_violations_table()
|
||||
|
||||
def _populate_violations_table(self):
|
||||
"""Populate the violations table with data"""
|
||||
self.violations_table.setRowCount(len(self.violations_data))
|
||||
|
||||
for row, violation in enumerate(self.violations_data):
|
||||
# ID
|
||||
self.violations_table.setItem(row, 0, QTableWidgetItem(violation.violation_id))
|
||||
|
||||
# Type
|
||||
type_item = QTableWidgetItem(violation.violation_type)
|
||||
if violation.violation_type == "Red Light Violation":
|
||||
type_item.setBackground(QColor(231, 76, 60, 50)) # Light red
|
||||
elif violation.violation_type == "Speed Violation":
|
||||
type_item.setBackground(QColor(243, 156, 18, 50)) # Light orange
|
||||
self.violations_table.setItem(row, 1, type_item)
|
||||
|
||||
# Time
|
||||
time_str = violation.timestamp.toString("MM/dd hh:mm")
|
||||
self.violations_table.setItem(row, 2, QTableWidgetItem(time_str))
|
||||
|
||||
# Location
|
||||
self.violations_table.setItem(row, 3, QTableWidgetItem(violation.location))
|
||||
|
||||
# Vehicle
|
||||
self.violations_table.setItem(row, 4, QTableWidgetItem(violation.vehicle_id))
|
||||
|
||||
# Status
|
||||
status_item = QTableWidgetItem(violation.status.title())
|
||||
if violation.status == "pending":
|
||||
status_item.setBackground(QColor(243, 156, 18, 50)) # Orange
|
||||
elif violation.status == "acknowledged":
|
||||
status_item.setBackground(QColor(39, 174, 96, 50)) # Green
|
||||
elif violation.status == "dismissed":
|
||||
status_item.setBackground(QColor(149, 165, 166, 50)) # Gray
|
||||
self.violations_table.setItem(row, 5, status_item)
|
||||
|
||||
# Actions (placeholder)
|
||||
actions_item = QTableWidgetItem("View")
|
||||
self.violations_table.setItem(row, 6, actions_item)
|
||||
|
||||
def _apply_filters(self):
|
||||
"""Apply current filters to violations list"""
|
||||
# This would filter the violations based on current filter settings
|
||||
self._populate_violations_table()
|
||||
print("🚨 Filters applied to violations list")
|
||||
|
||||
def _clear_filters(self):
|
||||
"""Clear all filters"""
|
||||
self.type_filter.setCurrentIndex(0)
|
||||
self.status_filter.setCurrentIndex(0)
|
||||
self.date_from.setDate(QDate.currentDate().addDays(-7))
|
||||
self.date_to.setDate(QDate.currentDate())
|
||||
self.search_input.clear()
|
||||
self.show_acknowledged_cb.setChecked(True)
|
||||
print("🚨 Filters cleared")
|
||||
|
||||
def _on_violation_selected(self):
|
||||
"""Handle violation selection"""
|
||||
current_row = self.violations_table.currentRow()
|
||||
if current_row >= 0 and current_row < len(self.violations_data):
|
||||
violation = self.violations_data[current_row]
|
||||
self._show_violation_details(violation)
|
||||
|
||||
# Enable action buttons
|
||||
self.acknowledge_btn.setEnabled(violation.status == "pending")
|
||||
self.dismiss_btn.setEnabled(violation.status == "pending")
|
||||
|
||||
def _show_violation_details(self, violation):
|
||||
"""Show details for selected violation"""
|
||||
# Update details text
|
||||
details = f"""
|
||||
Violation ID: {violation.violation_id}
|
||||
Type: {violation.violation_type}
|
||||
Timestamp: {violation.timestamp.toString("yyyy-MM-dd hh:mm:ss")}
|
||||
Location: {violation.location}
|
||||
Vehicle ID: {violation.vehicle_id}
|
||||
Status: {violation.status.title()}
|
||||
|
||||
Description: This violation was automatically detected by the traffic monitoring system.
|
||||
The evidence has been captured and is available for review.
|
||||
""".strip()
|
||||
|
||||
self.details_text.setPlainText(details)
|
||||
|
||||
# Update metadata labels
|
||||
self.confidence_label.setText(f"Confidence: {violation.confidence:.1%}")
|
||||
self.camera_label.setText("Camera: Camera 1")
|
||||
self.coordinates_label.setText("Coordinates: 37.7749, -122.4194")
|
||||
self.weather_label.setText("Weather: Clear")
|
||||
self.visibility_label.setText("Visibility: Good")
|
||||
self.reviewed_by_label.setText("Reviewed by: --")
|
||||
|
||||
# Update evidence display
|
||||
self.evidence_image.setText(f"Evidence: {violation.evidence_path or 'No evidence available'}")
|
||||
|
||||
print(f"🚨 Showing details for violation: {violation.violation_id}")
|
||||
|
||||
def _acknowledge_violation(self):
|
||||
"""Acknowledge the selected violation"""
|
||||
current_row = self.violations_table.currentRow()
|
||||
if current_row >= 0:
|
||||
violation = self.violations_data[current_row]
|
||||
violation.status = "acknowledged"
|
||||
violation.reviewed = True
|
||||
|
||||
# Update table
|
||||
self._populate_violations_table()
|
||||
|
||||
# Disable buttons
|
||||
self.acknowledge_btn.setEnabled(False)
|
||||
self.dismiss_btn.setEnabled(False)
|
||||
|
||||
# Emit signal
|
||||
self.violation_acknowledged.emit(violation.violation_id)
|
||||
|
||||
print(f"🚨 Violation {violation.violation_id} acknowledged")
|
||||
|
||||
def _dismiss_violation(self):
|
||||
"""Dismiss the selected violation"""
|
||||
current_row = self.violations_table.currentRow()
|
||||
if current_row >= 0:
|
||||
violation = self.violations_data[current_row]
|
||||
violation.status = "dismissed"
|
||||
violation.reviewed = True
|
||||
|
||||
# Update table
|
||||
self._populate_violations_table()
|
||||
|
||||
# Disable buttons
|
||||
self.acknowledge_btn.setEnabled(False)
|
||||
self.dismiss_btn.setEnabled(False)
|
||||
|
||||
print(f"🚨 Violation {violation.violation_id} dismissed")
|
||||
|
||||
def _flag_violation(self):
|
||||
"""Flag violation for manual review"""
|
||||
current_row = self.violations_table.currentRow()
|
||||
if current_row >= 0:
|
||||
violation = self.violations_data[current_row]
|
||||
print(f"🚨 Violation {violation.violation_id} flagged for review")
|
||||
|
||||
def _add_notes(self):
|
||||
"""Add notes to violation"""
|
||||
current_row = self.violations_table.currentRow()
|
||||
if current_row >= 0:
|
||||
violation = self.violations_data[current_row]
|
||||
print(f"🚨 Adding notes to violation {violation.violation_id}")
|
||||
|
||||
def _select_all_violations(self):
|
||||
"""Select all violations in table"""
|
||||
self.violations_table.selectAll()
|
||||
print("🚨 All violations selected")
|
||||
|
||||
def _bulk_acknowledge(self):
|
||||
"""Acknowledge all selected violations"""
|
||||
selected_rows = set()
|
||||
for item in self.violations_table.selectedItems():
|
||||
selected_rows.add(item.row())
|
||||
|
||||
for row in selected_rows:
|
||||
if row < len(self.violations_data):
|
||||
violation = self.violations_data[row]
|
||||
if violation.status == "pending":
|
||||
violation.status = "acknowledged"
|
||||
violation.reviewed = True
|
||||
|
||||
# Update table
|
||||
self._populate_violations_table()
|
||||
|
||||
print(f"🚨 Bulk acknowledged {len(selected_rows)} violations")
|
||||
|
||||
def _export_violations(self):
|
||||
"""Export violations report"""
|
||||
violation_ids = [v.violation_id for v in self.violations_data]
|
||||
self.violation_exported.emit(violation_ids)
|
||||
print("🚨 Violations exported")
|
||||
|
||||
def _clear_old_violations(self):
|
||||
"""Clear old acknowledged violations"""
|
||||
# Remove violations older than 30 days and acknowledged
|
||||
cutoff_date = QDateTime.currentDateTime().addDays(-30)
|
||||
|
||||
original_count = len(self.violations_data)
|
||||
self.violations_data = [
|
||||
v for v in self.violations_data
|
||||
if not (v.status == "acknowledged" and v.timestamp < cutoff_date)
|
||||
]
|
||||
|
||||
removed_count = original_count - len(self.violations_data)
|
||||
|
||||
# Update table
|
||||
self._populate_violations_table()
|
||||
|
||||
print(f"🚨 Cleared {removed_count} old violations")
|
||||
|
||||
def _save_evidence(self):
|
||||
"""Save current evidence"""
|
||||
current_row = self.violations_table.currentRow()
|
||||
if current_row >= 0:
|
||||
violation = self.violations_data[current_row]
|
||||
self.evidence_viewed.emit(violation.violation_id)
|
||||
print(f"🚨 Evidence saved for violation {violation.violation_id}")
|
||||
|
||||
def add_violation(self, violation_data):
|
||||
"""Add a new violation to the list"""
|
||||
violation = ViolationItem(**violation_data)
|
||||
self.violations_data.insert(0, violation) # Add to beginning
|
||||
self._populate_violations_table()
|
||||
print(f"🚨 New violation added: {violation.violation_id}")
|
||||
|
||||
def get_violation_summary(self):
|
||||
"""Get summary of violations"""
|
||||
total = len(self.violations_data)
|
||||
pending = sum(1 for v in self.violations_data if v.status == "pending")
|
||||
acknowledged = sum(1 for v in self.violations_data if v.status == "acknowledged")
|
||||
dismissed = sum(1 for v in self.violations_data if v.status == "dismissed")
|
||||
|
||||
return {
|
||||
'total': total,
|
||||
'pending': pending,
|
||||
'acknowledged': acknowledged,
|
||||
'dismissed': dismissed
|
||||
}
|
||||
586
qt_app_pyside1/ui/tabs/vlm_insights_tab.py
Normal file
586
qt_app_pyside1/ui/tabs/vlm_insights_tab.py
Normal file
@@ -0,0 +1,586 @@
|
||||
"""
|
||||
VLM AI Insights Tab - ChatGPT-like interface for Vision Language Model interactions
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
|
||||
QLineEdit, QPushButton, QScrollArea, QFrame,
|
||||
QLabel, QSplitter, QComboBox, QSlider, QGroupBox,
|
||||
QCheckBox, QSpinBox)
|
||||
from PySide6.QtCore import Qt, Signal, QTimer, QDateTime
|
||||
from PySide6.QtGui import QFont, QTextCharFormat, QColor, QPixmap
|
||||
|
||||
class ChatMessage(QFrame):
|
||||
"""Individual chat message widget"""
|
||||
|
||||
def __init__(self, message, is_user=True, timestamp=None, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.message = message
|
||||
self.is_user = is_user
|
||||
self.timestamp = timestamp or QDateTime.currentDateTime()
|
||||
|
||||
self._setup_ui()
|
||||
self._apply_style()
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup message UI"""
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 8, 10, 8)
|
||||
|
||||
if self.is_user:
|
||||
layout.addStretch()
|
||||
|
||||
# Message bubble
|
||||
bubble = QFrame()
|
||||
bubble.setMaximumWidth(400)
|
||||
bubble_layout = QVBoxLayout(bubble)
|
||||
bubble_layout.setContentsMargins(12, 8, 12, 8)
|
||||
|
||||
# Message text
|
||||
message_label = QLabel(self.message)
|
||||
message_label.setWordWrap(True)
|
||||
message_label.setFont(QFont("Segoe UI", 9))
|
||||
bubble_layout.addWidget(message_label)
|
||||
|
||||
# Timestamp
|
||||
time_label = QLabel(self.timestamp.toString("hh:mm"))
|
||||
time_label.setFont(QFont("Segoe UI", 7))
|
||||
time_label.setAlignment(Qt.AlignRight if self.is_user else Qt.AlignLeft)
|
||||
bubble_layout.addWidget(time_label)
|
||||
|
||||
layout.addWidget(bubble)
|
||||
|
||||
if not self.is_user:
|
||||
layout.addStretch()
|
||||
|
||||
def _apply_style(self):
|
||||
"""Apply message styling"""
|
||||
if self.is_user:
|
||||
# User message (blue, right-aligned)
|
||||
self.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #3498db;
|
||||
border-radius: 12px;
|
||||
margin-left: 50px;
|
||||
}
|
||||
QLabel {
|
||||
color: white;
|
||||
}
|
||||
""")
|
||||
else:
|
||||
# AI message (gray, left-aligned)
|
||||
self.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #ecf0f1;
|
||||
border-radius: 12px;
|
||||
margin-right: 50px;
|
||||
}
|
||||
QLabel {
|
||||
color: #2c3e50;
|
||||
}
|
||||
""")
|
||||
|
||||
class VLMInsightsTab(QWidget):
|
||||
"""
|
||||
VLM AI Insights Tab with ChatGPT-like interface
|
||||
|
||||
Features:
|
||||
- Chat-style interface for VLM interactions
|
||||
- Image context from traffic cameras
|
||||
- Predefined prompts for traffic analysis
|
||||
- Conversation history
|
||||
- AI insights and recommendations
|
||||
- Export conversation functionality
|
||||
"""
|
||||
|
||||
# Signals
|
||||
insight_generated = Signal(str)
|
||||
vlm_query_sent = Signal(str, dict)
|
||||
conversation_exported = Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.conversation_history = []
|
||||
self.current_image_context = None
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
print("🤖 VLM AI Insights Tab initialized")
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup the VLM insights UI"""
|
||||
# Main splitter
|
||||
main_splitter = QSplitter(Qt.Horizontal)
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(main_splitter)
|
||||
|
||||
# Left panel - Chat interface
|
||||
left_panel = self._create_chat_panel()
|
||||
main_splitter.addWidget(left_panel)
|
||||
|
||||
# Right panel - Settings and context
|
||||
right_panel = self._create_settings_panel()
|
||||
main_splitter.addWidget(right_panel)
|
||||
|
||||
# Set splitter proportions (70% chat, 30% settings)
|
||||
main_splitter.setSizes([700, 300])
|
||||
|
||||
def _create_chat_panel(self):
|
||||
"""Create chat interface panel"""
|
||||
panel = QFrame()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# Chat header
|
||||
header = self._create_chat_header()
|
||||
layout.addWidget(header)
|
||||
|
||||
# Conversation area
|
||||
self.conversation_scroll = QScrollArea()
|
||||
self.conversation_scroll.setWidgetResizable(True)
|
||||
self.conversation_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarNever)
|
||||
|
||||
self.conversation_widget = QWidget()
|
||||
self.conversation_layout = QVBoxLayout(self.conversation_widget)
|
||||
self.conversation_layout.setAlignment(Qt.AlignTop)
|
||||
self.conversation_layout.setSpacing(5)
|
||||
|
||||
self.conversation_scroll.setWidget(self.conversation_widget)
|
||||
layout.addWidget(self.conversation_scroll, 1)
|
||||
|
||||
# Input area
|
||||
input_area = self._create_input_area()
|
||||
layout.addWidget(input_area)
|
||||
|
||||
return panel
|
||||
|
||||
def _create_chat_header(self):
|
||||
"""Create chat header with title and controls"""
|
||||
header = QFrame()
|
||||
header.setFixedHeight(50)
|
||||
header.setStyleSheet("background-color: #34495e; border-radius: 8px; margin-bottom: 5px;")
|
||||
|
||||
layout = QHBoxLayout(header)
|
||||
layout.setContentsMargins(15, 10, 15, 10)
|
||||
|
||||
# Title and status
|
||||
title_layout = QVBoxLayout()
|
||||
|
||||
title = QLabel("🤖 VLM AI Assistant")
|
||||
title.setFont(QFont("Segoe UI", 12, QFont.Bold))
|
||||
title.setStyleSheet("color: white;")
|
||||
title_layout.addWidget(title)
|
||||
|
||||
self.status_label = QLabel("Ready to analyze traffic scenes")
|
||||
self.status_label.setFont(QFont("Segoe UI", 8))
|
||||
self.status_label.setStyleSheet("color: #bdc3c7;")
|
||||
title_layout.addWidget(self.status_label)
|
||||
|
||||
layout.addLayout(title_layout)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Action buttons
|
||||
clear_btn = QPushButton("🗑️ Clear")
|
||||
clear_btn.setFixedSize(60, 30)
|
||||
clear_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 8pt;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
""")
|
||||
clear_btn.clicked.connect(self._clear_conversation)
|
||||
layout.addWidget(clear_btn)
|
||||
|
||||
export_btn = QPushButton("📤 Export")
|
||||
export_btn.setFixedSize(60, 30)
|
||||
export_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 8pt;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
""")
|
||||
export_btn.clicked.connect(self._export_conversation)
|
||||
layout.addWidget(export_btn)
|
||||
|
||||
return header
|
||||
|
||||
def _create_input_area(self):
|
||||
"""Create message input area"""
|
||||
input_frame = QFrame()
|
||||
input_frame.setFixedHeight(100)
|
||||
layout = QVBoxLayout(input_frame)
|
||||
|
||||
# Quick prompts
|
||||
prompts_layout = QHBoxLayout()
|
||||
|
||||
prompts = [
|
||||
"Analyze current traffic",
|
||||
"Count vehicles",
|
||||
"Detect violations",
|
||||
"Safety assessment"
|
||||
]
|
||||
|
||||
for prompt in prompts:
|
||||
btn = QPushButton(prompt)
|
||||
btn.setMaximumHeight(25)
|
||||
btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #ecf0f1;
|
||||
border: 1px solid #bdc3c7;
|
||||
border-radius: 12px;
|
||||
padding: 4px 8px;
|
||||
font-size: 8pt;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #d5dbdb;
|
||||
}
|
||||
""")
|
||||
btn.clicked.connect(lambda checked, p=prompt: self._send_quick_prompt(p))
|
||||
prompts_layout.addWidget(btn)
|
||||
|
||||
prompts_layout.addStretch()
|
||||
layout.addLayout(prompts_layout)
|
||||
|
||||
# Message input
|
||||
input_layout = QHBoxLayout()
|
||||
|
||||
self.message_input = QLineEdit()
|
||||
self.message_input.setPlaceholderText("Ask the AI about traffic conditions, violations, or safety...")
|
||||
self.message_input.setFont(QFont("Segoe UI", 9))
|
||||
self.message_input.setStyleSheet("""
|
||||
QLineEdit {
|
||||
border: 2px solid #bdc3c7;
|
||||
border-radius: 20px;
|
||||
padding: 8px 15px;
|
||||
font-size: 9pt;
|
||||
}
|
||||
QLineEdit:focus {
|
||||
border-color: #3498db;
|
||||
}
|
||||
""")
|
||||
self.message_input.returnPressed.connect(self._send_message)
|
||||
input_layout.addWidget(self.message_input)
|
||||
|
||||
self.send_btn = QPushButton("➤")
|
||||
self.send_btn.setFixedSize(40, 40)
|
||||
self.send_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
font-size: 16pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #21618c;
|
||||
}
|
||||
""")
|
||||
self.send_btn.clicked.connect(self._send_message)
|
||||
input_layout.addWidget(self.send_btn)
|
||||
|
||||
layout.addLayout(input_layout)
|
||||
|
||||
return input_frame
|
||||
|
||||
def _create_settings_panel(self):
|
||||
"""Create settings and context panel"""
|
||||
panel = QFrame()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# Image context section
|
||||
context_section = self._create_context_section()
|
||||
layout.addWidget(context_section)
|
||||
|
||||
# VLM settings
|
||||
settings_section = self._create_vlm_settings()
|
||||
layout.addWidget(settings_section)
|
||||
|
||||
# Conversation stats
|
||||
stats_section = self._create_stats_section()
|
||||
layout.addWidget(stats_section)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
return panel
|
||||
|
||||
def _create_context_section(self):
|
||||
"""Create image context section"""
|
||||
section = QGroupBox("Image Context")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# Current image preview
|
||||
self.image_preview = QLabel("No image selected")
|
||||
self.image_preview.setFixedSize(200, 150)
|
||||
self.image_preview.setAlignment(Qt.AlignCenter)
|
||||
self.image_preview.setStyleSheet("""
|
||||
QLabel {
|
||||
border: 2px dashed #bdc3c7;
|
||||
border-radius: 8px;
|
||||
background-color: #ecf0f1;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.image_preview)
|
||||
|
||||
# Image source selection
|
||||
source_layout = QHBoxLayout()
|
||||
source_layout.addWidget(QLabel("Source:"))
|
||||
|
||||
self.image_source_combo = QComboBox()
|
||||
self.image_source_combo.addItems([
|
||||
"Live Camera 1",
|
||||
"Live Camera 2",
|
||||
"Current Frame",
|
||||
"Upload Image"
|
||||
])
|
||||
self.image_source_combo.currentTextChanged.connect(self._change_image_source)
|
||||
source_layout.addWidget(self.image_source_combo)
|
||||
|
||||
layout.addLayout(source_layout)
|
||||
|
||||
# Capture button
|
||||
capture_btn = QPushButton("📸 Capture Current")
|
||||
capture_btn.clicked.connect(self._capture_current_frame)
|
||||
layout.addWidget(capture_btn)
|
||||
|
||||
return section
|
||||
|
||||
def _create_vlm_settings(self):
|
||||
"""Create VLM model settings"""
|
||||
section = QGroupBox("AI Settings")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# Model selection
|
||||
model_layout = QHBoxLayout()
|
||||
model_layout.addWidget(QLabel("Model:"))
|
||||
|
||||
self.model_combo = QComboBox()
|
||||
self.model_combo.addItems([
|
||||
"LLaVA-Next-Video",
|
||||
"GPT-4 Vision",
|
||||
"Claude Vision"
|
||||
])
|
||||
model_layout.addWidget(self.model_combo)
|
||||
|
||||
layout.addLayout(model_layout)
|
||||
|
||||
# Temperature setting
|
||||
temp_layout = QHBoxLayout()
|
||||
temp_layout.addWidget(QLabel("Creativity:"))
|
||||
|
||||
self.temperature_slider = QSlider(Qt.Horizontal)
|
||||
self.temperature_slider.setRange(1, 10)
|
||||
self.temperature_slider.setValue(5)
|
||||
temp_layout.addWidget(self.temperature_slider)
|
||||
|
||||
self.temp_label = QLabel("0.5")
|
||||
temp_layout.addWidget(self.temp_label)
|
||||
|
||||
layout.addLayout(temp_layout)
|
||||
|
||||
# Max response length
|
||||
length_layout = QHBoxLayout()
|
||||
length_layout.addWidget(QLabel("Max Length:"))
|
||||
|
||||
self.max_length_spin = QSpinBox()
|
||||
self.max_length_spin.setRange(50, 1000)
|
||||
self.max_length_spin.setValue(300)
|
||||
self.max_length_spin.setSuffix(" words")
|
||||
length_layout.addWidget(self.max_length_spin)
|
||||
|
||||
layout.addLayout(length_layout)
|
||||
|
||||
# Analysis options
|
||||
self.detailed_analysis_cb = QCheckBox("Detailed Analysis")
|
||||
self.detailed_analysis_cb.setChecked(True)
|
||||
layout.addWidget(self.detailed_analysis_cb)
|
||||
|
||||
self.safety_focus_cb = QCheckBox("Safety Focus")
|
||||
self.safety_focus_cb.setChecked(True)
|
||||
layout.addWidget(self.safety_focus_cb)
|
||||
|
||||
# Connect temperature slider to label
|
||||
self.temperature_slider.valueChanged.connect(
|
||||
lambda v: self.temp_label.setText(f"{v/10:.1f}")
|
||||
)
|
||||
|
||||
return section
|
||||
|
||||
def _create_stats_section(self):
|
||||
"""Create conversation statistics"""
|
||||
section = QGroupBox("Conversation Stats")
|
||||
layout = QVBoxLayout(section)
|
||||
|
||||
# Message count
|
||||
self.message_count_label = QLabel("Messages: 0")
|
||||
layout.addWidget(self.message_count_label)
|
||||
|
||||
# Insights generated
|
||||
self.insights_count_label = QLabel("Insights: 0")
|
||||
layout.addWidget(self.insights_count_label)
|
||||
|
||||
# Session time
|
||||
self.session_time_label = QLabel("Session: 0 min")
|
||||
layout.addWidget(self.session_time_label)
|
||||
|
||||
return section
|
||||
|
||||
def _send_message(self):
|
||||
"""Send user message"""
|
||||
message = self.message_input.text().strip()
|
||||
if not message:
|
||||
return
|
||||
|
||||
# Add user message
|
||||
self._add_message(message, is_user=True)
|
||||
|
||||
# Clear input
|
||||
self.message_input.clear()
|
||||
|
||||
# Send to VLM (simulate response for now)
|
||||
self._simulate_vlm_response(message)
|
||||
|
||||
# Emit signal
|
||||
context = {
|
||||
'image': self.current_image_context,
|
||||
'settings': self._get_current_settings()
|
||||
}
|
||||
self.vlm_query_sent.emit(message, context)
|
||||
|
||||
def _send_quick_prompt(self, prompt):
|
||||
"""Send a quick prompt"""
|
||||
self.message_input.setText(prompt)
|
||||
self._send_message()
|
||||
|
||||
def _add_message(self, message, is_user=True):
|
||||
"""Add message to conversation"""
|
||||
chat_message = ChatMessage(message, is_user)
|
||||
self.conversation_layout.addWidget(chat_message)
|
||||
|
||||
# Scroll to bottom
|
||||
QTimer.singleShot(100, lambda: self.conversation_scroll.verticalScrollBar().setValue(
|
||||
self.conversation_scroll.verticalScrollBar().maximum()
|
||||
))
|
||||
|
||||
# Update stats
|
||||
self.conversation_history.append({
|
||||
'message': message,
|
||||
'is_user': is_user,
|
||||
'timestamp': QDateTime.currentDateTime()
|
||||
})
|
||||
|
||||
self._update_stats()
|
||||
|
||||
def _simulate_vlm_response(self, user_message):
|
||||
"""Simulate VLM response (replace with actual VLM integration)"""
|
||||
# Simulate processing delay
|
||||
self.status_label.setText("AI is analyzing...")
|
||||
|
||||
QTimer.singleShot(2000, lambda: self._generate_response(user_message))
|
||||
|
||||
def _generate_response(self, user_message):
|
||||
"""Generate AI response"""
|
||||
# Simple response simulation
|
||||
responses = {
|
||||
"analyze current traffic": "I can see moderate traffic flow with 8 vehicles currently in view. Traffic appears to be flowing smoothly with no apparent congestion. Most vehicles are maintaining safe following distances.",
|
||||
"count vehicles": "I count 5 cars, 2 trucks, and 1 motorcycle currently visible in the intersection. Traffic density appears normal for this time of day.",
|
||||
"detect violations": "I don't detect any obvious traffic violations at this moment. All vehicles appear to be following traffic signals and maintaining proper lanes.",
|
||||
"safety assessment": "Overall safety conditions look good. Visibility is clear, traffic signals are functioning properly, and vehicle speeds appear appropriate for the intersection."
|
||||
}
|
||||
|
||||
# Find best matching response
|
||||
response = None
|
||||
for key, value in responses.items():
|
||||
if key.lower() in user_message.lower():
|
||||
response = value
|
||||
break
|
||||
|
||||
if not response:
|
||||
response = f"I understand you're asking about '{user_message}'. Based on the current traffic scene, I can provide analysis of vehicle movements, count objects, assess safety conditions, and identify potential violations. Could you be more specific about what aspect you'd like me to focus on?"
|
||||
|
||||
# Add AI response
|
||||
self._add_message(response, is_user=False)
|
||||
|
||||
# Update status
|
||||
self.status_label.setText("Ready to analyze traffic scenes")
|
||||
|
||||
# Emit insight signal
|
||||
self.insight_generated.emit(response)
|
||||
|
||||
def _clear_conversation(self):
|
||||
"""Clear conversation history"""
|
||||
# Remove all message widgets
|
||||
for i in reversed(range(self.conversation_layout.count())):
|
||||
child = self.conversation_layout.itemAt(i).widget()
|
||||
if child:
|
||||
child.setParent(None)
|
||||
|
||||
# Clear history
|
||||
self.conversation_history.clear()
|
||||
|
||||
# Update stats
|
||||
self._update_stats()
|
||||
|
||||
print("🤖 Conversation cleared")
|
||||
|
||||
def _export_conversation(self):
|
||||
"""Export conversation history"""
|
||||
self.conversation_exported.emit("conversation_history")
|
||||
print("🤖 Conversation exported")
|
||||
|
||||
def _change_image_source(self, source):
|
||||
"""Change image context source"""
|
||||
self.image_preview.setText(f"Source: {source}")
|
||||
print(f"🤖 Image source changed to: {source}")
|
||||
|
||||
def _capture_current_frame(self):
|
||||
"""Capture current frame for analysis"""
|
||||
# Simulate frame capture
|
||||
self.image_preview.setText("Current frame\ncaptured")
|
||||
self.current_image_context = "current_frame"
|
||||
print("🤖 Current frame captured for analysis")
|
||||
|
||||
def _get_current_settings(self):
|
||||
"""Get current VLM settings"""
|
||||
return {
|
||||
'model': self.model_combo.currentText(),
|
||||
'temperature': self.temperature_slider.value() / 10.0,
|
||||
'max_length': self.max_length_spin.value(),
|
||||
'detailed_analysis': self.detailed_analysis_cb.isChecked(),
|
||||
'safety_focus': self.safety_focus_cb.isChecked()
|
||||
}
|
||||
|
||||
def _update_stats(self):
|
||||
"""Update conversation statistics"""
|
||||
total_messages = len(self.conversation_history)
|
||||
ai_messages = sum(1 for msg in self.conversation_history if not msg['is_user'])
|
||||
|
||||
self.message_count_label.setText(f"Messages: {total_messages}")
|
||||
self.insights_count_label.setText(f"Insights: {ai_messages}")
|
||||
|
||||
def add_ai_insight(self, insight_text):
|
||||
"""Add an AI insight to the conversation"""
|
||||
self._add_message(insight_text, is_user=False)
|
||||
|
||||
def set_image_context(self, image_data):
|
||||
"""Set image context for VLM analysis"""
|
||||
self.current_image_context = image_data
|
||||
# Update image preview if needed
|
||||
print("🤖 Image context updated for VLM analysis")
|
||||
Reference in New Issue
Block a user