quality_rgb_side_panel.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. """
  2. RGB Side View Panel
  3. Panel for displaying side-view RGB camera with shape analysis and outline detection.
  4. """
  5. import os
  6. from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy
  7. from PyQt5.QtCore import Qt
  8. from PyQt5.QtGui import QFont, QPixmap, QImage, QPainter, QColor, QPen, QBrush
  9. from ui.widgets.panel_header import PanelHeader
  10. class QualityRGBSidePanel(QWidget):
  11. """
  12. Panel for RGB side view camera display with shape analysis.
  13. Shows sample fruit profile with shape outline detection and analysis.
  14. """
  15. def __init__(self, parent=None):
  16. super().__init__(parent)
  17. self.shape_analysis_active = True
  18. self.init_ui()
  19. def init_ui(self):
  20. """Initialize the panel UI."""
  21. # Set size policy to expand equally
  22. self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
  23. layout = QVBoxLayout(self)
  24. layout.setContentsMargins(0, 0, 0, 0)
  25. layout.setSpacing(0)
  26. # Main panel container with card styling
  27. self.setStyleSheet("""
  28. QWidget {
  29. background-color: white;
  30. border: 1px solid #ddd;
  31. }
  32. """)
  33. # Header using the PanelHeader widget
  34. header = PanelHeader(
  35. title="RGB Side View",
  36. color="#3498db" # Blue for RGB
  37. )
  38. layout.addWidget(header)
  39. # Content area
  40. content = QWidget()
  41. content.setStyleSheet("""
  42. background-color: #2c3e50;
  43. border: 1px solid #34495e;
  44. border-top: none;
  45. """)
  46. content_layout = QVBoxLayout(content)
  47. content_layout.setContentsMargins(10, 10, 10, 10)
  48. content_layout.setAlignment(Qt.AlignCenter)
  49. content_layout.setSpacing(5)
  50. # Create sample fruit side view with shape outline
  51. self.image_display = SampleFruitSideWidget()
  52. self.image_display.setMinimumSize(200, 150)
  53. content_layout.addWidget(self.image_display)
  54. # Info labels
  55. info_widget = QWidget()
  56. info_layout = QVBoxLayout(info_widget)
  57. info_layout.setContentsMargins(0, 5, 0, 0)
  58. info_layout.setSpacing(2)
  59. # Shape detection info
  60. shape_label = QLabel("Shape Detection Active")
  61. shape_label.setFont(QFont("Arial", 10))
  62. shape_label.setStyleSheet("color: #27ae60; font-weight: bold;")
  63. shape_label.setAlignment(Qt.AlignCenter)
  64. info_layout.addWidget(shape_label)
  65. # Analysis info
  66. analysis_label = QLabel("Symmetry: 91.2% | Regular Shape")
  67. analysis_label.setFont(QFont("Arial", 9))
  68. analysis_label.setStyleSheet("color: #bdc3c7;")
  69. analysis_label.setAlignment(Qt.AlignCenter)
  70. info_layout.addWidget(analysis_label)
  71. content_layout.addWidget(info_widget)
  72. layout.addWidget(content, 1)
  73. def update_shape_analysis(self, symmetry, shape_type):
  74. """Update shape analysis results."""
  75. self.image_display.update_analysis(symmetry, shape_type)
  76. self.update()
  77. def set_image(self, image_path=None):
  78. """Set the image to display."""
  79. if image_path and os.path.exists(image_path):
  80. try:
  81. pixmap = QPixmap(image_path)
  82. if not pixmap.isNull():
  83. # Scale to fit display area
  84. scaled_pixmap = pixmap.scaled(
  85. 240, 170, Qt.KeepAspectRatio, Qt.SmoothTransformation
  86. )
  87. # Update display
  88. self.image_display.update_with_image(scaled_pixmap)
  89. # Update status
  90. filename = os.path.basename(image_path)
  91. self.image_display.setToolTip(f"Loaded: {filename}")
  92. except Exception as e:
  93. print(f"Error loading image: {e}")
  94. else:
  95. # Show sample data
  96. self.image_display.update()
  97. class SampleFruitSideWidget(QWidget):
  98. """Widget showing sample fruit side view with shape outline."""
  99. def __init__(self, parent=None):
  100. super().__init__(parent)
  101. self.symmetry = 91.2
  102. self.shape_type = "Regular"
  103. self.setAttribute(Qt.WA_StyledBackground, True)
  104. def update_analysis(self, symmetry, shape_type):
  105. """Update shape analysis data."""
  106. self.symmetry = symmetry
  107. self.shape_type = shape_type
  108. self.update()
  109. def update_with_image(self, pixmap):
  110. """Update display with external image."""
  111. self.external_pixmap = pixmap
  112. self.has_external_image = True
  113. self.update()
  114. def paintEvent(self, event):
  115. """Custom paint event to draw fruit side view, shape outline, or external image."""
  116. painter = QPainter(self)
  117. painter.setRenderHint(QPainter.Antialiasing)
  118. # Get widget dimensions
  119. width = self.width()
  120. height = self.height()
  121. # If we have an external image, display it
  122. if hasattr(self, 'external_pixmap') and self.external_pixmap:
  123. # Draw the external image scaled to fit
  124. scaled_pixmap = self.external_pixmap.scaled(
  125. width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation
  126. )
  127. # Center the image
  128. x = (width - scaled_pixmap.width()) // 2
  129. y = (height - scaled_pixmap.height()) // 2
  130. painter.drawPixmap(x, y, scaled_pixmap)
  131. else:
  132. # Draw default fruit visualization
  133. self._draw_fruit_visualization(painter, width, height)
  134. def _draw_fruit_visualization(self, painter, width, height):
  135. """Draw the default fruit side view visualization."""
  136. center_x = width // 2
  137. center_y = height // 2
  138. # Draw background
  139. painter.fillRect(0, 0, width, height, QColor("#2c3e50"))
  140. # Draw main fruit (elliptical shape for side view)
  141. fruit_width = min(width, height) // 2
  142. fruit_height = fruit_width * 3 // 4 # Slightly flattened
  143. fruit_rect = (
  144. center_x - fruit_width // 2,
  145. center_y - fruit_height // 2,
  146. fruit_width,
  147. fruit_height
  148. )
  149. # Fruit body
  150. fruit_color = QColor("#8B4513") # Brown/orange fruit color
  151. painter.setBrush(fruit_color)
  152. painter.setPen(QPen(QColor("#654321"), 2)) # Darker border
  153. painter.drawEllipse(*fruit_rect)
  154. # Draw shape outline (dashed border for detected shape)
  155. painter.setBrush(Qt.NoBrush)
  156. painter.setPen(QPen(QColor("#27ae60"), 2, Qt.DashLine))
  157. painter.drawEllipse(*fruit_rect)
  158. # Draw symmetry indicators (small lines showing symmetry axis)
  159. symmetry_y = center_y
  160. symmetry_start_x = center_x - fruit_width // 4
  161. symmetry_end_x = center_x + fruit_width // 4
  162. painter.setPen(QPen(QColor("#3498db"), 1, Qt.SolidLine))
  163. painter.drawLine(symmetry_start_x, symmetry_y - 5, symmetry_start_x, symmetry_y + 5)
  164. painter.drawLine(symmetry_end_x, symmetry_y - 5, symmetry_end_x, symmetry_y + 5)
  165. # Draw aspect ratio visualization
  166. self._draw_aspect_ratio_info(painter, center_x, center_y, fruit_width, fruit_height)
  167. # Draw locule counting on side view
  168. self._draw_locule_side_view(painter, center_x, center_y, fruit_width, fruit_height)
  169. def _draw_aspect_ratio_info(self, painter, center_x, center_y, fruit_width, fruit_height):
  170. """Draw aspect ratio and shape information."""
  171. # Calculate aspect ratio
  172. aspect_ratio = fruit_width / fruit_height if fruit_height > 0 else 1.0
  173. # Draw info text
  174. info_x = center_x - 60
  175. info_y = center_y + fruit_height // 2 + 20
  176. painter.setPen(QPen(QColor("#ecf0f1"), 1))
  177. # Shape type
  178. painter.drawText(info_x, info_y, f"Shape: {self.shape_type}")
  179. # Symmetry
  180. painter.drawText(info_x, info_y + 15, f"Symmetry: {self.symmetry:.1f}%")
  181. # Aspect ratio
  182. painter.drawText(info_x, info_y + 30, f"Aspect Ratio: {aspect_ratio:.2f}")
  183. def _draw_locule_side_view(self, painter, center_x, center_y, fruit_width, fruit_height):
  184. """Draw locule counting visualization on side view."""
  185. # Draw internal locule structure (cross-section view)
  186. locule_radius = min(fruit_width, fruit_height) // 8
  187. # Draw 4 locules in a circular pattern within the fruit
  188. for i in range(4):
  189. angle = (i * 360 / 4) * (3.14159 / 180) # Convert to radians
  190. locule_x = center_x + int((fruit_width * 0.3) * (1 if i % 2 == 0 else -1) * (1 if i < 2 else 0.7))
  191. locule_y = center_y + int((fruit_height * 0.3) * (1 if i % 2 == 1 else -1) * (1 if i < 2 else 0.7))
  192. # Draw locule as colored circle
  193. locule_colors = ["#3498db", "#e74c3c", "#2ecc71", "#f39c12"]
  194. painter.setBrush(QBrush(QColor(locule_colors[i])))
  195. painter.setPen(QPen(QColor("#34495e"), 1))
  196. painter.drawEllipse(locule_x - locule_radius, locule_y - locule_radius,
  197. locule_radius * 2, locule_radius * 2)
  198. def update(self):
  199. """Override update to ensure repaint."""
  200. super().update()
  201. self.repaint()