Files
Geniusia_v2/geniusia2/gui/dialogs/correction_dialog.py
2026-03-05 00:20:25 +01:00

413 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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")