| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 |
- """
- 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([])
|