from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QLineEdit, QLabel, QPushButton, QSplitter, QHeaderView, QComboBox, QGroupBox, QFormLayout ) from PySide6.QtCore import Qt, Slot from PySide6.QtGui import QPixmap, QColor from datetime import datetime import os class ViolationsTab(QWidget): """Tab for displaying and managing traffic violations.""" def __init__(self): super().__init__() self.initUI() self.violations_data = [] def initUI(self): """Initialize UI components""" layout = QVBoxLayout(self) # Add status label for violations self.status_label = QLabel("🟢 Red Light Violation Detection Active") self.status_label.setStyleSheet("font-size: 16px; color: #22AA22; font-weight: bold; padding: 10px;") self.status_label.setAlignment(Qt.AlignCenter) layout.addWidget(self.status_label) # Search and filter controls filter_layout = QHBoxLayout() self.search_box = QLineEdit() self.search_box.setPlaceholderText("Search violations...") self.search_box.textChanged.connect(self.filter_violations) self.filter_combo = QComboBox() self.filter_combo.addItem("All Types") self.filter_combo.addItem("Red Light") self.filter_combo.addItem("Stop Sign") self.filter_combo.addItem("Speed") self.filter_combo.addItem("Lane") self.filter_combo.currentTextChanged.connect(self.filter_violations) filter_layout.addWidget(QLabel("Filter:")) filter_layout.addWidget(self.filter_combo) filter_layout.addStretch(1) filter_layout.addWidget(QLabel("Search:")) filter_layout.addWidget(self.search_box) layout.addLayout(filter_layout) # Splitter for table and details splitter = QSplitter(Qt.Horizontal) # Violations table self.table = QTableWidget(0, 5) self.table.setHorizontalHeaderLabels(["ID", "Type", "Timestamp", "Details", "Vehicle"]) self.table.setSelectionBehavior(QTableWidget.SelectRows) self.table.setSelectionMode(QTableWidget.SingleSelection) self.table.setEditTriggers(QTableWidget.NoEditTriggers) self.table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch) self.table.verticalHeader().setVisible(False) self.table.setAlternatingRowColors(True) self.table.setStyleSheet("alternate-background-color: rgba(240, 240, 240, 100);") self.table.selectionModel().selectionChanged.connect(self.on_violation_selected) splitter.addWidget(self.table) # Violation details panel details_panel = QWidget() details_layout = QVBoxLayout(details_panel) # Violation info info_group = QGroupBox("Violation Details") info_layout = QFormLayout(info_group) self.violation_type_label = QLabel("--") self.violation_time_label = QLabel("--") self.violation_details_label = QLabel("--") self.violation_vehicle_label = QLabel("--") self.violation_location_label = QLabel("--") info_layout.addRow("Type:", self.violation_type_label) info_layout.addRow("Time:", self.violation_time_label) info_layout.addRow("Details:", self.violation_details_label) info_layout.addRow("Vehicle ID:", self.violation_vehicle_label) info_layout.addRow("Location:", self.violation_location_label) details_layout.addWidget(info_group) # Snapshot preview snapshot_group = QGroupBox("Violation Snapshot") snapshot_layout = QVBoxLayout(snapshot_group) self.preview_label = QLabel() self.preview_label.setAlignment(Qt.AlignCenter) self.preview_label.setMinimumSize(320, 240) self.preview_label.setStyleSheet("background-color: #222; border: 1px solid #444;") snapshot_layout.addWidget(self.preview_label) details_layout.addWidget(snapshot_group) # Actions actions_layout = QHBoxLayout() self.export_btn = QPushButton("Export Report") self.dismiss_btn = QPushButton("Dismiss") actions_layout.addWidget(self.export_btn) actions_layout.addWidget(self.dismiss_btn) details_layout.addLayout(actions_layout) details_layout.addStretch(1) splitter.addWidget(details_panel) splitter.setSizes([600, 400]) # Initial sizes layout.addWidget(splitter) # Status bar status_layout = QHBoxLayout() self.status_label = QLabel("No violations recorded") status_layout.addWidget(self.status_label) self.clear_btn = QPushButton("Clear All") status_layout.addWidget(self.clear_btn) layout.addLayout(status_layout) @Slot() def filter_violations(self): """Filter violations based on search text and type filter""" search_text = self.search_box.text().lower() filter_type = self.filter_combo.currentText() self.table.setRowCount(0) filtered_count = 0 for violation in self.violations_data: # Filter by type if filter_type != "All Types": violation_type = violation.get('type', '').lower() filter_match = filter_type.lower() in violation_type if not filter_match: continue # Filter by search text if search_text: # Search in multiple fields searchable_text = ( violation.get('type', '').lower() + ' ' + violation.get('details', '').lower() + ' ' + str(violation.get('vehicle_id', '')).lower() + ' ' + str(violation.get('timestamp_str', '')).lower() ) if search_text not in searchable_text: continue # Add row for matching violation row_position = self.table.rowCount() self.table.insertRow(row_position) # Create violation ID violation_id = violation.get('id', filtered_count + 1) self.table.setItem(row_position, 0, QTableWidgetItem(str(violation_id))) # Format violation type violation_type = violation.get('type', '').replace('_', ' ').title() type_item = QTableWidgetItem(violation_type) # Color-code by violation type if 'red light' in violation_type.lower(): type_item.setForeground(QColor(255, 0, 0)) elif 'stop sign' in violation_type.lower(): type_item.setForeground(QColor(255, 140, 0)) elif 'speed' in violation_type.lower(): type_item.setForeground(QColor(0, 0, 255)) self.table.setItem(row_position, 1, type_item) # Format timestamp timestamp = violation.get('timestamp', 0) if isinstance(timestamp, (int, float)): timestamp_str = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") violation['timestamp_str'] = timestamp_str # Store for search else: timestamp_str = str(timestamp) self.table.setItem(row_position, 2, QTableWidgetItem(timestamp_str)) # Details self.table.setItem(row_position, 3, QTableWidgetItem(violation.get('details', ''))) # Vehicle ID self.table.setItem(row_position, 4, QTableWidgetItem(str(violation.get('vehicle_id', '')))) filtered_count += 1 # Update status self.status_label.setText(f"Showing {filtered_count} of {len(self.violations_data)} violations") @Slot() def on_violation_selected(self): """Handle violation selection in table""" selected_items = self.table.selectedItems() if not selected_items: return row = selected_items[0].row() violation_id = int(self.table.item(row, 0).text()) # Find violation in data violation = None for v in self.violations_data: if v.get('id', -1) == violation_id: violation = v break if not violation: return # Update details panel with enhanced information violation_type = violation.get('violation_type', 'red_light').replace('_', ' ').title() # Add traffic light confidence if available traffic_light_info = violation.get('traffic_light', {}) if isinstance(traffic_light_info, dict) and 'confidence' in traffic_light_info: tl_color = traffic_light_info.get('color', 'red').upper() tl_confidence = traffic_light_info.get('confidence', 0.0) violation_type = f"{violation_type} - {tl_color} ({tl_confidence:.2f})" self.violation_type_label.setText(violation_type) # Format timestamp timestamp = violation.get('timestamp', 0) if isinstance(timestamp, (int, float)): timestamp_str = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") else: timestamp_str = str(timestamp) self.violation_time_label.setText(timestamp_str) # Add vehicle details with confidence vehicle_type = violation.get('vehicle_type', 'Unknown').capitalize() vehicle_confidence = violation.get('confidence', 0.0) details = f"{vehicle_type} (Conf: {vehicle_confidence:.2f})" self.violation_details_label.setText(details) self.violation_vehicle_label.setText(str(violation.get('track_id', '--'))) # Format location if 'bbox' in violation: bbox = violation['bbox'] loc_str = f"X: {int(bbox[0])}, Y: {int(bbox[1])}" else: loc_str = "Unknown" self.violation_location_label.setText(loc_str) # Update snapshot if available if 'snapshot' in violation and violation['snapshot'] is not None: self.preview_label.setPixmap(QPixmap(violation['snapshot'])) else: self.preview_label.setText("No snapshot available") @Slot(list) def update_violations(self, violations): """ Update violations list. Args: violations: List of violation dictionaries """ # Store violations data for violation in violations: # Check if already in list (by timestamp and vehicle ID) is_duplicate = False for existing in self.violations_data: if (existing.get('timestamp') == violation.get('timestamp') and existing.get('vehicle_id') == violation.get('vehicle_id')): is_duplicate = True break if not is_duplicate: # Assign ID violation['id'] = len(self.violations_data) + 1 self.violations_data.append(violation) # Refresh display self.filter_violations() def clear_all_violations(self): """Clear all violation data""" self.violations_data = [] self.table.setRowCount(0) self.status_label.setText("No violations recorded") # Clear details self.violation_type_label.setText("--") self.violation_time_label.setText("--") self.violation_details_label.setText("--") self.violation_vehicle_label.setText("--") self.violation_location_label.setText("--") self.preview_label.clear() self.preview_label.setText("No violation selected") @Slot(object) def add_violation(self, violation): """ Add a new violation to the table. Args: violation: Dictionary with violation information """ try: # Update status to show active violations self.status_label.setText(f"🚨 RED LIGHT VIOLATION DETECTED - Total: {len(self.violations_data) + 1}") self.status_label.setStyleSheet("font-size: 16px; color: #FF2222; font-weight: bold; padding: 10px;") # Add to violations data self.violations_data.append(violation) # Add to table row = self.table.rowCount() self.table.insertRow(row) # Format timestamp timestamp_str = violation['timestamp'].strftime("%Y-%m-%d %H:%M:%S") # Set table items with enhanced information self.table.setItem(row, 0, QTableWidgetItem(str(violation['id']))) # Check for traffic light confidence information traffic_light_info = violation.get('traffic_light', {}) if traffic_light_info and isinstance(traffic_light_info, dict): tl_confidence = traffic_light_info.get('confidence', 0.0) violation_type = f"Red Light ({tl_confidence:.2f})" else: violation_type = "Red Light" self.table.setItem(row, 1, QTableWidgetItem(violation_type)) self.table.setItem(row, 2, QTableWidgetItem(timestamp_str)) # Add vehicle type and detection confidence vehicle_type = violation.get('vehicle_type', 'Unknown').capitalize() self.table.setItem(row, 3, QTableWidgetItem(f"{vehicle_type}")) self.table.setItem(row, 4, QTableWidgetItem(f"{violation.get('confidence', 0.0):.2f}")) # Highlight new row for col in range(5): item = self.table.item(row, col) if item: item.setBackground(QColor(255, 200, 200)) # Load snapshot if available if violation.get('snapshot_path') and os.path.exists(violation['snapshot_path']): pixmap = QPixmap(violation['snapshot_path']) if not pixmap.isNull(): # Store reference to avoid garbage collection violation['pixmap'] = pixmap except Exception as e: print(f"❌ Error adding violation to UI: {e}") import traceback traceback.print_exc()