cleanup and files added
This commit is contained in:
315
qt_app_pyside1/ui/widgets/video_display_widget.py
Normal file
315
qt_app_pyside1/ui/widgets/video_display_widget.py
Normal file
@@ -0,0 +1,315 @@
|
||||
"""
|
||||
Video Display Widget - Modern video player with detection overlays
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame
|
||||
from PySide6.QtCore import Qt, Signal, QTimer, QRect
|
||||
from PySide6.QtGui import QPainter, QPixmap, QColor, QFont, QPen, QBrush
|
||||
|
||||
class VideoDisplayWidget(QWidget):
|
||||
"""
|
||||
Modern video display widget with detection overlays and controls
|
||||
|
||||
Features:
|
||||
- Video frame display with aspect ratio preservation
|
||||
- Detection bounding boxes with confidence scores
|
||||
- Tracking ID display
|
||||
- Recording indicator
|
||||
- Camera status overlay
|
||||
- Fullscreen support via double-click
|
||||
"""
|
||||
|
||||
# Signals
|
||||
double_clicked = Signal()
|
||||
recording_toggled = Signal(bool)
|
||||
snapshot_requested = Signal()
|
||||
|
||||
def __init__(self, camera_id="camera_1", parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.camera_id = camera_id
|
||||
self.current_frame = None
|
||||
self.detections = []
|
||||
self.confidence_threshold = 0.5
|
||||
self.is_recording = False
|
||||
self.camera_status = "offline"
|
||||
|
||||
# Overlay settings
|
||||
self.overlay_settings = {
|
||||
'show_boxes': True,
|
||||
'show_tracks': True,
|
||||
'show_speed': False,
|
||||
'show_confidence': True
|
||||
}
|
||||
|
||||
# Colors for different detection classes
|
||||
self.class_colors = {
|
||||
'car': QColor('#3498db'), # Blue
|
||||
'truck': QColor('#e74c3c'), # Red
|
||||
'bus': QColor('#f39c12'), # Orange
|
||||
'motorcycle': QColor('#9b59b6'), # Purple
|
||||
'bicycle': QColor('#1abc9c'), # Turquoise
|
||||
'person': QColor('#2ecc71'), # Green
|
||||
'default': QColor('#95a5a6') # Gray
|
||||
}
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
print(f"📺 Video Display Widget initialized for {camera_id}")
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup the video display UI"""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(2, 2, 2, 2)
|
||||
layout.setSpacing(2)
|
||||
|
||||
# Header with camera info
|
||||
header = self._create_header()
|
||||
layout.addWidget(header)
|
||||
|
||||
# Video display area
|
||||
self.video_frame = QFrame()
|
||||
self.video_frame.setMinimumSize(320, 240)
|
||||
self.video_frame.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #2c3e50;
|
||||
border: 1px solid #34495e;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.video_frame, 1)
|
||||
|
||||
# Footer with controls
|
||||
footer = self._create_footer()
|
||||
layout.addWidget(footer)
|
||||
|
||||
def _create_header(self):
|
||||
"""Create header with camera info"""
|
||||
header = QFrame()
|
||||
header.setFixedHeight(25)
|
||||
header.setStyleSheet("background-color: rgba(0, 0, 0, 0.7); border-radius: 2px;")
|
||||
|
||||
layout = QHBoxLayout(header)
|
||||
layout.setContentsMargins(5, 2, 5, 2)
|
||||
|
||||
# Camera name
|
||||
self.camera_label = QLabel(self.camera_id.replace('_', ' ').title())
|
||||
self.camera_label.setStyleSheet("color: white; font-weight: bold; font-size: 8pt;")
|
||||
layout.addWidget(self.camera_label)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Status indicator
|
||||
self.status_label = QLabel("●")
|
||||
self.status_label.setStyleSheet("color: #e74c3c; font-size: 10pt;")
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
# Recording indicator
|
||||
self.recording_indicator = QLabel()
|
||||
self.recording_indicator.setStyleSheet("color: #e74c3c; font-size: 8pt;")
|
||||
self.recording_indicator.hide()
|
||||
layout.addWidget(self.recording_indicator)
|
||||
|
||||
return header
|
||||
|
||||
def _create_footer(self):
|
||||
"""Create footer with quick controls"""
|
||||
footer = QFrame()
|
||||
footer.setFixedHeight(20)
|
||||
footer.setStyleSheet("background-color: rgba(0, 0, 0, 0.5); border-radius: 2px;")
|
||||
|
||||
layout = QHBoxLayout(footer)
|
||||
layout.setContentsMargins(2, 1, 2, 1)
|
||||
|
||||
# Quick action buttons (small)
|
||||
snapshot_btn = QPushButton("📸")
|
||||
snapshot_btn.setFixedSize(16, 16)
|
||||
snapshot_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 6pt;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 2px;
|
||||
}
|
||||
""")
|
||||
snapshot_btn.clicked.connect(self.take_snapshot)
|
||||
layout.addWidget(snapshot_btn)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# FPS counter
|
||||
self.fps_label = QLabel("-- fps")
|
||||
self.fps_label.setStyleSheet("color: white; font-size: 6pt;")
|
||||
layout.addWidget(self.fps_label)
|
||||
|
||||
return footer
|
||||
|
||||
def set_frame(self, frame):
|
||||
"""Set the current video frame"""
|
||||
self.current_frame = frame
|
||||
self.update() # Trigger repaint
|
||||
|
||||
def add_detections(self, detections):
|
||||
"""Add detection results to display"""
|
||||
self.detections = detections
|
||||
self.update() # Trigger repaint
|
||||
|
||||
def set_confidence_threshold(self, threshold):
|
||||
"""Set confidence threshold for detection display"""
|
||||
self.confidence_threshold = threshold
|
||||
self.update()
|
||||
|
||||
def update_overlay_settings(self, settings):
|
||||
"""Update overlay display settings"""
|
||||
self.overlay_settings.update(settings)
|
||||
self.update()
|
||||
|
||||
def set_recording_indicator(self, recording):
|
||||
"""Set recording status indicator"""
|
||||
self.is_recording = recording
|
||||
if recording:
|
||||
self.recording_indicator.setText("🔴 REC")
|
||||
self.recording_indicator.show()
|
||||
else:
|
||||
self.recording_indicator.hide()
|
||||
|
||||
def set_camera_status(self, status):
|
||||
"""Set camera status (online/offline/error)"""
|
||||
self.camera_status = status
|
||||
|
||||
status_colors = {
|
||||
'online': '#27ae60', # Green
|
||||
'offline': '#e74c3c', # Red
|
||||
'error': '#f39c12', # Orange
|
||||
'connecting': '#3498db' # Blue
|
||||
}
|
||||
|
||||
color = status_colors.get(status, '#e74c3c')
|
||||
self.status_label.setStyleSheet(f"color: {color}; font-size: 10pt;")
|
||||
|
||||
def take_snapshot(self):
|
||||
"""Take a snapshot of current frame"""
|
||||
self.snapshot_requested.emit()
|
||||
|
||||
# Visual feedback
|
||||
self.setStyleSheet("border: 2px solid white;")
|
||||
QTimer.singleShot(200, lambda: self.setStyleSheet(""))
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
"""Handle double-click for fullscreen"""
|
||||
if event.button() == Qt.LeftButton:
|
||||
self.double_clicked.emit()
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""Custom paint event for video and overlays"""
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
|
||||
# Get drawing area
|
||||
rect = self.video_frame.geometry()
|
||||
|
||||
# Draw video frame if available
|
||||
if self.current_frame is not None:
|
||||
# Scale frame to fit widget while maintaining aspect ratio
|
||||
scaled_pixmap = self.current_frame.scaled(
|
||||
rect.size(),
|
||||
Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation
|
||||
)
|
||||
|
||||
# Center the image
|
||||
x = rect.x() + (rect.width() - scaled_pixmap.width()) // 2
|
||||
y = rect.y() + (rect.height() - scaled_pixmap.height()) // 2
|
||||
|
||||
painter.drawPixmap(x, y, scaled_pixmap)
|
||||
|
||||
# Draw detection overlays
|
||||
if self.detections and any(self.overlay_settings.values()):
|
||||
self._draw_detections(painter, rect, scaled_pixmap.size())
|
||||
else:
|
||||
# Draw placeholder
|
||||
painter.fillRect(rect, QColor(44, 62, 80))
|
||||
painter.setPen(QColor(255, 255, 255))
|
||||
painter.setFont(QFont("Arial", 12))
|
||||
painter.drawText(rect, Qt.AlignCenter, "No Signal")
|
||||
|
||||
def _draw_detections(self, painter, display_rect, frame_size):
|
||||
"""Draw detection overlays"""
|
||||
# Calculate scaling factors
|
||||
scale_x = frame_size.width() / display_rect.width()
|
||||
scale_y = frame_size.height() / display_rect.height()
|
||||
|
||||
for detection in self.detections:
|
||||
# Skip low confidence detections
|
||||
confidence = detection.get('confidence', 0)
|
||||
if confidence < self.confidence_threshold:
|
||||
continue
|
||||
|
||||
# Get detection info
|
||||
bbox = detection.get('bbox', [0, 0, 0, 0]) # [x, y, w, h]
|
||||
class_name = detection.get('class', 'unknown')
|
||||
track_id = detection.get('track_id', None)
|
||||
speed = detection.get('speed', None)
|
||||
|
||||
# Scale bounding box to display coordinates
|
||||
x = int(bbox[0] / scale_x) + display_rect.x()
|
||||
y = int(bbox[1] / scale_y) + display_rect.y()
|
||||
w = int(bbox[2] / scale_x)
|
||||
h = int(bbox[3] / scale_y)
|
||||
|
||||
# Get color for this class
|
||||
color = self.class_colors.get(class_name, self.class_colors['default'])
|
||||
|
||||
# Draw bounding box
|
||||
if self.overlay_settings['show_boxes']:
|
||||
pen = QPen(color, 2)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(Qt.NoBrush)
|
||||
painter.drawRect(x, y, w, h)
|
||||
|
||||
# Draw labels
|
||||
labels = []
|
||||
|
||||
if self.overlay_settings['show_confidence']:
|
||||
labels.append(f"{class_name}: {confidence:.2f}")
|
||||
|
||||
if self.overlay_settings['show_tracks'] and track_id is not None:
|
||||
labels.append(f"ID: {track_id}")
|
||||
|
||||
if self.overlay_settings['show_speed'] and speed is not None:
|
||||
labels.append(f"{speed:.1f} km/h")
|
||||
|
||||
if labels:
|
||||
self._draw_label(painter, x, y, labels, color)
|
||||
|
||||
def _draw_label(self, painter, x, y, labels, color):
|
||||
"""Draw detection label with background"""
|
||||
text = " | ".join(labels)
|
||||
|
||||
# Set font
|
||||
font = QFont("Arial", 8, QFont.Bold)
|
||||
painter.setFont(font)
|
||||
|
||||
# Calculate text size
|
||||
fm = painter.fontMetrics()
|
||||
text_rect = fm.boundingRect(text)
|
||||
|
||||
# Draw background
|
||||
bg_rect = QRect(x, y - text_rect.height() - 4,
|
||||
text_rect.width() + 8, text_rect.height() + 4)
|
||||
|
||||
bg_color = QColor(color)
|
||||
bg_color.setAlpha(180)
|
||||
painter.fillRect(bg_rect, bg_color)
|
||||
|
||||
# Draw text
|
||||
painter.setPen(QColor(255, 255, 255))
|
||||
painter.drawText(x + 4, y - 4, text)
|
||||
|
||||
def update_fps(self, fps):
|
||||
"""Update FPS display"""
|
||||
self.fps_label.setText(f"{fps:.1f} fps")
|
||||
Reference in New Issue
Block a user