from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame, QComboBox, QCheckBox, QFileDialog, QProgressBar, QGroupBox, QTextEdit ) from PySide6.QtCore import Signal, Qt from PySide6.QtGui import QPixmap, QFont, QDragEnterEvent, QDropEvent import os class VideoDropZone(QFrame): """Simple drag and drop zone for video files""" video_dropped = Signal(str) def __init__(self): super().__init__() self.setupUI() self.setAcceptDrops(True) def setupUI(self): self.setObjectName("VideoDropZone") self.setStyleSheet(""" QFrame#VideoDropZone { background: #1E1E1E; border: 2px dashed #2C2C2C; border-radius: 12px; min-height: 200px; } QFrame#VideoDropZone:hover { border: 2px dashed #007BFF; background: #232323; } """) layout = QVBoxLayout(self) layout.setAlignment(Qt.AlignCenter) layout.setSpacing(16) # Drop icon icon_label = QLabel("📹") icon_label.setAlignment(Qt.AlignCenter) icon_label.setStyleSheet("font-size: 48px; color: #B0B0B0;") # Text text_label = QLabel("Drop video file here\nor click to browse") text_label.setAlignment(Qt.AlignCenter) text_label.setStyleSheet(""" color: #B0B0B0; font-size: 16px; line-height: 1.4; """) # Browse button self.browse_btn = QPushButton("Browse Files") self.browse_btn.setObjectName("primary") self.browse_btn.clicked.connect(self.browse_file) layout.addWidget(icon_label) layout.addWidget(text_label) layout.addWidget(self.browse_btn) def dragEnterEvent(self, event: QDragEnterEvent): if event.mimeData().hasUrls(): urls = event.mimeData().urls() if urls and urls[0].toLocalFile().lower().endswith(('.mp4', '.avi', '.mov', '.mkv', '.wmv')): event.acceptProposedAction() else: event.ignore() else: event.ignore() def dropEvent(self, event: QDropEvent): urls = event.mimeData().urls() if urls: file_path = urls[0].toLocalFile() if file_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv', '.wmv')): self.video_dropped.emit(file_path) event.acceptProposedAction() else: event.ignore() def browse_file(self): file_path, _ = QFileDialog.getOpenFileName( self, "Select Video File", "", "Video Files (*.mp4 *.avi *.mov *.mkv *.wmv);;All Files (*)" ) if file_path: self.video_dropped.emit(file_path) class VideoAnalysisTab(QWidget): """Clean and simple video analysis tab""" # Signals for data publishing video_selected = Signal(str) analysis_started = Signal() analysis_stopped = Signal() data_ready = Signal(dict) # For InfluxDB/MQTT/Grafana def __init__(self): super().__init__() self.current_video = None self.setupUI() def setupUI(self): # Set dark background self.setStyleSheet(""" QWidget { background: #121212; color: #FFFFFF; } QPushButton[objectName="primary"] { background: #007BFF; color: #FFFFFF; border-radius: 8px; font-weight: bold; padding: 12px 24px; font-size: 14px; border: none; min-width: 120px; } QPushButton[objectName="primary"]:hover { background: #3399FF; } QPushButton[objectName="secondary"] { background: #2ECC71; color: #FFFFFF; border-radius: 8px; padding: 12px 24px; font-size: 14px; border: none; min-width: 120px; } QPushButton[objectName="secondary"]:hover { background: #48D187; } QPushButton[objectName="warning"] { background: #E74C3C; color: #FFFFFF; border-radius: 8px; font-weight: bold; padding: 12px 24px; font-size: 14px; border: none; min-width: 120px; } QPushButton[objectName="warning"]:hover { background: #FF6B5A; } QGroupBox { border: 1px solid #2C2C2C; border-radius: 8px; margin-top: 16px; background: transparent; font-weight: bold; color: #FFFFFF; font-size: 14px; padding-top: 10px; } QGroupBox::title { subcontrol-origin: margin; left: 12px; top: 0px; padding: 0 8px; background: #121212; } """) # Main layout main_layout = QHBoxLayout(self) main_layout.setContentsMargins(20, 20, 20, 20) main_layout.setSpacing(20) # Left side - Video input and display left_widget = QWidget() left_layout = QVBoxLayout(left_widget) left_layout.setSpacing(20) # Video Input Section input_group = QGroupBox("📹 Video Input") input_layout = QVBoxLayout(input_group) input_layout.setSpacing(16) # Drop zone self.drop_zone = VideoDropZone() self.drop_zone.video_dropped.connect(self.load_video) # Current video info self.video_info = QLabel("No video selected") self.video_info.setStyleSheet(""" color: #B0B0B0; font-size: 12px; padding: 8px 12px; background: #1E1E1E; border-radius: 6px; """) input_layout.addWidget(self.drop_zone) input_layout.addWidget(self.video_info) # Video Display Section display_group = QGroupBox("đŸŽŦ Video Preview") display_layout = QVBoxLayout(display_group) # Video preview self.video_preview = QLabel("Video preview will appear here") self.video_preview.setAlignment(Qt.AlignCenter) self.video_preview.setMinimumHeight(300) self.video_preview.setStyleSheet(""" QLabel { background: #1E1E1E; border: 1px solid #2C2C2C; border-radius: 8px; color: #B0B0B0; font-size: 16px; } """) # Progress bar self.progress_bar = QProgressBar() self.progress_bar.setVisible(False) self.progress_bar.setStyleSheet(""" QProgressBar { border: 1px solid #2C2C2C; border-radius: 4px; background: #1E1E1E; text-align: center; color: #FFFFFF; font-size: 12px; } QProgressBar::chunk { background: #007BFF; border-radius: 3px; } """) display_layout.addWidget(self.video_preview) display_layout.addWidget(self.progress_bar) left_layout.addWidget(input_group) left_layout.addWidget(display_group) # Right side - Simple controls and data output right_widget = QWidget() right_widget.setFixedWidth(320) right_layout = QVBoxLayout(right_widget) right_layout.setSpacing(20) # Detection Settings detection_group = QGroupBox("đŸŽ¯ Detection Settings") detection_layout = QVBoxLayout(detection_group) detection_layout.setSpacing(12) self.detect_vehicles = QCheckBox("🚗 Detect Vehicles") self.detect_vehicles.setChecked(True) self.detect_vehicles.setStyleSheet("color: #FFFFFF; font-size: 12px;") self.detect_pedestrians = QCheckBox("đŸšļ Detect Pedestrians") self.detect_pedestrians.setChecked(True) self.detect_pedestrians.setStyleSheet("color: #FFFFFF; font-size: 12px;") self.detect_traffic_lights = QCheckBox("đŸšĻ Detect Traffic Lights") self.detect_traffic_lights.setChecked(True) self.detect_traffic_lights.setStyleSheet("color: #FFFFFF; font-size: 12px;") detection_layout.addWidget(self.detect_vehicles) detection_layout.addWidget(self.detect_pedestrians) detection_layout.addWidget(self.detect_traffic_lights) # Control Buttons controls_group = QGroupBox("🎮 Controls") controls_layout = QVBoxLayout(controls_group) controls_layout.setSpacing(12) self.start_btn = QPushButton("â–ļī¸ Start Analysis") self.start_btn.setObjectName("primary") self.start_btn.clicked.connect(self.start_analysis) self.start_btn.setEnabled(False) self.stop_btn = QPushButton("âšī¸ Stop Analysis") self.stop_btn.setObjectName("warning") self.stop_btn.clicked.connect(self.stop_analysis) self.stop_btn.setEnabled(False) # Data Publishing Button (Main feature for InfluxDB/MQTT/Grafana) self.publish_data_btn = QPushButton("📊 Publish to Grafana") self.publish_data_btn.setObjectName("secondary") self.publish_data_btn.clicked.connect(self.publish_data) self.publish_data_btn.setEnabled(False) controls_layout.addWidget(self.start_btn) controls_layout.addWidget(self.stop_btn) controls_layout.addWidget(self.publish_data_btn) # Live Results results_group = QGroupBox("📈 Live Results") results_layout = QVBoxLayout(results_group) results_layout.setSpacing(8) self.total_vehicles = QLabel("🚗 Vehicles: 0") self.total_vehicles.setStyleSheet("color: #00FFFF; font-size: 12px;") self.total_pedestrians = QLabel("đŸšļ Pedestrians: 0") self.total_pedestrians.setStyleSheet("color: #FFA500; font-size: 12px;") self.traffic_lights = QLabel("đŸšĻ Traffic Lights: 0") self.traffic_lights.setStyleSheet("color: #FFD700; font-size: 12px;") self.avg_speed = QLabel("⚡ Avg Speed: 0 km/h") self.avg_speed.setStyleSheet("color: #00FF00; font-size: 12px;") results_layout.addWidget(self.total_vehicles) results_layout.addWidget(self.total_pedestrians) results_layout.addWidget(self.traffic_lights) results_layout.addWidget(self.avg_speed) # Data Output Log log_group = QGroupBox("📋 Data Log") log_layout = QVBoxLayout(log_group) self.data_log = QTextEdit() self.data_log.setMaximumHeight(150) self.data_log.setStyleSheet(""" QTextEdit { background: #1E1E1E; border: 1px solid #2C2C2C; border-radius: 6px; color: #00E6E6; font-family: 'Consolas', monospace; font-size: 10px; padding: 8px; } """) self.data_log.setPlainText("📊 Data publishing log will appear here...") log_layout.addWidget(self.data_log) right_layout.addWidget(detection_group) right_layout.addWidget(controls_group) right_layout.addWidget(results_group) right_layout.addWidget(log_group) right_layout.addStretch() # Add to main layout main_layout.addWidget(left_widget, 2) main_layout.addWidget(right_widget, 1) def load_video(self, video_path): """Load video file""" if os.path.exists(video_path): self.current_video = video_path filename = os.path.basename(video_path) # Update video info self.video_info.setText(f"📹 {filename}") self.video_info.setStyleSheet(""" color: #00E6E6; font-size: 12px; font-weight: bold; padding: 8px 12px; background: #1E1E1E; border: 1px solid #00E6E6; border-radius: 6px; """) # Enable start button self.start_btn.setEnabled(True) # Update preview self.video_preview.setText(f"✅ Video loaded: {filename}\nReady for analysis") self.video_preview.setStyleSheet(""" QLabel { background: #1E1E1E; border: 1px solid #00E6E6; border-radius: 8px; color: #00E6E6; font-size: 14px; } """) self.video_selected.emit(video_path) self.log_data(f"📹 Video loaded: {filename}") def start_analysis(self): """Start video analysis""" if self.current_video: self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.progress_bar.setVisible(True) self.progress_bar.setValue(0) self.video_preview.setText("🔄 Analysis in progress...\nProcessing video frames") self.video_preview.setStyleSheet(""" QLabel { background: #1E1E1E; border: 1px solid #FFD700; border-radius: 8px; color: #FFD700; font-size: 14px; } """) self.analysis_started.emit() self.log_data("â–ļī¸ Analysis started") def stop_analysis(self): """Stop video analysis""" self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.publish_data_btn.setEnabled(True) self.progress_bar.setVisible(False) self.video_preview.setText("âšī¸ Analysis completed\nData ready for publishing") self.video_preview.setStyleSheet(""" QLabel { background: #1E1E1E; border: 1px solid #2ECC71; border-radius: 8px; color: #2ECC71; font-size: 14px; } """) self.analysis_stopped.emit() self.log_data("âšī¸ Analysis completed") def publish_data(self): """Publish data to InfluxDB/MQTT/Grafana""" # Prepare data for publishing data = { 'timestamp': 'now', 'video_file': os.path.basename(self.current_video) if self.current_video else '', 'total_vehicles': int(self.total_vehicles.text().split(': ')[1]), 'total_pedestrians': int(self.total_pedestrians.text().split(': ')[1]), 'traffic_lights': int(self.traffic_lights.text().split(': ')[1]), 'avg_speed': float(self.avg_speed.text().split(': ')[1].split(' ')[0]), 'detection_settings': { 'vehicles': self.detect_vehicles.isChecked(), 'pedestrians': self.detect_pedestrians.isChecked(), 'traffic_lights': self.detect_traffic_lights.isChecked() } } # Emit data for external handling self.data_ready.emit(data) # Update UI self.log_data("📊 Data published to InfluxDB/MQTT/Grafana") self.log_data(f"📈 Vehicles: {data['total_vehicles']}, Pedestrians: {data['total_pedestrians']}") # Show success self.video_preview.setText("📊 Data published successfully!\nCheck Grafana dashboard") self.video_preview.setStyleSheet(""" QLabel { background: #1E1E1E; border: 1px solid #00FF00; border-radius: 8px; color: #00FF00; font-size: 14px; } """) def update_progress(self, value): """Update progress bar""" self.progress_bar.setValue(value) def update_results(self, vehicles=0, pedestrians=0, traffic_lights=0, speed=0.0): """Update analysis results""" self.total_vehicles.setText(f"🚗 Vehicles: {vehicles}") self.total_pedestrians.setText(f"đŸšļ Pedestrians: {pedestrians}") self.traffic_lights.setText(f"đŸšĻ Traffic Lights: {traffic_lights}") self.avg_speed.setText(f"⚡ Avg Speed: {speed:.1f} km/h") def log_data(self, message): """Add message to data log""" import datetime timestamp = datetime.datetime.now().strftime("%H:%M:%S") self.data_log.append(f"[{timestamp}] {message}") # Keep log reasonable size if self.data_log.document().blockCount() > 50: cursor = self.data_log.textCursor() cursor.movePosition(cursor.Start) cursor.select(cursor.BlockUnderCursor) cursor.removeSelectedText() cursor.deleteChar() # Remove the newline """Advanced overlay for Smart Intersection analytics.""" def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" background: rgba(0,20,40,0.85); border: 2px solid #03DAC5; border-radius: 12px; color: #fff; font-family: 'Consolas', 'SF Mono', 'monospace'; font-size: 12px; """) self.setFixedHeight(140) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setAttribute(Qt.WA_TransparentForMouseEvents) layout = QVBoxLayout(self) layout.setContentsMargins(16, 12, 16, 12) layout.setSpacing(4) # Title title = QLabel("đŸšĻ Smart Intersection Analytics") title.setStyleSheet("color: #03DAC5; font-weight: bold; font-size: 14px;") layout.addWidget(title) # Scene data self.scene_label = QLabel("Scene: Multi-Camera Fusion") self.tracking_label = QLabel("Active Tracks: 0") self.roi_label = QLabel("ROI Events: 0") # Camera data self.camera_label = QLabel("Cameras: North(0) East(0) South(0) West(0)") # Analytics data self.analytics_label = QLabel("Analytics: Crosswalk(0) Lane(0) Safety(0)") for w in [self.scene_label, self.tracking_label, self.roi_label, self.camera_label, self.analytics_label]: w.setStyleSheet("color: #fff;") layout.addWidget(w) def update_smart_intersection(self, scene_data): """Update smart intersection specific data""" if not scene_data: return # Update tracking info active_tracks = scene_data.get('active_tracks', 0) self.tracking_label.setText(f"Active Tracks: {active_tracks}") # Update ROI events roi_events = scene_data.get('roi_events', 0) self.roi_label.setText(f"ROI Events: {roi_events}") # Update camera data cameras = scene_data.get('cameras', {}) north = cameras.get('north', 0) east = cameras.get('east', 0) south = cameras.get('south', 0) west = cameras.get('west', 0) self.camera_label.setText(f"Cameras: North({north}) East({east}) South({south}) West({west})") # Update analytics analytics = scene_data.get('analytics', {}) crosswalk = analytics.get('crosswalk_events', 0) lane = analytics.get('lane_events', 0) safety = analytics.get('safety_events', 0) self.analytics_label.setText(f"Analytics: Crosswalk({crosswalk}) Lane({lane}) Safety({safety})") class IntersectionROIWidget(QFrame): """Widget for defining and managing ROI regions for smart intersection""" roi_updated = Signal(dict) def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" QFrame { background: #1a1a1a; border: 1px solid #424242; border-radius: 8px; } """) self.setFixedWidth(300) layout = QVBoxLayout(self) layout.setContentsMargins(16, 16, 16, 16) # Title title = QLabel("đŸŽ¯ Region of Interest (ROI)") title.setStyleSheet("color: #03DAC5; font-weight: bold; font-size: 14px;") layout.addWidget(title) # ROI Type selection type_layout = QHBoxLayout() type_layout.addWidget(QLabel("Type:")) self.roi_type = QComboBox() self.roi_type.addItems(["Crosswalk", "Traffic Lane", "Safety Zone", "Intersection Center"]) type_layout.addWidget(self.roi_type) layout.addLayout(type_layout) # ROI List self.roi_list = QListWidget() self.roi_list.setMaximumHeight(120) layout.addWidget(self.roi_list) # ROI Controls roi_controls = QHBoxLayout() self.add_roi_btn = QPushButton("Add ROI") self.delete_roi_btn = QPushButton("Delete") self.add_roi_btn.setStyleSheet("background: #27ae60; color: white; border-radius: 4px; padding: 6px;") self.delete_roi_btn.setStyleSheet("background: #e74c3c; color: white; border-radius: 4px; padding: 6px;") roi_controls.addWidget(self.add_roi_btn) roi_controls.addWidget(self.delete_roi_btn) layout.addLayout(roi_controls) # Analytics settings analytics_group = QGroupBox("Analytics Settings") analytics_layout = QVBoxLayout(analytics_group) self.enable_tracking = QCheckBox("Multi-Object Tracking") self.enable_speed = QCheckBox("Speed Estimation") self.enable_direction = QCheckBox("Direction Analysis") self.enable_safety = QCheckBox("Safety Monitoring") for cb in [self.enable_tracking, self.enable_speed, self.enable_direction, self.enable_safety]: cb.setChecked(True) cb.setStyleSheet("color: white;") analytics_layout.addWidget(cb) layout.addWidget(analytics_group) # Connect signals self.add_roi_btn.clicked.connect(self._add_roi) self.delete_roi_btn.clicked.connect(self._delete_roi) # Initialize with default ROIs self._init_default_rois() def _init_default_rois(self): """Initialize with default intersection ROIs""" default_rois = [ "North Crosswalk", "South Crosswalk", "East Crosswalk", "West Crosswalk", "Center Intersection", "North Lane", "South Lane", "East Lane", "West Lane" ] for roi in default_rois: item = QListWidgetItem(roi) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) self.roi_list.addItem(item) def _add_roi(self): """Add new ROI""" roi_type = self.roi_type.currentText() roi_name = f"{roi_type}_{self.roi_list.count() + 1}" item = QListWidgetItem(roi_name) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) self.roi_list.addItem(item) self._emit_roi_update() def _delete_roi(self): """Delete selected ROI""" current_row = self.roi_list.currentRow() if current_row >= 0: self.roi_list.takeItem(current_row) self._emit_roi_update() def _emit_roi_update(self): """Emit ROI configuration update""" roi_config = { 'rois': [], 'analytics': { 'tracking': self.enable_tracking.isChecked(), 'speed': self.enable_speed.isChecked(), 'direction': self.enable_direction.isChecked(), 'safety': self.enable_safety.isChecked() } } for i in range(self.roi_list.count()): item = self.roi_list.item(i) roi_config['rois'].append({ 'name': item.text(), 'enabled': item.checkState() == Qt.Checked }) self.roi_updated.emit(roi_config) class MultiCameraView(QFrame): """Multi-camera view for smart intersection""" def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" QFrame { background: #0a0a0a; border: 2px solid #424242; border-radius: 8px; } """) layout = QGridLayout(self) layout.setContentsMargins(8, 8, 8, 8) layout.setSpacing(4) # Create camera views self.camera_views = {} positions = [('North', 0, 1), ('West', 1, 0), ('East', 1, 2), ('South', 2, 1)] for pos_name, row, col in positions: view = self._create_camera_view(pos_name) self.camera_views[pos_name.lower()] = view layout.addWidget(view, row, col) # Center intersection view center_view = self._create_intersection_center() layout.addWidget(center_view, 1, 1) def _create_camera_view(self, position): """Create individual camera view""" view = QFrame() view.setStyleSheet(""" background: #1a1a1a; border: 1px solid #555; border-radius: 4px; """) view.setMinimumSize(160, 120) view.setMaximumSize(200, 150) layout = QVBoxLayout(view) layout.setContentsMargins(4, 4, 4, 4) # Title title = QLabel(f"📹 {position}") title.setStyleSheet("color: #03DAC5; font-weight: bold; font-size: 10px;") title.setAlignment(Qt.AlignCenter) layout.addWidget(title) # Video area video_area = QLabel("No feed") video_area.setStyleSheet("background: #000; color: #666; border: 1px dashed #333;") video_area.setAlignment(Qt.AlignCenter) video_area.setMinimumHeight(80) layout.addWidget(video_area) # Stats stats = QLabel("Objects: 0") stats.setStyleSheet("color: #aaa; font-size: 9px;") stats.setAlignment(Qt.AlignCenter) layout.addWidget(stats) return view def _create_intersection_center(self): """Create center intersection overview""" view = QFrame() view.setStyleSheet(""" background: #2a1a1a; border: 2px solid #03DAC5; border-radius: 8px; """) view.setMinimumSize(160, 120) view.setMaximumSize(200, 150) layout = QVBoxLayout(view) layout.setContentsMargins(8, 8, 8, 8) title = QLabel("đŸšĻ Intersection") title.setStyleSheet("color: #03DAC5; font-weight: bold; font-size: 12px;") title.setAlignment(Qt.AlignCenter) layout.addWidget(title) # Intersection map map_area = QLabel("Scene Map") map_area.setStyleSheet("background: #000; color: #03DAC5; border: 1px solid #03DAC5;") map_area.setAlignment(Qt.AlignCenter) map_area.setMinimumHeight(80) layout.addWidget(map_area) # Total stats total_stats = QLabel("Total Objects: 0") total_stats.setStyleSheet("color: #03DAC5; font-size: 10px; font-weight: bold;") total_stats.setAlignment(Qt.AlignCenter) layout.addWidget(total_stats) return view def update_camera_feed(self, camera_position, pixmap, object_count=0): """Update specific camera feed""" if camera_position.lower() in self.camera_views: view = self.camera_views[camera_position.lower()] video_label = view.findChild(QLabel) if video_label and pixmap: scaled = pixmap.scaled(video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) video_label.setPixmap(scaled) # Update stats stats_labels = view.findChildren(QLabel) if len(stats_labels) >= 3: # title, video, stats stats_labels[2].setText(f"Objects: {object_count}") class DiagnosticOverlay(QFrame): """Semi-transparent overlay for diagnostics.""" def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" background: rgba(0,0,0,0.5); border-radius: 8px; color: #fff; font-family: 'Consolas', 'SF Mono', 'monospace'; font-size: 13px; """) # self.setFixedWidth(260) # Remove fixed width self.setFixedHeight(90) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) # Allow horizontal stretch self.setAttribute(Qt.WA_TransparentForMouseEvents) layout = QVBoxLayout(self) layout.setContentsMargins(12, 8, 12, 8) self.model_label = QLabel("Model: -") self.device_label = QLabel("Device: -") self.stats_label = QLabel("Cars: 0 | Trucks: 0 | Ped: 0 | TLights: 0 | Moto: 0") for w in [self.model_label, self.device_label, self.stats_label]: w.setStyleSheet("color: #fff;") layout.addWidget(w) layout.addStretch(1) def update_overlay(self, model, device, cars, trucks, peds, tlights, motorcycles): self.model_label.setText(f"Model: {model}") self.device_label.setText(f"Device: {device}") self.stats_label.setText(f"Cars: {cars} | Trucks: {trucks} | Ped: {peds} | TLights: {tlights} | Moto: {motorcycles}") class VideoDetectionTab(QWidget): file_selected = Signal(str) play_clicked = Signal() pause_clicked = Signal() stop_clicked = Signal() detection_toggled = Signal(bool) screenshot_clicked = Signal() seek_changed = Signal(int) auto_select_model_device = Signal() # Smart Intersection signals smart_intersection_enabled = Signal(bool) multi_camera_mode_enabled = Signal(bool) roi_configuration_changed = Signal(dict) scene_analytics_toggled = Signal(bool) def __init__(self): super().__init__() self.video_loaded = False self.smart_intersection_mode = False self.multi_camera_mode = False # Load smart intersection config self.load_smart_intersection_config() # Main layout main_layout = QHBoxLayout(self) main_layout.setContentsMargins(16, 16, 16, 16) main_layout.setSpacing(16) # Left panel - video and controls left_panel = self._create_left_panel() main_layout.addWidget(left_panel, 3) # 3/4 of the space # Right panel - smart intersection controls right_panel = self._create_right_panel() main_layout.addWidget(right_panel, 1) # 1/4 of the space def load_smart_intersection_config(self): """Load smart intersection configuration""" config_path = Path(__file__).parent.parent / "config" / "smart-intersection" / "desktop-config.json" try: if config_path.exists(): with open(config_path, 'r') as f: self.smart_config = json.load(f) else: self.smart_config = self._get_default_config() except Exception as e: print(f"Error loading smart intersection config: {e}") self.smart_config = self._get_default_config() def _get_default_config(self): """Get default smart intersection configuration""" return { "desktop_app_config": { "scene_analytics": { "enable_multi_camera": True, "enable_roi_analytics": True, "enable_vlm_integration": True }, "camera_settings": { "max_cameras": 4, "default_fps": 30 }, "analytics_settings": { "object_tracking": True, "speed_estimation": True, "direction_analysis": True, "safety_monitoring": True } } } def _create_left_panel(self): """Create main video panel""" panel = QWidget() layout = QVBoxLayout(panel) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(16) # Smart Intersection Mode Toggle mode_bar = self._create_mode_bar() layout.addWidget(mode_bar) # File select bar file_bar = self._create_file_bar() layout.addWidget(file_bar) # Video display area (with tabs for different modes) self.video_tabs = QTabWidget() self.video_tabs.setStyleSheet(""" QTabWidget::pane { border: 1px solid #424242; background: #121212; border-radius: 8px; } QTabBar::tab { background: #232323; color: #fff; padding: 8px 16px; margin-right: 2px; border-top-left-radius: 8px; border-top-right-radius: 8px; } QTabBar::tab:selected { background: #03DAC5; color: #000; } """) # Single camera tab self.single_cam_widget = self._create_single_camera_view() self.video_tabs.addTab(self.single_cam_widget, "📹 Single Camera") # Multi-camera tab self.multi_cam_widget = MultiCameraView() self.video_tabs.addTab(self.multi_cam_widget, "đŸšĻ Multi-Camera Intersection") layout.addWidget(self.video_tabs) # Analytics overlay self.analytics_overlay = self._create_analytics_overlay() layout.addWidget(self.analytics_overlay) # Control bar control_bar = self._create_control_bar() layout.addWidget(control_bar) return panel def _create_mode_bar(self): """Create smart intersection mode toggle bar""" bar = QFrame() bar.setStyleSheet(""" QFrame { background: #1a2332; border: 2px solid #03DAC5; border-radius: 12px; padding: 8px; } """) bar.setFixedHeight(60) layout = QHBoxLayout(bar) layout.setContentsMargins(16, 8, 16, 8) # Smart Intersection Toggle self.smart_intersection_toggle = QCheckBox("đŸšĻ Smart Intersection Mode") self.smart_intersection_toggle.setStyleSheet(""" QCheckBox { color: #03DAC5; font-weight: bold; font-size: 14px; } QCheckBox::indicator { width: 20px; height: 20px; } QCheckBox::indicator:checked { background: #03DAC5; border: 2px solid #03DAC5; border-radius: 4px; } """) self.smart_intersection_toggle.toggled.connect(self._toggle_smart_intersection) layout.addWidget(self.smart_intersection_toggle) layout.addSpacing(32) # Multi-camera Toggle self.multi_camera_toggle = QCheckBox("📹 Multi-Camera Fusion") self.multi_camera_toggle.setStyleSheet(""" QCheckBox { color: #e67e22; font-weight: bold; font-size: 14px; } QCheckBox::indicator { width: 20px; height: 20px; } QCheckBox::indicator:checked { background: #e67e22; border: 2px solid #e67e22; border-radius: 4px; } """) self.multi_camera_toggle.toggled.connect(self._toggle_multi_camera) layout.addWidget(self.multi_camera_toggle) layout.addStretch() # Status indicator self.mode_status = QLabel("Standard Detection Mode") self.mode_status.setStyleSheet("color: #bbb; font-size: 12px;") layout.addWidget(self.mode_status) return bar def _create_file_bar(self): """Create file selection bar""" widget = QWidget() bar = QHBoxLayout(widget) self.file_btn = QPushButton() self.file_btn.setIcon(QIcon.fromTheme("folder-video")) self.file_btn.setText("Select Video") self.file_btn.setStyleSheet("padding: 8px 18px; border-radius: 8px; background: #232323; color: #fff;") self.file_label = QLabel("No file selected") self.file_label.setStyleSheet("color: #bbb; font-size: 13px;") self.file_btn.clicked.connect(self._select_file) bar.addWidget(self.file_btn) bar.addWidget(self.file_label) bar.addStretch() return widget def _create_single_camera_view(self): """Create single camera view widget""" widget = QWidget() layout = QVBoxLayout(widget) layout.setContentsMargins(0, 0, 0, 0) # Video frame video_frame = QFrame() video_frame.setStyleSheet(""" background: #121212; border: 1px solid #424242; border-radius: 8px; """) video_frame.setMinimumSize(640, 360) video_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) video_layout = QVBoxLayout(video_frame) video_layout.setContentsMargins(0, 0, 0, 0) video_layout.setAlignment(Qt.AlignCenter) self.video_label = QLabel() self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setStyleSheet("background: transparent; color: #888; font-size: 18px;") self.video_label.setText("No video loaded. Please select a file.") self.video_label.setMinimumSize(640, 360) self.video_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) video_layout.addWidget(self.video_label) layout.addWidget(video_frame) return widget def _create_analytics_overlay(self): """Create analytics overlay that switches based on mode""" container = QWidget() self.overlay_layout = QVBoxLayout(container) self.overlay_layout.setContentsMargins(0, 0, 0, 0) # Standard overlay self.standard_overlay = DiagnosticOverlay() self.standard_overlay.setStyleSheet(self.standard_overlay.styleSheet() + "border: 1px solid #03DAC5;") # Smart intersection overlay self.smart_overlay = SmartIntersectionOverlay() # Badge bar self.badge_bar = QHBoxLayout() self.badge_bar.setContentsMargins(0, 8, 0, 8) self.fps_badge = QLabel("FPS: --") self.fps_badge.setStyleSheet("background: #27ae60; color: #fff; border-radius: 12px; padding: 4px 24px; font-weight: bold; font-size: 15px;") self.fps_badge.setAlignment(Qt.AlignCenter) self.inference_badge = QLabel("Inference: -- ms") self.inference_badge.setStyleSheet("background: #2980b9; color: #fff; border-radius: 12px; padding: 4px 24px; font-weight: bold; font-size: 15px;") self.inference_badge.setAlignment(Qt.AlignCenter) self.badge_bar.addWidget(self.fps_badge) self.badge_bar.addSpacing(12) self.badge_bar.addWidget(self.inference_badge) self.badge_bar.addSpacing(18) # Add current overlay (start with standard) self.current_overlay = self.standard_overlay self.badge_bar.addWidget(self.current_overlay) self.badge_bar.addStretch() self.overlay_layout.addLayout(self.badge_bar) return container def _create_control_bar(self): """Create control bar""" widget = QWidget() control_bar = QHBoxLayout(widget) control_bar.setContentsMargins(0, 16, 0, 0) # Playback controls self.play_btn = QPushButton() self.play_btn.setIcon(QIcon.fromTheme("media-playback-start")) self.play_btn.setToolTip("Play") self.play_btn.setFixedSize(48, 48) self.play_btn.setEnabled(False) self.play_btn.setStyleSheet(self._button_style()) self.pause_btn = QPushButton() self.pause_btn.setIcon(QIcon.fromTheme("media-playback-pause")) self.pause_btn.setToolTip("Pause") self.pause_btn.setFixedSize(48, 48) self.pause_btn.setEnabled(False) self.pause_btn.setStyleSheet(self._button_style()) self.stop_btn = QPushButton() self.stop_btn.setIcon(QIcon.fromTheme("media-playback-stop")) self.stop_btn.setToolTip("Stop") self.stop_btn.setFixedSize(48, 48) self.stop_btn.setEnabled(False) self.stop_btn.setStyleSheet(self._button_style()) for btn, sig in zip([self.play_btn, self.pause_btn, self.stop_btn], [self.play_clicked.emit, self.pause_clicked.emit, self.stop_clicked.emit]): btn.clicked.connect(sig) control_bar.addWidget(self.play_btn) control_bar.addWidget(self.pause_btn) control_bar.addWidget(self.stop_btn) control_bar.addSpacing(16) # Progress bar self.progress = QSlider(Qt.Horizontal) self.progress.setStyleSheet("QSlider::groove:horizontal { height: 6px; background: #232323; border-radius: 3px; } QSlider::handle:horizontal { background: #03DAC5; border-radius: 8px; width: 18px; }") self.progress.setMinimumWidth(240) self.progress.setEnabled(False) self.progress.valueChanged.connect(self.seek_changed.emit) control_bar.addWidget(self.progress, 2) self.timestamp = QLabel("00:00 / 00:00") self.timestamp.setStyleSheet("color: #bbb; font-size: 13px;") control_bar.addWidget(self.timestamp) control_bar.addSpacing(16) # Detection toggle & screenshot self.detection_toggle = QCheckBox("Enable Detection") self.detection_toggle.setChecked(True) self.detection_toggle.setStyleSheet("color: #fff; font-size: 14px;") self.detection_toggle.setEnabled(False) self.detection_toggle.toggled.connect(self.detection_toggled.emit) control_bar.addWidget(self.detection_toggle) self.screenshot_btn = QPushButton() self.screenshot_btn.setIcon(QIcon.fromTheme("camera-photo")) self.screenshot_btn.setText("Screenshot") self.screenshot_btn.setToolTip("Save current frame as image") self.screenshot_btn.setEnabled(False) self.screenshot_btn.setStyleSheet(self._button_style()) self.screenshot_btn.clicked.connect(self.screenshot_clicked.emit) control_bar.addWidget(self.screenshot_btn) control_bar.addStretch() return widget def _create_right_panel(self): """Create right panel for smart intersection controls""" panel = QScrollArea() panel.setWidgetResizable(True) panel.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) panel.setStyleSheet(""" QScrollArea { background: #1a1a1a; border: 1px solid #424242; border-radius: 8px; } """) content = QWidget() layout = QVBoxLayout(content) layout.setContentsMargins(16, 16, 16, 16) layout.setSpacing(16) # Smart Intersection Controls intersection_group = QGroupBox("đŸšĻ Smart Intersection") intersection_group.setStyleSheet(""" QGroupBox { color: #03DAC5; font-weight: bold; font-size: 14px; border: 2px solid #03DAC5; border-radius: 8px; margin-top: 12px; padding-top: 8px; } QGroupBox::title { subcontrol-origin: margin; left: 16px; padding: 0 8px 0 8px; } """) intersection_layout = QVBoxLayout(intersection_group) # Scene Analytics Toggle self.scene_analytics_toggle = QCheckBox("Scene Analytics") self.scene_analytics_toggle.setChecked(True) self.scene_analytics_toggle.setStyleSheet("color: white; font-size: 12px;") self.scene_analytics_toggle.toggled.connect(self.scene_analytics_toggled.emit) intersection_layout.addWidget(self.scene_analytics_toggle) # Multi-object tracking self.multi_tracking_toggle = QCheckBox("Multi-Object Tracking") self.multi_tracking_toggle.setChecked(True) self.multi_tracking_toggle.setStyleSheet("color: white; font-size: 12px;") intersection_layout.addWidget(self.multi_tracking_toggle) # Speed estimation self.speed_estimation_toggle = QCheckBox("Speed Estimation") self.speed_estimation_toggle.setChecked(True) self.speed_estimation_toggle.setStyleSheet("color: white; font-size: 12px;") intersection_layout.addWidget(self.speed_estimation_toggle) layout.addWidget(intersection_group) # ROI Management self.roi_widget = IntersectionROIWidget() self.roi_widget.roi_updated.connect(self.roi_configuration_changed.emit) layout.addWidget(self.roi_widget) # Analytics Summary analytics_group = QGroupBox("📊 Analytics Summary") analytics_group.setStyleSheet(intersection_group.styleSheet().replace("#03DAC5", "#e67e22")) analytics_layout = QVBoxLayout(analytics_group) self.total_objects_label = QLabel("Total Objects: 0") self.crosswalk_events_label = QLabel("Crosswalk Events: 0") self.lane_events_label = QLabel("Lane Violations: 0") self.safety_alerts_label = QLabel("Safety Alerts: 0") for label in [self.total_objects_label, self.crosswalk_events_label, self.lane_events_label, self.safety_alerts_label]: label.setStyleSheet("color: white; font-size: 12px;") analytics_layout.addWidget(label) layout.addWidget(analytics_group) # Performance Monitoring perf_group = QGroupBox("⚡ Performance") perf_group.setStyleSheet(intersection_group.styleSheet().replace("#03DAC5", "#9b59b6")) perf_layout = QVBoxLayout(perf_group) self.gpu_usage_label = QLabel("GPU Usage: -%") self.memory_usage_label = QLabel("Memory: - MB") self.processing_time_label = QLabel("Processing: - ms") for label in [self.gpu_usage_label, self.memory_usage_label, self.processing_time_label]: label.setStyleSheet("color: white; font-size: 12px;") perf_layout.addWidget(label) layout.addWidget(perf_group) layout.addStretch() panel.setWidget(content) return panel def _toggle_smart_intersection(self, enabled): """Toggle smart intersection mode""" self.smart_intersection_mode = enabled self.smart_intersection_enabled.emit(enabled) # Switch overlay if enabled: self._switch_to_smart_overlay() self.mode_status.setText("đŸšĻ Smart Intersection Active") self.mode_status.setStyleSheet("color: #03DAC5; font-weight: bold; font-size: 12px;") else: self._switch_to_standard_overlay() self.mode_status.setText("Standard Detection Mode") self.mode_status.setStyleSheet("color: #bbb; font-size: 12px;") # Enable/disable multi-camera toggle self.multi_camera_toggle.setEnabled(enabled) if not enabled: self.multi_camera_toggle.setChecked(False) def _toggle_multi_camera(self, enabled): """Toggle multi-camera mode""" self.multi_camera_mode = enabled self.multi_camera_mode_enabled.emit(enabled) if enabled: self.video_tabs.setCurrentIndex(1) # Switch to multi-camera tab self.mode_status.setText("đŸšĻ Multi-Camera Intersection Active") else: self.video_tabs.setCurrentIndex(0) # Switch to single camera tab if self.smart_intersection_mode: self.mode_status.setText("đŸšĻ Smart Intersection Active") def _switch_to_smart_overlay(self): """Switch to smart intersection overlay""" self.badge_bar.removeWidget(self.current_overlay) self.current_overlay.setParent(None) self.current_overlay = self.smart_overlay self.badge_bar.addWidget(self.current_overlay) def _switch_to_standard_overlay(self): """Switch to standard overlay""" self.badge_bar.removeWidget(self.current_overlay) self.current_overlay.setParent(None) self.current_overlay = self.standard_overlay self.badge_bar.addWidget(self.current_overlay) def _button_style(self): return """ QPushButton { background: #232323; border-radius: 24px; color: #fff; font-size: 15px; border: none; } QPushButton:hover { background: #03DAC5; color: #222; } QPushButton:pressed { background: #018786; } """ def _select_file(self): file_path, _ = QFileDialog.getOpenFileName(self, "Select Video File", "", "Video Files (*.mp4 *.avi *.mov *.mkv *.webm);;All Files (*)") if file_path: self.file_label.setText(file_path) self.file_selected.emit(file_path) self.video_loaded = True self._enable_controls(True) self.video_label.setText("") self.auto_select_model_device.emit() def _enable_controls(self, enabled): self.play_btn.setEnabled(enabled) self.pause_btn.setEnabled(enabled) self.stop_btn.setEnabled(enabled) self.progress.setEnabled(enabled) self.detection_toggle.setEnabled(enabled) self.screenshot_btn.setEnabled(enabled) if enabled: self.auto_select_model_device.emit() def update_display(self, pixmap): """Update display with new frame""" if pixmap: if self.multi_camera_mode: # In multi-camera mode, distribute to different camera views # For now, just update the single view scaled = pixmap.scaled(self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.video_label.setPixmap(scaled) else: scaled = pixmap.scaled(self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.video_label.setPixmap(scaled) self._set_controls_enabled(True) self.video_label.setStyleSheet("background: transparent; color: #888; font-size: 18px;") else: self.video_label.clear() self.video_label.setText("No video loaded. Please select a video file.") self._set_controls_enabled(False) self.video_label.setStyleSheet("background: transparent; color: #F44336; font-size: 18px;") def _set_controls_enabled(self, enabled): for btn in [self.play_btn, self.pause_btn, self.stop_btn, self.progress, self.detection_toggle, self.screenshot_btn]: btn.setEnabled(enabled) def update_stats(self, stats): """Update statistics display""" if self.smart_intersection_mode: # Update smart intersection overlay scene_data = { 'active_tracks': stats.get('total_objects', 0), 'roi_events': stats.get('roi_events', 0), 'cameras': { 'north': stats.get('north_objects', 0), 'east': stats.get('east_objects', 0), 'south': stats.get('south_objects', 0), 'west': stats.get('west_objects', 0) }, 'analytics': { 'crosswalk_events': stats.get('crosswalk_events', 0), 'lane_events': stats.get('lane_events', 0), 'safety_events': stats.get('safety_events', 0) } } self.smart_overlay.update_smart_intersection(scene_data) # Update right panel analytics self.total_objects_label.setText(f"Total Objects: {stats.get('total_objects', 0)}") self.crosswalk_events_label.setText(f"Crosswalk Events: {stats.get('crosswalk_events', 0)}") self.lane_events_label.setText(f"Lane Violations: {stats.get('lane_events', 0)}") self.safety_alerts_label.setText(f"Safety Alerts: {stats.get('safety_events', 0)}") else: # Update standard overlay cars = stats.get('cars', 0) trucks = stats.get('trucks', 0) peds = stats.get('peds', 0) tlights = stats.get('tlights', 0) motorcycles = stats.get('motorcycles', 0) model = stats.get('model', stats.get('model_name', '-')) device = stats.get('device', stats.get('device_name', '-')) self.standard_overlay.update_overlay(model, device, cars, trucks, peds, tlights, motorcycles) # Update performance badges fps = stats.get('fps', None) inference = stats.get('inference', stats.get('detection_time', stats.get('detection_time_ms', None))) if fps is not None: self.fps_badge.setText(f"FPS: {fps:.2f}") else: self.fps_badge.setText("FPS: --") if inference is not None: self.inference_badge.setText(f"Inference: {inference:.1f} ms") else: self.inference_badge.setText("Inference: -- ms") # Update performance panel self.gpu_usage_label.setText(f"GPU Usage: {stats.get('gpu_usage', 0):.1f}%") self.memory_usage_label.setText(f"Memory: {stats.get('memory_usage', 0):.1f} MB") self.processing_time_label.setText(f"Processing: {stats.get('processing_time', 0):.1f} ms") def update_progress(self, value, max_value, timestamp): self.progress.setMaximum(max_value) self.progress.setValue(value) if isinstance(timestamp, float) or isinstance(timestamp, int): timestamp_str = f"{timestamp:.2f}" else: timestamp_str = str(timestamp) self.timestamp.setText(timestamp_str) def update_multi_camera_feed(self, camera_position, pixmap, object_count=0): """Update specific camera feed in multi-camera mode""" if self.multi_camera_mode: self.multi_cam_widget.update_camera_feed(camera_position, pixmap, object_count) def get_smart_intersection_config(self): """Get current smart intersection configuration""" return { 'enabled': self.smart_intersection_mode, 'multi_camera': self.multi_camera_mode, 'scene_analytics': self.scene_analytics_toggle.isChecked(), 'multi_tracking': self.multi_tracking_toggle.isChecked(), 'speed_estimation': self.speed_estimation_toggle.isChecked() }