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)