Lemontropia-Suite/ui/vision_test_dialog.py

471 lines
17 KiB
Python

"""
Lemontropia Suite - Vision Test Dialog
Test and debug Game Vision AI functionality.
"""
import time
from pathlib import Path
from typing import Optional
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QComboBox, QCheckBox, QGroupBox, QFormLayout,
QMessageBox, QFileDialog, QTextEdit, QProgressBar,
QListWidget, QListWidgetItem, QSplitter, QWidget,
QTableWidget, QTableWidgetItem, QHeaderView
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QPixmap, QImage, QFont
import numpy as np
import logging
logger = logging.getLogger(__name__)
class VisionTestWorker(QThread):
"""Worker thread for vision testing."""
test_complete = pyqtSignal(dict)
progress = pyqtSignal(str)
error_occurred = pyqtSignal(str)
def __init__(self, image_path: Path, settings: dict):
super().__init__()
self.image_path = image_path
self.settings = settings
def run(self):
try:
from modules.game_vision_ai import GameVisionAI
self.progress.emit("Initializing Game Vision AI...")
vision = GameVisionAI(
use_gpu=self.settings.get('use_gpu', True),
ocr_lang=self.settings.get('ocr_lang', 'en')
)
self.progress.emit("Processing image...")
start_time = time.time()
result = vision.process_screenshot(
self.image_path,
extract_text=self.settings.get('extract_text', True),
extract_icons=self.settings.get('extract_icons', True)
)
processing_time = (time.time() - start_time) * 1000
# Prepare results
test_results = {
'success': True,
'processing_time_ms': processing_time,
'gpu_backend': result.gpu_backend,
'text_regions': [
{
'text': t.text,
'confidence': t.confidence,
'bbox': t.bbox,
'language': t.language
}
for t in result.text_regions
],
'icon_regions': [
{
'bbox': i.bbox,
'confidence': i.confidence,
'hash': i.icon_hash[:16] # Truncated hash
}
for i in result.icon_regions
],
'text_count': len(result.text_regions),
'icon_count': len(result.icon_regions)
}
self.test_complete.emit(test_results)
except Exception as e:
self.error_occurred.emit(str(e))
class VisionTestDialog(QDialog):
"""
Dialog for testing and debugging Game Vision AI.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Test Game Vision")
self.setMinimumSize(900, 700)
self.current_image_path: Optional[Path] = None
self.current_results: Optional[dict] = None
self.setup_ui()
def setup_ui(self):
"""Setup dialog UI."""
layout = QVBoxLayout(self)
layout.setSpacing(10)
# Title
title_label = QLabel("🧪 Game Vision Test & Debug")
title_font = QFont()
title_font.setPointSize(14)
title_font.setBold(True)
title_label.setFont(title_font)
layout.addWidget(title_label)
# Main splitter
splitter = QSplitter(Qt.Orientation.Horizontal)
layout.addWidget(splitter)
# Left panel - Controls
left_panel = QWidget()
left_layout = QVBoxLayout(left_panel)
left_layout.setContentsMargins(5, 5, 5, 5)
# Image selection
image_group = QGroupBox("Test Image")
image_layout = QVBoxLayout(image_group)
self.image_path_label = QLabel("No image selected")
self.image_path_label.setWordWrap(True)
image_layout.addWidget(self.image_path_label)
image_btn_layout = QHBoxLayout()
self.browse_btn = QPushButton("Browse...")
self.browse_btn.clicked.connect(self.browse_image)
image_btn_layout.addWidget(self.browse_btn)
self.capture_btn = QPushButton("Capture Screen")
self.capture_btn.clicked.connect(self.capture_screen)
image_btn_layout.addWidget(self.capture_btn)
image_btn_layout.addStretch()
image_layout.addLayout(image_btn_layout)
left_layout.addWidget(image_group)
# Test settings
settings_group = QGroupBox("Test Settings")
settings_layout = QFormLayout(settings_group)
self.use_gpu_cb = QCheckBox("Use GPU acceleration")
self.use_gpu_cb.setChecked(True)
settings_layout.addRow(self.use_gpu_cb)
self.extract_text_cb = QCheckBox("Extract text (OCR)")
self.extract_text_cb.setChecked(True)
settings_layout.addRow(self.extract_text_cb)
self.extract_icons_cb = QCheckBox("Extract icons")
self.extract_icons_cb.setChecked(True)
settings_layout.addRow(self.extract_icons_cb)
self.ocr_lang_combo = QComboBox()
self.ocr_lang_combo.addItem("English", "en")
self.ocr_lang_combo.addItem("Swedish", "sv")
settings_layout.addRow("OCR Language:", self.ocr_lang_combo)
left_layout.addWidget(settings_group)
# Run test button
self.test_btn = QPushButton("▶ Run Vision Test")
self.test_btn.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
color: white;
font-weight: bold;
padding: 10px;
}
QPushButton:hover {
background-color: #45a049;
}
QPushButton:disabled {
background-color: #cccccc;
}
""")
self.test_btn.clicked.connect(self.run_test)
self.test_btn.setEnabled(False)
left_layout.addWidget(self.test_btn)
# Progress
self.progress_label = QLabel("")
left_layout.addWidget(self.progress_label)
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 0) # Indeterminate
self.progress_bar.setVisible(False)
left_layout.addWidget(self.progress_bar)
# GPU Info
gpu_group = QGroupBox("GPU Information")
gpu_layout = QVBoxLayout(gpu_group)
self.gpu_info_label = QLabel("Click 'Check GPU' to detect")
self.gpu_info_label.setWordWrap(True)
gpu_layout.addWidget(self.gpu_info_label)
self.check_gpu_btn = QPushButton("Check GPU")
self.check_gpu_btn.clicked.connect(self.check_gpu)
gpu_layout.addWidget(self.check_gpu_btn)
left_layout.addWidget(gpu_group)
left_layout.addStretch()
splitter.addWidget(left_panel)
# Right panel - Results
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
right_layout.setContentsMargins(5, 5, 5, 5)
# Image preview
preview_group = QGroupBox("Image Preview")
preview_layout = QVBoxLayout(preview_group)
self.preview_label = QLabel("No image loaded")
self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.preview_label.setMinimumHeight(200)
self.preview_label.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;")
preview_layout.addWidget(self.preview_label)
right_layout.addWidget(preview_group)
# Results tabs
from PyQt6.QtWidgets import QTabWidget
self.results_tabs = QTabWidget()
right_layout.addWidget(self.results_tabs)
# Summary tab
self.summary_tab = QTextEdit()
self.summary_tab.setReadOnly(True)
self.results_tabs.addTab(self.summary_tab, "Summary")
# Text regions tab
self.text_table = QTableWidget()
self.text_table.setColumnCount(4)
self.text_table.setHorizontalHeaderLabels(["Text", "Confidence", "Position", "Language"])
self.text_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
self.results_tabs.addTab(self.text_table, "Text Regions")
# Icon regions tab
self.icon_table = QTableWidget()
self.icon_table.setColumnCount(3)
self.icon_table.setHorizontalHeaderLabels(["Position", "Confidence", "Hash"])
self.icon_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
self.results_tabs.addTab(self.icon_table, "Icon Regions")
# Log tab
self.log_text = QTextEdit()
self.log_text.setReadOnly(True)
self.results_tabs.addTab(self.log_text, "Log")
splitter.addWidget(right_panel)
splitter.setSizes([300, 600])
# Close button
btn_layout = QHBoxLayout()
btn_layout.addStretch()
self.close_btn = QPushButton("Close")
self.close_btn.clicked.connect(self.accept)
btn_layout.addWidget(self.close_btn)
layout.addLayout(btn_layout)
def browse_image(self):
"""Browse for test image."""
file_path, _ = QFileDialog.getOpenFileName(
self, "Select Test Image",
str(Path.home()),
"Images (*.png *.jpg *.jpeg *.bmp)"
)
if file_path:
self.load_image(Path(file_path))
def capture_screen(self):
"""Capture screen for testing."""
try:
import mss
import numpy as np
import cv2
from PyQt6.QtGui import QImage, QPixmap
self.progress_label.setText("Capturing screen...")
with mss.mss() as sct:
monitor = sct.monitors[1] # Primary monitor
screenshot = sct.grab(monitor)
# Convert to numpy array
img = np.array(screenshot)
img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
# Save temporarily
temp_path = Path.home() / ".lemontropia" / "temp_capture.png"
temp_path.parent.mkdir(parents=True, exist_ok=True)
cv2.imwrite(str(temp_path), img)
self.load_image(temp_path)
self.progress_label.setText("Screen captured")
except Exception as e:
QMessageBox.critical(self, "Capture Failed", f"Failed to capture screen: {e}")
self.progress_label.setText("")
def load_image(self, image_path: Path):
"""Load and display image."""
self.current_image_path = image_path
self.image_path_label.setText(str(image_path))
# Load and display preview
pixmap = QPixmap(str(image_path))
if not pixmap.isNull():
# Scale to fit
scaled = pixmap.scaled(
self.preview_label.size(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation
)
self.preview_label.setPixmap(scaled)
self.test_btn.setEnabled(True)
else:
self.preview_label.setText("Failed to load image")
self.test_btn.setEnabled(False)
def run_test(self):
"""Run vision test."""
if not self.current_image_path:
QMessageBox.warning(self, "No Image", "Please select an image first.")
return
# Collect settings
settings = {
'use_gpu': self.use_gpu_cb.isChecked(),
'extract_text': self.extract_text_cb.isChecked(),
'extract_icons': self.extract_icons_cb.isChecked(),
'ocr_lang': self.ocr_lang_combo.currentData()
}
# Disable controls
self.test_btn.setEnabled(False)
self.browse_btn.setEnabled(False)
self.capture_btn.setEnabled(False)
self.progress_bar.setVisible(True)
self.progress_label.setText("Running vision test...")
# Clear previous results
self.summary_tab.clear()
self.text_table.setRowCount(0)
self.icon_table.setRowCount(0)
# Start worker
self.worker = VisionTestWorker(self.current_image_path, settings)
self.worker.test_complete.connect(self.on_test_complete)
self.worker.progress.connect(self.on_test_progress)
self.worker.error_occurred.connect(self.on_test_error)
self.worker.start()
def on_test_progress(self, message: str):
"""Handle test progress."""
self.progress_label.setText(message)
self.log_text.append(f"[{time.strftime('%H:%M:%S')}] {message}")
def on_test_complete(self, results: dict):
"""Handle test completion."""
self.current_results = results
# Re-enable controls
self.test_btn.setEnabled(True)
self.browse_btn.setEnabled(True)
self.capture_btn.setEnabled(True)
self.progress_bar.setVisible(False)
self.progress_label.setText("Test complete!")
# Update summary
summary = f"""
<h2>Vision Test Results</h2>
<p><b>Processing Time:</b> {results['processing_time_ms']:.1f}ms</p>
<p><b>GPU Backend:</b> {results['gpu_backend']}</p>
<p><b>Text Regions Detected:</b> {results['text_count']}</p>
<p><b>Icon Regions Detected:</b> {results['icon_count']}</p>
"""
self.summary_tab.setHtml(summary)
# Update text table
self.text_table.setRowCount(len(results['text_regions']))
for i, text in enumerate(results['text_regions']):
self.text_table.setItem(i, 0, QTableWidgetItem(text['text']))
self.text_table.setItem(i, 1, QTableWidgetItem(f"{text['confidence']:.2%}"))
bbox_str = f"({text['bbox'][0]}, {text['bbox'][1]})"
self.text_table.setItem(i, 2, QTableWidgetItem(bbox_str))
self.text_table.setItem(i, 3, QTableWidgetItem(text['language']))
# Update icon table
self.icon_table.setRowCount(len(results['icon_regions']))
for i, icon in enumerate(results['icon_regions']):
bbox_str = f"({icon['bbox'][0]}, {icon['bbox'][1]}, {icon['bbox'][2]}x{icon['bbox'][3]})"
self.icon_table.setItem(i, 0, QTableWidgetItem(bbox_str))
self.icon_table.setItem(i, 1, QTableWidgetItem(f"{icon['confidence']:.2%}"))
self.icon_table.setItem(i, 2, QTableWidgetItem(icon['hash']))
logger.info(f"Vision test complete: {results['text_count']} texts, {results['icon_count']} icons")
def on_test_error(self, error: str):
"""Handle test error."""
self.test_btn.setEnabled(True)
self.browse_btn.setEnabled(True)
self.capture_btn.setEnabled(True)
self.progress_bar.setVisible(False)
self.progress_label.setText(f"Error: {error}")
QMessageBox.critical(self, "Test Failed", f"Vision test failed:\n{error}")
self.log_text.append(f"[ERROR] {error}")
logger.error(f"Vision test failed: {error}")
def check_gpu(self):
"""Check GPU availability."""
try:
from modules.game_vision_ai import GPUDetector
info = GPUDetector.get_gpu_info()
text = f"""
<b>GPU Information</b><br>
Backend: {info['backend']}<br>
CUDA Available: {info['cuda_available']}<br>
MPS Available: {info['mps_available']}<br>
"""
if info.get('devices'):
for dev in info['devices']:
mem_gb = dev.get('memory_total', 0) / (1024**3)
text += f"Device {dev['id']}: {dev['name']} ({mem_gb:.1f} GB)<br>"
self.gpu_info_label.setText(text)
except Exception as e:
self.gpu_info_label.setText(f"Error detecting GPU: {e}")
logger.error(f"GPU detection failed: {e}")
def resizeEvent(self, event):
"""Handle resize to update preview."""
super().resizeEvent(event)
if self.current_image_path and self.preview_label.pixmap():
pixmap = QPixmap(str(self.current_image_path))
scaled = pixmap.scaled(
self.preview_label.size(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation
)
self.preview_label.setPixmap(scaled)
# Export
__all__ = ['VisionTestDialog']