Files
Traffic-Intersection-Monito…/qt_app_pyside1/ui/analytics_tab.py
2025-08-26 13:07:59 -07:00

564 lines
22 KiB
Python

from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QTabWidget, QTableWidget, QTableWidgetItem, QHeaderView
)
from PySide6.QtCore import Qt, Slot
from PySide6.QtGui import QColor, QFont
class CleanAnalyticsWidget(QWidget):
"""Clean and minimal analytics widget with tabbed interface"""
def __init__(self):
super().__init__()
# Data storage for real-time updates
self.latest_traffic_lights = []
self.latest_violations = []
self.latest_vehicles = []
self.latest_frame_data = {}
self.init_ui()
def init_ui(self):
"""Initialize the clean UI with tabs"""
layout = QVBoxLayout(self)
layout.setContentsMargins(10, 10, 10, 10)
layout.setSpacing(10)
# Set dark background for the main widget
self.setStyleSheet("""
QWidget {
background-color: #2C3E50;
color: #FFFFFF;
}
""")
# Title
title_label = QLabel("🚦 Traffic Intersection Monitor")
title_label.setStyleSheet("""
QLabel {
font-size: 20px;
font-weight: bold;
color: #FFFFFF;
font-family: 'Roboto', Arial, sans-serif;
padding: 15px;
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
stop:0 #2C3E50, stop:1 #34495E);
border-radius: 8px;
border: 1px solid #34495E;
}
""")
title_label.setAlignment(Qt.AlignCenter)
layout.addWidget(title_label)
# Create tab widget
self.tab_widget = QTabWidget()
self.tab_widget.setStyleSheet("""
QTabWidget::pane {
border: 1px solid #34495E;
border-radius: 8px;
background-color: #2C3E50;
}
QTabBar::tab {
background: #34495E;
color: #FFFFFF;
padding: 12px 20px;
margin-right: 2px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
font-family: 'Roboto', Arial, sans-serif;
font-weight: 500;
min-width: 120px;
}
QTabBar::tab:selected {
background: #3498DB;
color: white;
}
QTabBar::tab:hover:!selected {
background: #2C3E50;
}
""")
# Create tabs
self.create_traffic_light_tab()
self.create_violation_tab()
self.create_vehicle_tab()
layout.addWidget(self.tab_widget)
# Refresh button
refresh_btn = QPushButton("🔄 Refresh Data")
refresh_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #3498DB, stop:1 #2980B9);
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-weight: bold;
font-family: 'Roboto', Arial, sans-serif;
font-size: 14px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #5DADE2, stop:1 #3498DB);
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #2980B9, stop:1 #21618C);
}
""")
refresh_btn.clicked.connect(self.refresh_all_data)
# Center the button
button_layout = QHBoxLayout()
button_layout.addStretch()
button_layout.addWidget(refresh_btn)
button_layout.addStretch()
layout.addLayout(button_layout)
def create_traffic_light_tab(self):
"""Create traffic light status tab"""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setContentsMargins(15, 15, 15, 15)
# Table
self.traffic_table = QTableWidget(0, 5)
self.traffic_table.setHorizontalHeaderLabels([
"Detection", "Red Ratio", "Yellow Ratio", "Green Ratio", "Status"
])
# Apply clean table styling
self.apply_table_style(self.traffic_table)
# Start with empty table - no sample data
layout.addWidget(self.traffic_table)
self.tab_widget.addTab(tab, "🚦 Traffic Lights")
def create_violation_tab(self):
"""Create violation summary tab"""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setContentsMargins(15, 15, 15, 15)
# Table
self.violation_table = QTableWidget(0, 3)
self.violation_table.setHorizontalHeaderLabels([
"Track ID", "Violation Type", "Status"
])
# Apply clean table styling
self.apply_table_style(self.violation_table)
# Start with empty table - no sample data
layout.addWidget(self.violation_table)
self.tab_widget.addTab(tab, "🚨 Violations")
def create_vehicle_tab(self):
"""Create vehicle tracking status tab"""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setContentsMargins(15, 15, 15, 15)
# Table
self.vehicle_table = QTableWidget(0, 6)
self.vehicle_table.setHorizontalHeaderLabels([
"Track ID", "Position (x,y)", "Center Y", "Moving", "Violating", "Status"
])
# Apply clean table styling
self.apply_table_style(self.vehicle_table)
# Start with empty table - no sample data
layout.addWidget(self.vehicle_table)
self.tab_widget.addTab(tab, "🚗 Vehicles")
def apply_table_style(self, table):
"""Apply consistent styling to tables"""
# Set font
font = QFont("Roboto", 10)
table.setFont(font)
# Header styling
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.horizontalHeader().setStyleSheet("""
QHeaderView::section {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #1A252F, stop:1 #2C3E50);
color: #FFFFFF;
padding: 10px;
border: 1px solid #2C3E50;
font-weight: bold;
font-family: 'Roboto', Arial, sans-serif;
}
""")
# Table styling
table.setStyleSheet("""
QTableWidget {
gridline-color: #34495E;
background-color: #2C3E50;
alternate-background-color: #34495E;
selection-background-color: #3498DB;
border: 1px solid #34495E;
border-radius: 6px;
color: #FFFFFF;
}
QTableWidget::item {
padding: 8px;
border-bottom: 1px solid #34495E;
color: #FFFFFF;
}
QTableWidget::item:selected {
background-color: #3498DB;
color: #FFFFFF;
}
""")
# Enable alternating row colors
table.setAlternatingRowColors(True)
# Set selection behavior
table.setSelectionBehavior(QTableWidget.SelectRows)
def populate_table(self, table, data, table_type):
"""Populate table with data and apply color coding for dark theme"""
table.setRowCount(len(data))
for i, row in enumerate(data):
for j, item in enumerate(row):
cell = QTableWidgetItem(str(item))
cell.setForeground(QColor(255, 255, 255)) # White text
# Apply color coding based on content for dark theme
if table_type == "traffic_light":
if "🔴" in str(item):
cell.setBackground(QColor(139, 69, 19)) # Dark red/brown
elif "🟡" in str(item):
cell.setBackground(QColor(184, 134, 11)) # Dark yellow
elif "🟢" in str(item):
cell.setBackground(QColor(34, 139, 34)) # Dark green
elif table_type == "violation":
if "Active" in str(item) or "🚨" in str(item):
cell.setBackground(QColor(139, 69, 19)) # Dark red/brown
cell.setForeground(QColor(255, 255, 255)) # White text
elif "Detected" in str(item):
cell.setBackground(QColor(205, 133, 63)) # Dark orange
cell.setForeground(QColor(255, 255, 255)) # White text
elif table_type == "vehicle":
if "🔴" in str(item) or ("True" in str(item) and j == 4): # Violating column
cell.setBackground(QColor(139, 69, 19)) # Dark red/brown
cell.setForeground(QColor(255, 255, 255)) # White text
elif "🟢" in str(item):
cell.setBackground(QColor(34, 139, 34)) # Dark green
cell.setForeground(QColor(255, 255, 255)) # White text
table.setItem(i, j, cell)
def refresh_all_data(self):
"""Refresh all tables with latest data"""
print("🔄 Refreshing analytics data...")
self.update_traffic_lights_table()
self.update_violations_table()
self.update_vehicles_table()
@Slot(dict)
def update_detection_data(self, detection_data):
"""Update analytics with detection data from video tab"""
try:
print(f"[ANALYTICS UPDATE] Received detection data with keys: {list(detection_data.keys())}")
self.latest_frame_data = detection_data
# Extract traffic lights
detections = detection_data.get('detections', [])
traffic_lights = []
vehicles = []
for detection in detections:
if hasattr(detection, 'label'):
label = detection.label
elif isinstance(detection, dict):
label = detection.get('label', detection.get('class', detection.get('class_name', '')))
else:
label = str(detection)
if 'traffic light' in str(label).lower():
traffic_lights.append(detection)
elif any(vehicle_type in str(label).lower() for vehicle_type in ['car', 'truck', 'bus', 'motorcycle']):
vehicles.append(detection)
self.latest_traffic_lights = traffic_lights
# Extract vehicle tracking data - Handle the EXACT structure from video controller
tracked_vehicles = detection_data.get('tracked_vehicles', [])
print(f"[ANALYTICS UPDATE] Found {len(tracked_vehicles)} tracked vehicles")
# Process tracked vehicles with the correct structure
processed_vehicles = []
for vehicle in tracked_vehicles:
print(f"[ANALYTICS UPDATE] Raw vehicle data: {vehicle}")
# Handle the actual structure: {id, bbox, center_y, is_moving, is_violation}
if isinstance(vehicle, dict):
track_id = vehicle.get('id', 'Unknown')
bbox = vehicle.get('bbox', [0, 0, 0, 0])
center_y = vehicle.get('center_y', 0)
moving = vehicle.get('is_moving', False)
violating = vehicle.get('is_violation', False)
# Calculate center_x from bbox
if len(bbox) >= 4:
center_x = (bbox[0] + bbox[2]) / 2
else:
center_x = 0
else:
# Fallback for other object types
track_id = getattr(vehicle, 'id', getattr(vehicle, 'track_id', 'Unknown'))
bbox = getattr(vehicle, 'bbox', [0, 0, 0, 0])
center_y = getattr(vehicle, 'center_y', 0)
moving = getattr(vehicle, 'is_moving', getattr(vehicle, 'moving', False))
violating = getattr(vehicle, 'is_violation', getattr(vehicle, 'violating', False))
if len(bbox) >= 4:
center_x = (bbox[0] + bbox[2]) / 2
else:
center_x = 0
processed_vehicles.append({
'track_id': track_id,
'center': (center_x, center_y),
'moving': moving,
'violating': violating
})
print(f"[ANALYTICS UPDATE] Processed vehicle ID={track_id}, center=({center_x:.1f}, {center_y:.1f}), moving={moving}, violating={violating}")
self.latest_vehicles = processed_vehicles
print(f"[ANALYTICS UPDATE] Stored {len(self.latest_vehicles)} processed vehicles")
# Update tables with new data
self.update_traffic_lights_table()
self.update_vehicles_table()
except Exception as e:
print(f"Error updating detection data: {e}")
import traceback
traceback.print_exc()
@Slot(dict)
def update_violation_data(self, violation_data):
"""Update violations data"""
try:
# Store violation data
track_id = violation_data.get('track_id')
violation_type = violation_data.get('type', 'Unknown')
# Add to violations list if not already present
existing = [v for v in self.latest_violations if v.get('track_id') == track_id]
if not existing:
self.latest_violations.append({
'track_id': track_id,
'type': violation_type,
'status': 'Active',
'timestamp': violation_data.get('timestamp', '')
})
self.update_violations_table()
except Exception as e:
print(f"Error updating violation data: {e}")
def update_traffic_lights_table(self):
"""Update traffic lights table with latest data"""
try:
data = []
# Check if we have traffic light data from frame analysis
latest_traffic_light = self.latest_frame_data.get('traffic_light', {})
if latest_traffic_light:
# Extract traffic light info
color = latest_traffic_light.get('color', 'unknown')
confidence = latest_traffic_light.get('confidence', 0.0)
# Create traffic light entries based on the detected signal
if color == 'red':
status = "🔴 Red"
red_ratio = confidence
yellow_ratio = 0.0
green_ratio = 0.0
elif color == 'yellow':
status = "🟡 Yellow"
red_ratio = 0.0
yellow_ratio = confidence
green_ratio = 0.0
elif color == 'green':
status = "🟢 Green"
red_ratio = 0.0
yellow_ratio = 0.0
green_ratio = confidence
else:
status = "❓ Unknown"
red_ratio = 0.0
yellow_ratio = 0.0
green_ratio = 0.0
data.append([
"Main Traffic Light",
f"{red_ratio:.3f}",
f"{yellow_ratio:.3f}",
f"{green_ratio:.3f}",
status
])
# Also check for individual traffic light detections
for i, tl in enumerate(self.latest_traffic_lights):
bbox = tl.get('bbox', [0, 0, 0, 0])
# Extract color ratios from debug data if available
color_info = tl.get('color_info', {})
red_ratio = color_info.get('red', 0.0)
yellow_ratio = color_info.get('yellow', 0.0)
green_ratio = color_info.get('green', 0.0)
# Determine status
if red_ratio > 0.3:
status = "🔴 Red"
elif yellow_ratio > 0.3:
status = "🟡 Yellow"
elif green_ratio > 0.3:
status = "🟢 Green"
else:
status = "❓ Unknown"
data.append([
f"Traffic Light {i+1}",
f"{red_ratio:.3f}",
f"{yellow_ratio:.3f}",
f"{green_ratio:.3f}",
status
])
# If no data, show empty table instead of sample data
if not data:
data = []
self.populate_table(self.traffic_table, data, "traffic_light")
except Exception as e:
print(f"Error updating traffic lights table: {e}")
def update_violations_table(self):
"""Update violations table with latest data"""
try:
data = []
for violation in self.latest_violations:
data.append([
str(violation.get('track_id', 'Unknown')),
f"🚨 {violation.get('type', 'Unknown')}",
violation.get('status', 'Active')
])
# If no violations, show empty table
if not data:
data = []
self.populate_table(self.violation_table, data, "violation")
except Exception as e:
print(f"Error updating violations table: {e}")
def update_vehicles_table(self):
"""Update vehicles table with latest data"""
try:
print(f"[ANALYTICS UPDATE] Updating vehicles table with {len(self.latest_vehicles)} vehicles")
data = []
for vehicle in self.latest_vehicles:
track_id = vehicle.get('track_id', 'Unknown')
center = vehicle.get('center', (0, 0))
position = f"({center[0]:.1f}, {center[1]:.1f})"
center_y = center[1] if len(center) > 1 else 0
moving = vehicle.get('moving', False)
violating = vehicle.get('violating', False)
if violating:
status = "🔴 Violating"
elif moving:
status = "🟡 Moving"
else:
status = "🟢 Stopped"
data.append([
str(track_id),
position,
f"{center_y:.1f}",
str(moving),
str(violating),
status
])
print(f"[ANALYTICS UPDATE] Added vehicle row: ID={track_id}, pos={position}, moving={moving}, violating={violating}, status={status}")
print(f"[ANALYTICS UPDATE] Total vehicle rows to display: {len(data)}")
# If no vehicles, show empty table
if not data:
data = []
self.populate_table(self.vehicle_table, data, "vehicle")
except Exception as e:
print(f"Error updating vehicles table: {e}")
import traceback
traceback.print_exc()
class AnalyticsTab(QWidget):
"""Main analytics tab with clean design"""
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
"""Initialize the main analytics interface"""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
# Create the clean analytics widget
self.analytics_widget = CleanAnalyticsWidget()
layout.addWidget(self.analytics_widget)
@Slot(dict)
def update_analytics(self, analytics):
"""Update analytics with new data"""
# Forward to the analytics widget
if hasattr(self.analytics_widget, 'update_detection_data'):
self.analytics_widget.update_detection_data(analytics)
@Slot(dict)
def update_detection_data(self, detection_data):
"""Update detection data from video tab"""
self.analytics_widget.update_detection_data(detection_data)
@Slot(dict)
def update_violation_data(self, violation_data):
"""Update violation data"""
self.analytics_widget.update_violation_data(violation_data)
@Slot(dict)
def update_smart_intersection_analytics(self, analytics_data):
"""Update smart intersection analytics"""
# Extract relevant data and forward
if 'detections' in analytics_data:
self.analytics_widget.update_detection_data(analytics_data)
if 'violations' in analytics_data:
for violation in analytics_data['violations']:
self.analytics_widget.update_violation_data(violation)