""" Dialogue de correction pour permettre à l'utilisateur de corriger les détections incorrectes et spécifier l'élément UI correct """ from PyQt5.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QWidget, QTextEdit, QGroupBox ) from PyQt5.QtCore import Qt, QRect, pyqtSignal from PyQt5.QtGui import QFont, QPainter, QColor, QPen, QPixmap, QImage from typing import Optional, Dict, Any, List, Tuple import logging import numpy as np class CorrectionDialog(QDialog): """ Dialogue permettant à l'utilisateur de corriger une détection incorrecte en sélectionnant l'élément UI correct parmi les détections alternatives ou en cliquant directement sur l'écran """ # Signal émis quand une correction est effectuée correction_made = pyqtSignal(dict) # Contient les détails de la correction def __init__(self, incorrect_detection: Dict[str, Any], alternative_detections: List[Dict[str, Any]] = None, screenshot: Optional[np.ndarray] = None, parent=None): """ Initialiser le dialogue de correction Args: incorrect_detection: Détection incorrecte à corriger { "label": str, "confidence": float, "bbox": (x, y, w, h), "action_type": str } alternative_detections: Liste de détections alternatives disponibles screenshot: Capture d'écran pour sélection visuelle (optionnel) parent: Widget parent (optionnel) """ super().__init__(parent) self.incorrect_detection = incorrect_detection self.alternative_detections = alternative_detections or [] self.screenshot = screenshot self.logger = logging.getLogger(__name__) self.corrected_element = None self.correction_method = None # "alternative" ou "manual" self.init_ui() self.logger.info(f"CorrectionDialog créé pour élément: {incorrect_detection.get('label')}") def init_ui(self): """Initialiser l'interface du dialogue""" self.setWindowTitle("Correction de Détection") self.setModal(True) self.setMinimumWidth(600) self.setMinimumHeight(500) # Layout principal main_layout = QVBoxLayout() self.setLayout(main_layout) # Titre title_label = QLabel("✎ Correction de Détection Incorrecte") title_label.setFont(QFont("Arial", 14, QFont.Bold)) title_label.setStyleSheet("color: #FF9800; padding: 10px;") main_layout.addWidget(title_label) # Informations sur la détection incorrecte incorrect_group = QGroupBox("Détection Incorrecte") incorrect_group.setFont(QFont("Arial", 11, QFont.Bold)) incorrect_layout = QVBoxLayout() incorrect_group.setLayout(incorrect_layout) incorrect_info = self._format_detection_info(self.incorrect_detection) incorrect_label = QLabel(incorrect_info) incorrect_label.setFont(QFont("Arial", 10)) incorrect_label.setStyleSheet("padding: 10px; background-color: #FFEBEE; border-radius: 5px;") incorrect_layout.addWidget(incorrect_label) main_layout.addWidget(incorrect_group) # Section des alternatives if self.alternative_detections: alternatives_group = QGroupBox("Sélectionner l'Élément Correct") alternatives_group.setFont(QFont("Arial", 11, QFont.Bold)) alternatives_layout = QVBoxLayout() alternatives_group.setLayout(alternatives_layout) instruction_label = QLabel( "Sélectionnez l'élément correct parmi les détections alternatives ci-dessous:" ) instruction_label.setFont(QFont("Arial", 10)) instruction_label.setWordWrap(True) alternatives_layout.addWidget(instruction_label) # Liste des alternatives self.alternatives_list = QListWidget() self.alternatives_list.setFont(QFont("Arial", 10)) self.alternatives_list.setStyleSheet(""" QListWidget { border: 2px solid #2196F3; border-radius: 5px; padding: 5px; } QListWidget::item { padding: 10px; border-bottom: 1px solid #E0E0E0; } QListWidget::item:selected { background-color: #E3F2FD; color: #1976D2; } QListWidget::item:hover { background-color: #F5F5F5; } """) for idx, detection in enumerate(self.alternative_detections): item_text = self._format_detection_item(detection, idx + 1) item = QListWidgetItem(item_text) item.setData(Qt.UserRole, detection) self.alternatives_list.addItem(item) self.alternatives_list.itemDoubleClicked.connect(self.on_alternative_selected) alternatives_layout.addWidget(self.alternatives_list) main_layout.addWidget(alternatives_group) # Section de sélection manuelle manual_group = QGroupBox("Ou Spécifier Manuellement") manual_group.setFont(QFont("Arial", 11, QFont.Bold)) manual_layout = QVBoxLayout() manual_group.setLayout(manual_layout) manual_instruction = QLabel( "Décrivez l'élément correct ou fournissez des informations supplémentaires:" ) manual_instruction.setFont(QFont("Arial", 10)) manual_instruction.setWordWrap(True) manual_layout.addWidget(manual_instruction) self.manual_input = QTextEdit() self.manual_input.setFont(QFont("Arial", 10)) self.manual_input.setPlaceholderText( "Ex: Le bouton 'Valider' en bas à droite de la fenêtre, " "ou les coordonnées approximatives..." ) self.manual_input.setMaximumHeight(80) self.manual_input.setStyleSheet(""" QTextEdit { border: 2px solid #9E9E9E; border-radius: 5px; padding: 5px; } QTextEdit:focus { border-color: #2196F3; } """) manual_layout.addWidget(self.manual_input) main_layout.addWidget(manual_group) # Boutons d'action button_layout = QHBoxLayout() self.select_button = QPushButton("✓ Valider la Sélection") self.select_button.setFont(QFont("Arial", 11)) self.select_button.setStyleSheet(""" QPushButton { background-color: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 5px; } QPushButton:hover { background-color: #45a049; } QPushButton:disabled { background-color: #cccccc; } """) self.select_button.clicked.connect(self.on_validate_correction) button_layout.addWidget(self.select_button) cancel_button = QPushButton("✗ Annuler") cancel_button.setFont(QFont("Arial", 11)) cancel_button.setStyleSheet(""" QPushButton { background-color: #f44336; color: white; border: none; padding: 10px 20px; border-radius: 5px; } QPushButton:hover { background-color: #da190b; } """) cancel_button.clicked.connect(self.reject) button_layout.addWidget(cancel_button) main_layout.addLayout(button_layout) # Note d'aide help_label = QLabel( "💡 Astuce: Double-cliquez sur une alternative pour la sélectionner rapidement" ) help_label.setFont(QFont("Arial", 9)) help_label.setStyleSheet("color: #666; padding: 5px;") main_layout.addWidget(help_label) def _format_detection_info(self, detection: Dict[str, Any]) -> str: """ Formater les informations d'une détection pour affichage Args: detection: Dictionnaire de détection Returns: Chaîne formatée """ label = detection.get("label", "inconnu") confidence = detection.get("confidence", 0.0) bbox = detection.get("bbox", (0, 0, 0, 0)) action_type = detection.get("action_type", "action") info = f"Élément: {label}
" info += f"Action: {action_type}
" info += f"Confiance: {confidence * 100:.1f}%
" info += f"Position: ({bbox[0]}, {bbox[1]}) - Taille: {bbox[2]}×{bbox[3]}px" return info def _format_detection_item(self, detection: Dict[str, Any], index: int) -> str: """ Formater un élément de détection pour la liste Args: detection: Dictionnaire de détection index: Numéro de l'alternative Returns: Chaîne formatée """ label = detection.get("label", "inconnu") confidence = detection.get("confidence", 0.0) bbox = detection.get("bbox", (0, 0, 0, 0)) return (f"{index}. {label} " f"(Confiance: {confidence * 100:.1f}%, " f"Position: {bbox[0]},{bbox[1]})") def on_alternative_selected(self, item: QListWidgetItem): """ Gestionnaire de sélection d'une alternative (double-clic) Args: item: Élément de liste sélectionné """ detection = item.data(Qt.UserRole) self.corrected_element = detection self.correction_method = "alternative" self.logger.info(f"Alternative sélectionnée: {detection.get('label')}") self.accept() def on_validate_correction(self): """Valider la correction (bouton Valider)""" # Vérifier si une alternative est sélectionnée if self.alternative_detections and self.alternatives_list.currentItem(): item = self.alternatives_list.currentItem() detection = item.data(Qt.UserRole) self.corrected_element = detection self.correction_method = "alternative" self.logger.info(f"Correction validée (alternative): {detection.get('label')}") self.accept() # Sinon, vérifier si une description manuelle est fournie elif self.manual_input.toPlainText().strip(): manual_description = self.manual_input.toPlainText().strip() self.corrected_element = { "label": "manual_correction", "description": manual_description, "confidence": 0.0, "bbox": self.incorrect_detection.get("bbox", (0, 0, 0, 0)), "action_type": self.incorrect_detection.get("action_type", "action") } self.correction_method = "manual" self.logger.info(f"Correction validée (manuelle): {manual_description[:50]}...") self.accept() else: # Aucune correction fournie from PyQt5.QtWidgets import QMessageBox QMessageBox.warning( self, "Correction Requise", "Veuillez sélectionner une alternative ou fournir une description manuelle." ) def get_corrected_element(self) -> Optional[Dict[str, Any]]: """ Obtenir l'élément corrigé après fermeture du dialogue Returns: Dictionnaire contenant les détails de la correction, ou None si annulé { "corrected_element": Dict[str, Any], "correction_method": str, "original_detection": Dict[str, Any] } """ if self.corrected_element: return { "corrected_element": self.corrected_element, "correction_method": self.correction_method, "original_detection": self.incorrect_detection } return None @staticmethod def show_correction_dialog(incorrect_detection: Dict[str, Any], alternative_detections: List[Dict[str, Any]] = None, screenshot: Optional[np.ndarray] = None, parent=None) -> Optional[Dict[str, Any]]: """ Méthode statique pour afficher le dialogue et obtenir la correction Args: incorrect_detection: Détection incorrecte alternative_detections: Liste de détections alternatives screenshot: Capture d'écran (optionnel) parent: Widget parent (optionnel) Returns: Dictionnaire de correction ou None si annulé """ dialog = CorrectionDialog( incorrect_detection, alternative_detections, screenshot, parent ) result = dialog.exec_() if result == QDialog.Accepted: return dialog.get_corrected_element() return None if __name__ == "__main__": """Test du dialogue de correction""" from PyQt5.QtWidgets import QApplication import sys app = QApplication(sys.argv) # Données de test incorrect = { "label": "annuler_button", "confidence": 0.75, "bbox": (300, 400, 100, 35), "action_type": "click" } alternatives = [ { "label": "valider_button", "confidence": 0.92, "bbox": (450, 400, 100, 35), "action_type": "click" }, { "label": "enregistrer_button", "confidence": 0.88, "bbox": (600, 400, 120, 35), "action_type": "click" }, { "label": "fermer_button", "confidence": 0.65, "bbox": (750, 50, 30, 30), "action_type": "click" } ] correction = CorrectionDialog.show_correction_dialog( incorrect, alternatives ) if correction: print("Correction effectuée:") print(f" Méthode: {correction['correction_method']}") print(f" Élément corrigé: {correction['corrected_element']['label']}") print(f" Élément original: {correction['original_detection']['label']}") else: print("Correction annulée")