Initial commit
This commit is contained in:
412
geniusia2/gui/dialogs/correction_dialog.py
Normal file
412
geniusia2/gui/dialogs/correction_dialog.py
Normal file
@@ -0,0 +1,412 @@
|
||||
"""
|
||||
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"<b>Élément:</b> {label}<br>"
|
||||
info += f"<b>Action:</b> {action_type}<br>"
|
||||
info += f"<b>Confiance:</b> {confidence * 100:.1f}%<br>"
|
||||
info += f"<b>Position:</b> ({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")
|
||||
Reference in New Issue
Block a user