""" RGB Side View Panel Panel for displaying side-view RGB camera with shape analysis and outline detection. """ import os from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont, QPixmap, QImage, QPainter, QColor, QPen, QBrush from ui.widgets.panel_header import PanelHeader class QualityRGBSidePanel(QWidget): """ Panel for RGB side view camera display with shape analysis. Shows sample fruit profile with shape outline detection and analysis. """ def __init__(self, parent=None): super().__init__(parent) self.shape_analysis_active = True self.init_ui() def init_ui(self): """Initialize the panel UI.""" # Set size policy to expand equally self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Main panel container with card styling self.setStyleSheet(""" QWidget { background-color: white; border: 1px solid #ddd; } """) # Header using the PanelHeader widget header = PanelHeader( title="RGB Side View", color="#3498db" # Blue for RGB ) layout.addWidget(header) # Content area content = QWidget() content.setStyleSheet(""" background-color: #2c3e50; border: 1px solid #34495e; border-top: none; """) content_layout = QVBoxLayout(content) content_layout.setContentsMargins(10, 10, 10, 10) content_layout.setAlignment(Qt.AlignCenter) content_layout.setSpacing(5) # Create sample fruit side view with shape outline self.image_display = SampleFruitSideWidget() self.image_display.setMinimumSize(200, 150) content_layout.addWidget(self.image_display) # Info labels info_widget = QWidget() info_layout = QVBoxLayout(info_widget) info_layout.setContentsMargins(0, 5, 0, 0) info_layout.setSpacing(2) # Shape detection info shape_label = QLabel("Shape Detection Active") shape_label.setFont(QFont("Arial", 10)) shape_label.setStyleSheet("color: #27ae60; font-weight: bold;") shape_label.setAlignment(Qt.AlignCenter) info_layout.addWidget(shape_label) # Analysis info analysis_label = QLabel("Symmetry: 91.2% | Regular Shape") analysis_label.setFont(QFont("Arial", 9)) analysis_label.setStyleSheet("color: #bdc3c7;") analysis_label.setAlignment(Qt.AlignCenter) info_layout.addWidget(analysis_label) content_layout.addWidget(info_widget) layout.addWidget(content, 1) def update_shape_analysis(self, symmetry, shape_type): """Update shape analysis results.""" self.image_display.update_analysis(symmetry, shape_type) self.update() def set_image(self, image_path=None): """Set the image to display.""" if image_path and os.path.exists(image_path): try: pixmap = QPixmap(image_path) if not pixmap.isNull(): # Scale to fit display area scaled_pixmap = pixmap.scaled( 240, 170, Qt.KeepAspectRatio, Qt.SmoothTransformation ) # Update display self.image_display.update_with_image(scaled_pixmap) # Update status filename = os.path.basename(image_path) self.image_display.setToolTip(f"Loaded: {filename}") except Exception as e: print(f"Error loading image: {e}") else: # Show sample data self.image_display.update() class SampleFruitSideWidget(QWidget): """Widget showing sample fruit side view with shape outline.""" def __init__(self, parent=None): super().__init__(parent) self.symmetry = 91.2 self.shape_type = "Regular" self.setAttribute(Qt.WA_StyledBackground, True) def update_analysis(self, symmetry, shape_type): """Update shape analysis data.""" self.symmetry = symmetry self.shape_type = shape_type self.update() def update_with_image(self, pixmap): """Update display with external image.""" self.external_pixmap = pixmap self.has_external_image = True self.update() def paintEvent(self, event): """Custom paint event to draw fruit side view, shape outline, or external image.""" painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # Get widget dimensions width = self.width() height = self.height() # If we have an external image, display it if hasattr(self, 'external_pixmap') and self.external_pixmap: # Draw the external image scaled to fit scaled_pixmap = self.external_pixmap.scaled( width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation ) # Center the image x = (width - scaled_pixmap.width()) // 2 y = (height - scaled_pixmap.height()) // 2 painter.drawPixmap(x, y, scaled_pixmap) else: # Draw default fruit visualization self._draw_fruit_visualization(painter, width, height) def _draw_fruit_visualization(self, painter, width, height): """Draw the default fruit side view visualization.""" center_x = width // 2 center_y = height // 2 # Draw background painter.fillRect(0, 0, width, height, QColor("#2c3e50")) # Draw main fruit (elliptical shape for side view) fruit_width = min(width, height) // 2 fruit_height = fruit_width * 3 // 4 # Slightly flattened fruit_rect = ( center_x - fruit_width // 2, center_y - fruit_height // 2, fruit_width, fruit_height ) # Fruit body fruit_color = QColor("#8B4513") # Brown/orange fruit color painter.setBrush(fruit_color) painter.setPen(QPen(QColor("#654321"), 2)) # Darker border painter.drawEllipse(*fruit_rect) # Draw shape outline (dashed border for detected shape) painter.setBrush(Qt.NoBrush) painter.setPen(QPen(QColor("#27ae60"), 2, Qt.DashLine)) painter.drawEllipse(*fruit_rect) # Draw symmetry indicators (small lines showing symmetry axis) symmetry_y = center_y symmetry_start_x = center_x - fruit_width // 4 symmetry_end_x = center_x + fruit_width // 4 painter.setPen(QPen(QColor("#3498db"), 1, Qt.SolidLine)) painter.drawLine(symmetry_start_x, symmetry_y - 5, symmetry_start_x, symmetry_y + 5) painter.drawLine(symmetry_end_x, symmetry_y - 5, symmetry_end_x, symmetry_y + 5) # Draw aspect ratio visualization self._draw_aspect_ratio_info(painter, center_x, center_y, fruit_width, fruit_height) # Draw locule counting on side view self._draw_locule_side_view(painter, center_x, center_y, fruit_width, fruit_height) def _draw_aspect_ratio_info(self, painter, center_x, center_y, fruit_width, fruit_height): """Draw aspect ratio and shape information.""" # Calculate aspect ratio aspect_ratio = fruit_width / fruit_height if fruit_height > 0 else 1.0 # Draw info text info_x = center_x - 60 info_y = center_y + fruit_height // 2 + 20 painter.setPen(QPen(QColor("#ecf0f1"), 1)) # Shape type painter.drawText(info_x, info_y, f"Shape: {self.shape_type}") # Symmetry painter.drawText(info_x, info_y + 15, f"Symmetry: {self.symmetry:.1f}%") # Aspect ratio painter.drawText(info_x, info_y + 30, f"Aspect Ratio: {aspect_ratio:.2f}") def _draw_locule_side_view(self, painter, center_x, center_y, fruit_width, fruit_height): """Draw locule counting visualization on side view.""" # Draw internal locule structure (cross-section view) locule_radius = min(fruit_width, fruit_height) // 8 # Draw 4 locules in a circular pattern within the fruit for i in range(4): angle = (i * 360 / 4) * (3.14159 / 180) # Convert to radians locule_x = center_x + int((fruit_width * 0.3) * (1 if i % 2 == 0 else -1) * (1 if i < 2 else 0.7)) locule_y = center_y + int((fruit_height * 0.3) * (1 if i % 2 == 1 else -1) * (1 if i < 2 else 0.7)) # Draw locule as colored circle locule_colors = ["#3498db", "#e74c3c", "#2ecc71", "#f39c12"] painter.setBrush(QBrush(QColor(locule_colors[i]))) painter.setPen(QPen(QColor("#34495e"), 1)) painter.drawEllipse(locule_x - locule_radius, locule_y - locule_radius, locule_radius * 2, locule_radius * 2) def update(self): """Override update to ensure repaint.""" super().update() self.repaint()