""" Defect Detection Results Panel Panel for displaying comprehensive defect detection results with analysis. """ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QScrollArea, QFrame) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QFont, QPixmap, QImage, QPainter, QColor, QPen, QBrush, QCursor from ui.widgets.panel_header import PanelHeader class QualityDefectsPanel(QWidget): """ Panel for displaying defect detection results and analysis. Shows detected defects with confidence levels and categorization. """ # Signals annotated_image_requested = pyqtSignal() # Emitted when user clicks on locule count defect_image_requested = pyqtSignal() # Emitted when user clicks on defect analysis def __init__(self, parent=None): super().__init__(parent) self.defects_data = [] self.annotated_image_path = None self.current_annotated_pixmap = None self.has_results = False self.has_defect_results = False 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="Defect Detection Results", color="#e74c3c" # Red for defects ) 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.setSpacing(10) # Scroll area for defects list scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll_area.setStyleSheet(""" QScrollArea { border: none; background-color: transparent; } QScrollBar:vertical { background-color: #34495e; width: 8px; border-radius: 4px; } QScrollBar::handle:vertical { background-color: #95a5a6; border-radius: 4px; } """) # Create main content layout main_content_layout = QVBoxLayout() # Add clickable locule count widget (initially shows placeholder) self.locule_widget = self._create_locule_count_widget() main_content_layout.addWidget(self.locule_widget) # Add clickable defect analysis widget self.defect_widget = self._create_defect_analysis_widget() main_content_layout.addWidget(self.defect_widget) # Set initial placeholder styling self._update_locule_widget_style(False) # Container for other defects self.defects_container = QWidget() self.defects_layout = QVBoxLayout(self.defects_container) self.defects_layout.setContentsMargins(0, 0, 0, 0) self.defects_layout.setSpacing(8) # Add sample defects for demonstration self._add_sample_defects() scroll_area.setWidget(self.defects_container) main_content_layout.addWidget(scroll_area) content_layout.addLayout(main_content_layout, 1) layout.addWidget(content, 1) def _create_locule_count_widget(self): """Create the clickable locule count widget.""" widget = QWidget() widget.setFixedHeight(80) # Make it clickable only when we have results widget.mousePressEvent = self._on_locule_widget_clicked layout = QHBoxLayout(widget) layout.setContentsMargins(15, 10, 15, 10) # Left side: Icon/Text left_widget = QWidget() left_layout = QVBoxLayout(left_widget) left_layout.setContentsMargins(0, 0, 0, 0) left_layout.setSpacing(2) self.title_label = QLabel("📁 Open File to Start") self.title_label.setFont(QFont("Arial", 10, QFont.Bold)) self.title_label.setStyleSheet("color: #6c757d;") self.subtitle_label = QLabel("Select an image for locule analysis") self.subtitle_label.setFont(QFont("Arial", 8)) self.subtitle_label.setStyleSheet("color: #6c757d;") left_layout.addWidget(self.title_label) left_layout.addWidget(self.subtitle_label) # Right side: Count display right_widget = QWidget() right_layout = QVBoxLayout(right_widget) right_layout.setContentsMargins(0, 0, 0, 0) right_layout.setAlignment(Qt.AlignCenter) self.locule_number_label = QLabel("--") self.locule_number_label.setFont(QFont("Arial", 20, QFont.Bold)) self.locule_number_label.setStyleSheet("color: #6c757d;") # self.confidence_label = QLabel("Ready for analysis") # self.confidence_label.setFont(QFont("Arial", 9)) # self.confidence_label.setStyleSheet("color: #6c757d;") right_layout.addWidget(self.locule_number_label) # right_layout.addWidget(self.confidence_label) # Add tooltip widget.setToolTip("Open a file to begin locule analysis") layout.addWidget(left_widget, 1) layout.addWidget(right_widget, 0) return widget def _create_defect_analysis_widget(self): """Create the clickable defect analysis widget.""" widget = QWidget() widget.setFixedHeight(80) # Make it clickable only when we have results widget.mousePressEvent = self._on_defect_widget_clicked layout = QHBoxLayout(widget) layout.setContentsMargins(15, 10, 15, 10) # Left side: Icon/Text left_widget = QWidget() left_layout = QVBoxLayout(left_widget) left_layout.setContentsMargins(0, 0, 0, 0) left_layout.setSpacing(2) self.defect_title_label = QLabel("🔍 Defect Detection") self.defect_title_label.setFont(QFont("Arial", 10, QFont.Bold)) self.defect_title_label.setStyleSheet("color: #6c757d; font-weight: bold;") self.defect_subtitle_label = QLabel("Open file for defect analysis") self.defect_subtitle_label.setFont(QFont("Arial", 8)) self.defect_subtitle_label.setStyleSheet("color: #6c757d;") left_layout.addWidget(self.defect_title_label) left_layout.addWidget(self.defect_subtitle_label) # Right side: Defect count display right_widget = QWidget() right_layout = QVBoxLayout(right_widget) right_layout.setContentsMargins(0, 0, 0, 0) right_layout.setAlignment(Qt.AlignCenter) self.defect_count_label = QLabel("--") self.defect_count_label.setFont(QFont("Arial", 20, QFont.Bold)) self.defect_count_label.setStyleSheet("color: #6c757d;") self.defect_status_label = QLabel("Ready") self.defect_status_label.setFont(QFont("Arial", 9)) self.defect_status_label.setStyleSheet("color: #6c757d;") right_layout.addWidget(self.defect_count_label) right_layout.addWidget(self.defect_status_label) # Add tooltip widget.setToolTip("Open a file to begin defect analysis") layout.addWidget(left_widget, 1) layout.addWidget(right_widget, 0) # Set initial styling (placeholder state) self._update_defect_widget_style(widget, False) return widget def _update_defect_widget_style(self, widget, has_results: bool): """Update the defect widget styling based on state.""" if has_results: # Results state - clickable with hover effects self.defect_title_label.setText("🔍 Defect Detection Result") self.defect_title_label.setStyleSheet("color: #721c24; font-weight: bold;") self.defect_subtitle_label.setText("Click to view defect analysis") self.defect_subtitle_label.setStyleSheet("color: #c82333;") widget.setStyleSheet(""" QWidget { background-color: #f8d7da; border: 2px solid #e74c3c; border-radius: 8px; margin: 5px; } QWidget:hover { background-color: #f5c6cb; border-color: #c82333; } """) widget.setCursor(QCursor(Qt.PointingHandCursor)) widget.setToolTip("Click to view the annotated image with defect detection") else: # Placeholder state - not clickable self.defect_title_label.setText("🔍 Defect Detection") self.defect_title_label.setStyleSheet("color: #6c757d; font-weight: bold;") self.defect_subtitle_label.setText("Open file for defect analysis") self.defect_subtitle_label.setStyleSheet("color: #6c757d;") widget.setStyleSheet(""" QWidget { background-color: #f8f9fa; border: 2px solid #dee2e6; border-radius: 8px; margin: 5px; } """) widget.setCursor(QCursor(Qt.ArrowCursor)) widget.setToolTip("Open a file to begin defect analysis") def _on_defect_widget_clicked(self, event): """Handle click on defect widget.""" if hasattr(self, 'has_defect_results') and self.has_defect_results: self.defect_image_requested.emit() def _update_locule_widget_style(self, has_results: bool): """Update the locule widget styling based on state.""" self.has_results = has_results if has_results: # Results state - clickable with hover effects self.title_label.setText("📊 Locule Analysis Result") self.title_label.setStyleSheet("color: #155724; font-weight: bold;") self.subtitle_label.setText("Click to view annotated image") self.subtitle_label.setStyleSheet("color: #218838;") self.locule_widget.setStyleSheet(""" QWidget { background-color: #d4edda; border: 2px solid #27ae60; border-radius: 8px; margin: 5px; } QWidget:hover { background-color: #c3e6cb; border-color: #218838; } """) self.locule_widget.setCursor(QCursor(Qt.PointingHandCursor)) self.locule_widget.setToolTip("Click to view the annotated image with locule segmentation") else: # Placeholder state - not clickable self.title_label.setText("📁 Open File to Start") self.title_label.setStyleSheet("color: #6c757d; font-weight: bold;") self.subtitle_label.setText("Select an image for locule analysis") self.subtitle_label.setStyleSheet("color: #6c757d;") self.locule_widget.setStyleSheet(""" QWidget { background-color: #f8f9fa; border: 2px solid #dee2e6; border-radius: 8px; margin: 5px; } """) self.locule_widget.setCursor(QCursor(Qt.ArrowCursor)) self.locule_widget.setToolTip("Open a file to begin locule analysis") def _on_locule_widget_clicked(self, event): """Handle click on locule widget.""" if self.has_results: self.annotated_image_requested.emit() def _add_sample_defects(self): """Add sample defects for demonstration.""" sample_defects = [ { 'type': 'Mechanical Damage', 'location': 'Top-Left', 'size': '8.2mm²', 'confidence': 87.3, 'color': '#f39c12', 'category': 'warning' }, { 'type': 'Surface Blemish', 'location': 'Side', 'size': '4.1mm²', 'confidence': 72.8, 'color': '#f39c12', 'category': 'warning' }, { 'type': 'Shape Analysis', 'result': 'Regular', 'symmetry': '91.2%', 'confidence': 94.1, 'color': '#27ae60', 'category': 'success' } ] for defect in sample_defects: defect_widget = self._create_defect_item(defect) self.defects_layout.addWidget(defect_widget) # Add stretch to push everything to the top self.defects_layout.addStretch() def update_locule_count(self, count: int, confidence: float = 94.5): """Update the locule count display with actual AI results.""" # Update the count display self.locule_number_label.setText(str(count)) # Update the styling to show results state self._update_locule_widget_style(True) def update_defect_count(self, count: int, primary_class: str = "minor defects"): """Update the defect count display with actual AI results.""" # Update the count display self.defect_count_label.setText(str(count)) # Update the styling to show results state self._update_defect_widget_style(self.defect_widget, True) # Update status text self.defect_status_label.setText(f"Primary: {primary_class}") self.defect_status_label.setStyleSheet("color: #c82333; font-weight: bold;") # Set the flag to indicate we have defect results self.has_defect_results = True def _create_defect_item(self, defect_data): """Create a widget for a single defect item.""" widget = QWidget() widget.setFixedHeight(60) widget.setStyleSheet(""" QWidget { background-color: #34495e; border-radius: 5px; border: 1px solid #4a5f7a; } """) layout = QHBoxLayout(widget) layout.setContentsMargins(10, 8, 10, 8) layout.setSpacing(10) # Status indicator (colored circle) indicator = QLabel() indicator.setFixedSize(12, 12) indicator.setStyleSheet(f""" QLabel {{ background-color: {defect_data['color']}; border-radius: 6px; border: 1px solid {defect_data.get('border_color', '#fff')}; }} """) # Content area content_widget = QWidget() content_layout = QVBoxLayout(content_widget) content_layout.setContentsMargins(0, 0, 0, 0) content_layout.setSpacing(2) # Main info if defect_data['type'] == 'Shape Analysis': main_text = f"Shape: {defect_data.get('result', 'Unknown')} | Symmetry: {defect_data.get('symmetry', 'N/A')}" elif defect_data['type'] == 'Locule Count': main_text = f"Detected Locules: {defect_data.get('count', '0')} | Confidence: {defect_data['confidence']:.1f}%" else: main_text = f"{defect_data['type']} | Location: {defect_data['location']} | Size: {defect_data['size']}" main_label = QLabel(main_text) main_label.setFont(QFont("Arial", 9, QFont.Bold)) main_label.setStyleSheet("color: #ecf0f1;") content_layout.addWidget(main_label) # Secondary info (confidence or additional details) if 'confidence' in defect_data: confidence_text = f"Confidence: {defect_data['confidence']:.1f}%" if defect_data['type'] != 'Locule Count' and 'size' in defect_data: # Already shown above confidence_text += f" | Size: {defect_data['size']}" secondary_label = QLabel(confidence_text) secondary_label.setFont(QFont("Arial", 8)) secondary_label.setStyleSheet("color: #bdc3c7;") content_layout.addWidget(secondary_label) # Confidence value (right-aligned) confidence_label = QLabel(f"{defect_data['confidence']:.1f}%") confidence_label.setFont(QFont("Arial", 10, QFont.Bold)) confidence_label.setStyleSheet(f"color: {defect_data['color']};") confidence_label.setFixedWidth(50) confidence_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) # Add widgets to layout layout.addWidget(indicator) layout.addWidget(content_widget, 1) layout.addWidget(confidence_label) return widget def update_defects(self, defects_list): """Update the defects list with new data.""" # Clear existing defects for i in reversed(range(self.defects_layout.count())): child = self.defects_layout.itemAt(i).widget() if child is not None: child.setParent(None) # Add new defects for defect in defects_list: defect_widget = self._create_defect_item(defect) self.defects_layout.addWidget(defect_widget) # Add stretch to push everything to the top self.defects_layout.addStretch() self.update() def clear_defects(self): """Clear all defects.""" self.update_defects([])