quality_defects_panel.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. """
  2. Defect Detection Results Panel
  3. Panel for displaying comprehensive defect detection results with analysis.
  4. """
  5. from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
  6. QSizePolicy, QScrollArea, QFrame)
  7. from PyQt5.QtCore import Qt, pyqtSignal
  8. from PyQt5.QtGui import QFont, QPixmap, QImage, QPainter, QColor, QPen, QBrush, QCursor
  9. from ui.widgets.panel_header import PanelHeader
  10. class QualityDefectsPanel(QWidget):
  11. """
  12. Panel for displaying defect detection results and analysis.
  13. Shows detected defects with confidence levels and categorization.
  14. """
  15. # Signals
  16. annotated_image_requested = pyqtSignal() # Emitted when user clicks on locule count
  17. defect_image_requested = pyqtSignal() # Emitted when user clicks on defect analysis
  18. def __init__(self, parent=None):
  19. super().__init__(parent)
  20. self.defects_data = []
  21. self.annotated_image_path = None
  22. self.current_annotated_pixmap = None
  23. self.has_results = False
  24. self.has_defect_results = False
  25. self.init_ui()
  26. def init_ui(self):
  27. """Initialize the panel UI."""
  28. # Set size policy to expand equally
  29. self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
  30. layout = QVBoxLayout(self)
  31. layout.setContentsMargins(0, 0, 0, 0)
  32. layout.setSpacing(0)
  33. # Main panel container with card styling
  34. self.setStyleSheet("""
  35. QWidget {
  36. background-color: white;
  37. border: 1px solid #ddd;
  38. }
  39. """)
  40. # Header using the PanelHeader widget
  41. header = PanelHeader(
  42. title="Defect Detection Results",
  43. color="#e74c3c" # Red for defects
  44. )
  45. layout.addWidget(header)
  46. # Content area
  47. content = QWidget()
  48. content.setStyleSheet("""
  49. background-color: #2c3e50;
  50. border: 1px solid #34495e;
  51. border-top: none;
  52. """)
  53. content_layout = QVBoxLayout(content)
  54. content_layout.setContentsMargins(10, 10, 10, 10)
  55. content_layout.setSpacing(10)
  56. # Scroll area for defects list
  57. scroll_area = QScrollArea()
  58. scroll_area.setWidgetResizable(True)
  59. scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  60. scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
  61. scroll_area.setStyleSheet("""
  62. QScrollArea {
  63. border: none;
  64. background-color: transparent;
  65. }
  66. QScrollBar:vertical {
  67. background-color: #34495e;
  68. width: 8px;
  69. border-radius: 4px;
  70. }
  71. QScrollBar::handle:vertical {
  72. background-color: #95a5a6;
  73. border-radius: 4px;
  74. }
  75. """)
  76. # Create main content layout
  77. main_content_layout = QVBoxLayout()
  78. # Add clickable locule count widget (initially shows placeholder)
  79. self.locule_widget = self._create_locule_count_widget()
  80. main_content_layout.addWidget(self.locule_widget)
  81. # Add clickable defect analysis widget
  82. self.defect_widget = self._create_defect_analysis_widget()
  83. main_content_layout.addWidget(self.defect_widget)
  84. # Set initial placeholder styling
  85. self._update_locule_widget_style(False)
  86. # Container for other defects
  87. self.defects_container = QWidget()
  88. self.defects_layout = QVBoxLayout(self.defects_container)
  89. self.defects_layout.setContentsMargins(0, 0, 0, 0)
  90. self.defects_layout.setSpacing(8)
  91. # Add sample defects for demonstration
  92. self._add_sample_defects()
  93. scroll_area.setWidget(self.defects_container)
  94. main_content_layout.addWidget(scroll_area)
  95. content_layout.addLayout(main_content_layout, 1)
  96. layout.addWidget(content, 1)
  97. def _create_locule_count_widget(self):
  98. """Create the clickable locule count widget."""
  99. widget = QWidget()
  100. widget.setFixedHeight(80)
  101. # Make it clickable only when we have results
  102. widget.mousePressEvent = self._on_locule_widget_clicked
  103. layout = QHBoxLayout(widget)
  104. layout.setContentsMargins(15, 10, 15, 10)
  105. # Left side: Icon/Text
  106. left_widget = QWidget()
  107. left_layout = QVBoxLayout(left_widget)
  108. left_layout.setContentsMargins(0, 0, 0, 0)
  109. left_layout.setSpacing(2)
  110. self.title_label = QLabel("📁 Open File to Start")
  111. self.title_label.setFont(QFont("Arial", 10, QFont.Bold))
  112. self.title_label.setStyleSheet("color: #6c757d;")
  113. self.subtitle_label = QLabel("Select an image for locule analysis")
  114. self.subtitle_label.setFont(QFont("Arial", 8))
  115. self.subtitle_label.setStyleSheet("color: #6c757d;")
  116. left_layout.addWidget(self.title_label)
  117. left_layout.addWidget(self.subtitle_label)
  118. # Right side: Count display
  119. right_widget = QWidget()
  120. right_layout = QVBoxLayout(right_widget)
  121. right_layout.setContentsMargins(0, 0, 0, 0)
  122. right_layout.setAlignment(Qt.AlignCenter)
  123. self.locule_number_label = QLabel("--")
  124. self.locule_number_label.setFont(QFont("Arial", 20, QFont.Bold))
  125. self.locule_number_label.setStyleSheet("color: #6c757d;")
  126. # self.confidence_label = QLabel("Ready for analysis")
  127. # self.confidence_label.setFont(QFont("Arial", 9))
  128. # self.confidence_label.setStyleSheet("color: #6c757d;")
  129. right_layout.addWidget(self.locule_number_label)
  130. # right_layout.addWidget(self.confidence_label)
  131. # Add tooltip
  132. widget.setToolTip("Open a file to begin locule analysis")
  133. layout.addWidget(left_widget, 1)
  134. layout.addWidget(right_widget, 0)
  135. return widget
  136. def _create_defect_analysis_widget(self):
  137. """Create the clickable defect analysis widget."""
  138. widget = QWidget()
  139. widget.setFixedHeight(80)
  140. # Make it clickable only when we have results
  141. widget.mousePressEvent = self._on_defect_widget_clicked
  142. layout = QHBoxLayout(widget)
  143. layout.setContentsMargins(15, 10, 15, 10)
  144. # Left side: Icon/Text
  145. left_widget = QWidget()
  146. left_layout = QVBoxLayout(left_widget)
  147. left_layout.setContentsMargins(0, 0, 0, 0)
  148. left_layout.setSpacing(2)
  149. self.defect_title_label = QLabel("🔍 Defect Detection")
  150. self.defect_title_label.setFont(QFont("Arial", 10, QFont.Bold))
  151. self.defect_title_label.setStyleSheet("color: #6c757d; font-weight: bold;")
  152. self.defect_subtitle_label = QLabel("Open file for defect analysis")
  153. self.defect_subtitle_label.setFont(QFont("Arial", 8))
  154. self.defect_subtitle_label.setStyleSheet("color: #6c757d;")
  155. left_layout.addWidget(self.defect_title_label)
  156. left_layout.addWidget(self.defect_subtitle_label)
  157. # Right side: Defect count display
  158. right_widget = QWidget()
  159. right_layout = QVBoxLayout(right_widget)
  160. right_layout.setContentsMargins(0, 0, 0, 0)
  161. right_layout.setAlignment(Qt.AlignCenter)
  162. self.defect_count_label = QLabel("--")
  163. self.defect_count_label.setFont(QFont("Arial", 20, QFont.Bold))
  164. self.defect_count_label.setStyleSheet("color: #6c757d;")
  165. self.defect_status_label = QLabel("Ready")
  166. self.defect_status_label.setFont(QFont("Arial", 9))
  167. self.defect_status_label.setStyleSheet("color: #6c757d;")
  168. right_layout.addWidget(self.defect_count_label)
  169. right_layout.addWidget(self.defect_status_label)
  170. # Add tooltip
  171. widget.setToolTip("Open a file to begin defect analysis")
  172. layout.addWidget(left_widget, 1)
  173. layout.addWidget(right_widget, 0)
  174. # Set initial styling (placeholder state)
  175. self._update_defect_widget_style(widget, False)
  176. return widget
  177. def _update_defect_widget_style(self, widget, has_results: bool):
  178. """Update the defect widget styling based on state."""
  179. if has_results:
  180. # Results state - clickable with hover effects
  181. self.defect_title_label.setText("🔍 Defect Detection Result")
  182. self.defect_title_label.setStyleSheet("color: #721c24; font-weight: bold;")
  183. self.defect_subtitle_label.setText("Click to view defect analysis")
  184. self.defect_subtitle_label.setStyleSheet("color: #c82333;")
  185. widget.setStyleSheet("""
  186. QWidget {
  187. background-color: #f8d7da;
  188. border: 2px solid #e74c3c;
  189. border-radius: 8px;
  190. margin: 5px;
  191. }
  192. QWidget:hover {
  193. background-color: #f5c6cb;
  194. border-color: #c82333;
  195. }
  196. """)
  197. widget.setCursor(QCursor(Qt.PointingHandCursor))
  198. widget.setToolTip("Click to view the annotated image with defect detection")
  199. else:
  200. # Placeholder state - not clickable
  201. self.defect_title_label.setText("🔍 Defect Detection")
  202. self.defect_title_label.setStyleSheet("color: #6c757d; font-weight: bold;")
  203. self.defect_subtitle_label.setText("Open file for defect analysis")
  204. self.defect_subtitle_label.setStyleSheet("color: #6c757d;")
  205. widget.setStyleSheet("""
  206. QWidget {
  207. background-color: #f8f9fa;
  208. border: 2px solid #dee2e6;
  209. border-radius: 8px;
  210. margin: 5px;
  211. }
  212. """)
  213. widget.setCursor(QCursor(Qt.ArrowCursor))
  214. widget.setToolTip("Open a file to begin defect analysis")
  215. def _on_defect_widget_clicked(self, event):
  216. """Handle click on defect widget."""
  217. if hasattr(self, 'has_defect_results') and self.has_defect_results:
  218. self.defect_image_requested.emit()
  219. def _update_locule_widget_style(self, has_results: bool):
  220. """Update the locule widget styling based on state."""
  221. self.has_results = has_results
  222. if has_results:
  223. # Results state - clickable with hover effects
  224. self.title_label.setText("📊 Locule Analysis Result")
  225. self.title_label.setStyleSheet("color: #155724; font-weight: bold;")
  226. self.subtitle_label.setText("Click to view annotated image")
  227. self.subtitle_label.setStyleSheet("color: #218838;")
  228. self.locule_widget.setStyleSheet("""
  229. QWidget {
  230. background-color: #d4edda;
  231. border: 2px solid #27ae60;
  232. border-radius: 8px;
  233. margin: 5px;
  234. }
  235. QWidget:hover {
  236. background-color: #c3e6cb;
  237. border-color: #218838;
  238. }
  239. """)
  240. self.locule_widget.setCursor(QCursor(Qt.PointingHandCursor))
  241. self.locule_widget.setToolTip("Click to view the annotated image with locule segmentation")
  242. else:
  243. # Placeholder state - not clickable
  244. self.title_label.setText("📁 Open File to Start")
  245. self.title_label.setStyleSheet("color: #6c757d; font-weight: bold;")
  246. self.subtitle_label.setText("Select an image for locule analysis")
  247. self.subtitle_label.setStyleSheet("color: #6c757d;")
  248. self.locule_widget.setStyleSheet("""
  249. QWidget {
  250. background-color: #f8f9fa;
  251. border: 2px solid #dee2e6;
  252. border-radius: 8px;
  253. margin: 5px;
  254. }
  255. """)
  256. self.locule_widget.setCursor(QCursor(Qt.ArrowCursor))
  257. self.locule_widget.setToolTip("Open a file to begin locule analysis")
  258. def _on_locule_widget_clicked(self, event):
  259. """Handle click on locule widget."""
  260. if self.has_results:
  261. self.annotated_image_requested.emit()
  262. def _add_sample_defects(self):
  263. """Add sample defects for demonstration."""
  264. sample_defects = [
  265. {
  266. 'type': 'Mechanical Damage',
  267. 'location': 'Top-Left',
  268. 'size': '8.2mm²',
  269. 'confidence': 87.3,
  270. 'color': '#f39c12',
  271. 'category': 'warning'
  272. },
  273. {
  274. 'type': 'Surface Blemish',
  275. 'location': 'Side',
  276. 'size': '4.1mm²',
  277. 'confidence': 72.8,
  278. 'color': '#f39c12',
  279. 'category': 'warning'
  280. },
  281. {
  282. 'type': 'Shape Analysis',
  283. 'result': 'Regular',
  284. 'symmetry': '91.2%',
  285. 'confidence': 94.1,
  286. 'color': '#27ae60',
  287. 'category': 'success'
  288. }
  289. ]
  290. for defect in sample_defects:
  291. defect_widget = self._create_defect_item(defect)
  292. self.defects_layout.addWidget(defect_widget)
  293. # Add stretch to push everything to the top
  294. self.defects_layout.addStretch()
  295. def update_locule_count(self, count: int, confidence: float = 94.5):
  296. """Update the locule count display with actual AI results."""
  297. # Update the count display
  298. self.locule_number_label.setText(str(count))
  299. # Update the styling to show results state
  300. self._update_locule_widget_style(True)
  301. def update_defect_count(self, count: int, primary_class: str = "minor defects"):
  302. """Update the defect count display with actual AI results."""
  303. # Update the count display
  304. self.defect_count_label.setText(str(count))
  305. # Update the styling to show results state
  306. self._update_defect_widget_style(self.defect_widget, True)
  307. # Update status text
  308. self.defect_status_label.setText(f"Primary: {primary_class}")
  309. self.defect_status_label.setStyleSheet("color: #c82333; font-weight: bold;")
  310. # Set the flag to indicate we have defect results
  311. self.has_defect_results = True
  312. def _create_defect_item(self, defect_data):
  313. """Create a widget for a single defect item."""
  314. widget = QWidget()
  315. widget.setFixedHeight(60)
  316. widget.setStyleSheet("""
  317. QWidget {
  318. background-color: #34495e;
  319. border-radius: 5px;
  320. border: 1px solid #4a5f7a;
  321. }
  322. """)
  323. layout = QHBoxLayout(widget)
  324. layout.setContentsMargins(10, 8, 10, 8)
  325. layout.setSpacing(10)
  326. # Status indicator (colored circle)
  327. indicator = QLabel()
  328. indicator.setFixedSize(12, 12)
  329. indicator.setStyleSheet(f"""
  330. QLabel {{
  331. background-color: {defect_data['color']};
  332. border-radius: 6px;
  333. border: 1px solid {defect_data.get('border_color', '#fff')};
  334. }}
  335. """)
  336. # Content area
  337. content_widget = QWidget()
  338. content_layout = QVBoxLayout(content_widget)
  339. content_layout.setContentsMargins(0, 0, 0, 0)
  340. content_layout.setSpacing(2)
  341. # Main info
  342. if defect_data['type'] == 'Shape Analysis':
  343. main_text = f"Shape: {defect_data.get('result', 'Unknown')} | Symmetry: {defect_data.get('symmetry', 'N/A')}"
  344. elif defect_data['type'] == 'Locule Count':
  345. main_text = f"Detected Locules: {defect_data.get('count', '0')} | Confidence: {defect_data['confidence']:.1f}%"
  346. else:
  347. main_text = f"{defect_data['type']} | Location: {defect_data['location']} | Size: {defect_data['size']}"
  348. main_label = QLabel(main_text)
  349. main_label.setFont(QFont("Arial", 9, QFont.Bold))
  350. main_label.setStyleSheet("color: #ecf0f1;")
  351. content_layout.addWidget(main_label)
  352. # Secondary info (confidence or additional details)
  353. if 'confidence' in defect_data:
  354. confidence_text = f"Confidence: {defect_data['confidence']:.1f}%"
  355. if defect_data['type'] != 'Locule Count' and 'size' in defect_data: # Already shown above
  356. confidence_text += f" | Size: {defect_data['size']}"
  357. secondary_label = QLabel(confidence_text)
  358. secondary_label.setFont(QFont("Arial", 8))
  359. secondary_label.setStyleSheet("color: #bdc3c7;")
  360. content_layout.addWidget(secondary_label)
  361. # Confidence value (right-aligned)
  362. confidence_label = QLabel(f"{defect_data['confidence']:.1f}%")
  363. confidence_label.setFont(QFont("Arial", 10, QFont.Bold))
  364. confidence_label.setStyleSheet(f"color: {defect_data['color']};")
  365. confidence_label.setFixedWidth(50)
  366. confidence_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
  367. # Add widgets to layout
  368. layout.addWidget(indicator)
  369. layout.addWidget(content_widget, 1)
  370. layout.addWidget(confidence_label)
  371. return widget
  372. def update_defects(self, defects_list):
  373. """Update the defects list with new data."""
  374. # Clear existing defects
  375. for i in reversed(range(self.defects_layout.count())):
  376. child = self.defects_layout.itemAt(i).widget()
  377. if child is not None:
  378. child.setParent(None)
  379. # Add new defects
  380. for defect in defects_list:
  381. defect_widget = self._create_defect_item(defect)
  382. self.defects_layout.addWidget(defect_widget)
  383. # Add stretch to push everything to the top
  384. self.defects_layout.addStretch()
  385. self.update()
  386. def clear_defects(self):
  387. """Clear all defects."""
  388. self.update_defects([])