471 lines
17 KiB
Python
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']
|