EU-Utility/plugins/game_reader_test/plugin.py

831 lines
30 KiB
Python

"""
EU-Utility - Game Reader Test Plugin
Debug and test tool for OCR and game reading functionality.
Tests screen capture, OCR accuracy, and text extraction.
"""
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
QLabel, QPushButton, QComboBox, QCheckBox,
QSpinBox, QGroupBox, QSplitter, QFrame,
QTabWidget, QLineEdit, QProgressBar,
QFileDialog, QMessageBox
)
from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal
from PyQt6.QtGui import QPixmap, QImage, QColor
from plugins.base_plugin import BasePlugin
class OCRTestThread(QThread):
"""Background thread for OCR testing."""
result_ready = pyqtSignal(dict)
progress_update = pyqtSignal(int, str)
def __init__(self, region=None, backend='auto'):
super().__init__()
self.region = region
self.backend = backend
def run(self):
"""Run OCR test."""
import time
results = {
'success': False,
'text': '',
'backend_used': '',
'processing_time': 0,
'error': None
}
try:
start_time = time.time()
self.progress_update.emit(10, "Capturing screen...")
# Capture screen
from PIL import Image, ImageGrab
if self.region:
screenshot = ImageGrab.grab(bbox=self.region)
else:
screenshot = ImageGrab.grab()
self.progress_update.emit(30, "Running OCR...")
# Try OCR backends
text = ""
backend_used = "none"
if self.backend in ('auto', 'easyocr'):
try:
import easyocr
self.progress_update.emit(50, "Loading EasyOCR...")
reader = easyocr.Reader(['en'], gpu=False, verbose=False)
self.progress_update.emit(70, "Processing with EasyOCR...")
ocr_result = reader.readtext(
screenshot,
detail=0,
paragraph=True
)
text = '\n'.join(ocr_result)
backend_used = "easyocr"
except Exception as e:
if self.backend == 'easyocr':
raise e
if not text and self.backend in ('auto', 'tesseract'):
try:
import pytesseract
self.progress_update.emit(70, "Processing with Tesseract...")
text = pytesseract.image_to_string(screenshot)
backend_used = "tesseract"
except Exception as e:
if self.backend == 'tesseract':
raise e
if not text and self.backend in ('auto', 'paddle'):
try:
from paddleocr import PaddleOCR
self.progress_update.emit(70, "Processing with PaddleOCR...")
ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log=False)
result = ocr.ocr(screenshot, cls=True)
texts = [line[1][0] for line in result[0]]
text = '\n'.join(texts)
backend_used = "paddleocr"
except Exception as e:
if self.backend == 'paddle':
raise e
processing_time = time.time() - start_time
results.update({
'success': True,
'text': text or "No text detected",
'backend_used': backend_used,
'processing_time': processing_time
})
self.progress_update.emit(100, "Complete!")
except Exception as e:
results['error'] = str(e)
self.progress_update.emit(100, f"Error: {e}")
self.result_ready.emit(results)
class GameReaderTestPlugin(BasePlugin):
"""Test and debug tool for game reading/OCR functionality."""
name = "Game Reader Test"
version = "1.0.0"
author = "EU-Utility"
description = "Debug tool for testing OCR and screen reading"
def __init__(self, overlay_window, config):
super().__init__(overlay_window, config)
self.test_history = []
self.max_history = 50
self.ocr_thread = None
def initialize(self):
"""Initialize plugin."""
self.log_info("Game Reader Test initialized")
def get_ui(self):
"""Create plugin UI."""
widget = QWidget()
layout = QVBoxLayout(widget)
layout.setSpacing(12)
# Header
header = QLabel("📷 Game Reader Test & Debug Tool")
header.setStyleSheet("font-size: 16px; font-weight: bold; color: #ff8c42;")
layout.addWidget(header)
# Create tabs
tabs = QTabWidget()
# Tab 1: Quick Test
tabs.addTab(self._create_quick_test_tab(), "Quick Test")
# Tab 2: File Test (NEW)
tabs.addTab(self._create_file_test_tab(), "File Test")
# Tab 3: Region Test
tabs.addTab(self._create_region_test_tab(), "Region Test")
# Tab 3: History
tabs.addTab(self._create_history_tab(), "History")
# Tab 4: Calibration
tabs.addTab(self._create_calibration_tab(), "Calibration")
layout.addWidget(tabs, 1)
# Status bar
status_frame = QFrame()
status_frame.setStyleSheet("background-color: #1a1f2e; border-radius: 6px; padding: 8px;")
status_layout = QHBoxLayout(status_frame)
self.status_label = QLabel("Ready - Select a tab to begin testing")
self.status_label.setStyleSheet("color: #4ecdc4;")
status_layout.addWidget(self.status_label)
layout.addWidget(status_frame)
return widget
def _create_quick_test_tab(self):
"""Create quick test tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
# Info
info = QLabel("Quick OCR Test - Captures full screen and extracts text")
info.setStyleSheet("color: #888;")
layout.addWidget(info)
# Backend selection
backend_layout = QHBoxLayout()
backend_layout.addWidget(QLabel("OCR Backend:"))
self.backend_combo = QComboBox()
self.backend_combo.addItems(["Auto (try all)", "EasyOCR", "Tesseract", "PaddleOCR"])
backend_layout.addWidget(self.backend_combo)
backend_layout.addStretch()
layout.addLayout(backend_layout)
# Progress bar
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
self.progress_bar.setStyleSheet("""
QProgressBar {
border: 1px solid #333;
border-radius: 4px;
text-align: center;
}
QProgressBar::chunk {
background-color: #ff8c42;
}
""")
layout.addWidget(self.progress_bar)
# Test button
self.test_btn = QPushButton("▶ Run OCR Test")
self.test_btn.setStyleSheet("""
QPushButton {
background-color: #ff8c42;
color: #141f23;
font-weight: bold;
padding: 12px;
font-size: 14px;
}
QPushButton:hover {
background-color: #ffa05c;
}
""")
self.test_btn.clicked.connect(self._run_quick_test)
layout.addWidget(self.test_btn)
# Results
results_group = QGroupBox("OCR Results")
results_layout = QVBoxLayout(results_group)
self.results_text = QTextEdit()
self.results_text.setReadOnly(True)
self.results_text.setPlaceholderText("OCR results will appear here...")
self.results_text.setStyleSheet("""
QTextEdit {
background-color: #0d1117;
color: #c9d1d9;
font-family: Consolas, monospace;
font-size: 12px;
}
""")
results_layout.addWidget(self.results_text)
# Stats
self.stats_label = QLabel("Backend: - | Time: - | Status: Waiting")
self.stats_label.setStyleSheet("color: #888;")
results_layout.addWidget(self.stats_label)
layout.addWidget(results_group)
# Save buttons
btn_layout = QHBoxLayout()
save_text_btn = QPushButton("💾 Save Text")
save_text_btn.clicked.connect(self._save_text)
btn_layout.addWidget(save_text_btn)
copy_btn = QPushButton("📋 Copy to Clipboard")
copy_btn.clicked.connect(self._copy_to_clipboard)
btn_layout.addWidget(copy_btn)
clear_btn = QPushButton("🗑 Clear")
clear_btn.clicked.connect(self._clear_results)
btn_layout.addWidget(clear_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
layout.addStretch()
return tab
def _create_file_test_tab(self):
"""Create file-based OCR test tab for testing with saved screenshots."""
tab = QWidget()
layout = QVBoxLayout(tab)
# Info
info = QLabel("Test OCR on an image file (PNG, JPG, BMP)")
info.setStyleSheet("color: #888;")
layout.addWidget(info)
# File selection
file_layout = QHBoxLayout()
self.file_path_label = QLabel("No file selected")
self.file_path_label.setStyleSheet("color: #aaa; padding: 8px; background-color: #1a1f2e; border-radius: 4px;")
file_layout.addWidget(self.file_path_label, 1)
browse_btn = QPushButton("Browse...")
browse_btn.setStyleSheet("""
QPushButton {
background-color: #4ecdc4;
color: #141f23;
font-weight: bold;
padding: 8px 16px;
}
""")
browse_btn.clicked.connect(self._browse_image_file)
file_layout.addWidget(browse_btn)
layout.addLayout(file_layout)
# Backend selection
backend_layout = QHBoxLayout()
backend_layout.addWidget(QLabel("OCR Backend:"))
self.file_backend_combo = QComboBox()
self.file_backend_combo.addItems(["Auto (try all)", "EasyOCR", "Tesseract", "PaddleOCR"])
backend_layout.addWidget(self.file_backend_combo)
backend_layout.addStretch()
layout.addLayout(backend_layout)
# Test button
file_test_btn = QPushButton("▶ Run OCR on File")
file_test_btn.setStyleSheet("""
QPushButton {
background-color: #ff8c42;
color: #141f23;
font-weight: bold;
padding: 12px;
font-size: 14px;
}
""")
file_test_btn.clicked.connect(self._run_file_test)
layout.addWidget(file_test_btn)
# Results
results_group = QGroupBox("OCR Results")
results_layout = QVBoxLayout(results_group)
self.file_results_text = QTextEdit()
self.file_results_text.setReadOnly(True)
self.file_results_text.setPlaceholderText("OCR results will appear here...")
self.file_results_text.setStyleSheet("""
QTextEdit {
background-color: #0d1117;
color: #c9d1d9;
font-family: Consolas, monospace;
font-size: 12px;
}
""")
results_layout.addWidget(self.file_results_text)
# Stats
self.file_stats_label = QLabel("File: - | Backend: - | Status: Waiting")
self.file_stats_label.setStyleSheet("color: #888;")
results_layout.addWidget(self.file_stats_label)
layout.addWidget(results_group, 1)
layout.addStretch()
return tab
def _create_region_test_tab(self):
"""Create region test tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
info = QLabel("Test OCR on specific screen region")
info.setStyleSheet("color: #888;")
layout.addWidget(info)
# Region input
region_group = QGroupBox("Screen Region (pixels)")
region_layout = QHBoxLayout(region_group)
self.x_input = QSpinBox()
self.x_input.setRange(0, 10000)
self.x_input.setValue(100)
region_layout.addWidget(QLabel("X:"))
region_layout.addWidget(self.x_input)
self.y_input = QSpinBox()
self.y_input.setRange(0, 10000)
self.y_input.setValue(100)
region_layout.addWidget(QLabel("Y:"))
region_layout.addWidget(self.y_input)
self.w_input = QSpinBox()
self.w_input.setRange(100, 10000)
self.w_input.setValue(400)
region_layout.addWidget(QLabel("Width:"))
region_layout.addWidget(self.w_input)
self.h_input = QSpinBox()
self.h_input.setRange(100, 10000)
self.h_input.setValue(300)
region_layout.addWidget(QLabel("Height:"))
region_layout.addWidget(self.h_input)
layout.addWidget(region_group)
# Presets
preset_layout = QHBoxLayout()
preset_layout.addWidget(QLabel("Quick Presets:"))
presets = [
("Chat Window", 10, 800, 600, 200),
("Skills Window", 100, 100, 500, 400),
("Inventory", 1200, 200, 600, 500),
("Mission Tracker", 1600, 100, 300, 600),
]
for name, x, y, w, h in presets:
btn = QPushButton(name)
btn.clicked.connect(lambda checked, px=x, py=y, pw=w, ph=h: self._set_region(px, py, pw, ph))
preset_layout.addWidget(btn)
preset_layout.addStretch()
layout.addLayout(preset_layout)
# Test button
region_test_btn = QPushButton("▶ Test Region OCR")
region_test_btn.setStyleSheet("""
QPushButton {
background-color: #4ecdc4;
color: #141f23;
font-weight: bold;
padding: 10px;
}
""")
region_test_btn.clicked.connect(self._run_region_test)
layout.addWidget(region_test_btn)
# Results
self.region_results = QTextEdit()
self.region_results.setReadOnly(True)
self.region_results.setPlaceholderText("Region OCR results will appear here...")
self.region_results.setStyleSheet("""
QTextEdit {
background-color: #0d1117;
color: #c9d1d9;
font-family: Consolas, monospace;
}
""")
layout.addWidget(self.region_results)
layout.addStretch()
return tab
def _create_history_tab(self):
"""Create history tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
info = QLabel("History of OCR tests")
info.setStyleSheet("color: #888;")
layout.addWidget(info)
self.history_text = QTextEdit()
self.history_text.setReadOnly(True)
self.history_text.setStyleSheet("""
QTextEdit {
background-color: #0d1117;
color: #c9d1d9;
}
""")
layout.addWidget(self.history_text)
# Controls
btn_layout = QHBoxLayout()
refresh_btn = QPushButton("🔄 Refresh")
refresh_btn.clicked.connect(self._update_history)
btn_layout.addWidget(refresh_btn)
clear_hist_btn = QPushButton("🗑 Clear History")
clear_hist_btn.clicked.connect(self._clear_history)
btn_layout.addWidget(clear_hist_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
return tab
def _create_calibration_tab(self):
"""Create calibration tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
info = QLabel("Calibration tools for optimizing OCR accuracy")
info.setStyleSheet("color: #888;")
layout.addWidget(info)
# DPI awareness
dpi_group = QGroupBox("Display Settings")
dpi_layout = QVBoxLayout(dpi_group)
self.dpi_label = QLabel("Detecting display DPI...")
dpi_layout.addWidget(self.dpi_label)
detect_dpi_btn = QPushButton("Detect Display Settings")
detect_dpi_btn.clicked.connect(self._detect_display_settings)
dpi_layout.addWidget(detect_dpi_btn)
layout.addWidget(dpi_group)
# Backend status
backend_group = QGroupBox("OCR Backend Status")
backend_layout = QVBoxLayout(backend_group)
self.backend_status = QTextEdit()
self.backend_status.setReadOnly(True)
self.backend_status.setMaximumHeight(150)
self._check_backends()
backend_layout.addWidget(self.backend_status)
layout.addWidget(backend_group)
# Tips
tips_group = QGroupBox("Tips for Best Results")
tips_layout = QVBoxLayout(tips_group)
tips_text = QLabel("""
• Make sure text is clearly visible and not blurry
• Use region selection to focus on specific text areas
• Higher resolution screens work better for OCR
• Close other windows to reduce background noise
• Ensure good contrast between text and background
""")
tips_text.setWordWrap(True)
tips_text.setStyleSheet("color: #aaa;")
tips_layout.addWidget(tips_text)
layout.addWidget(tips_group)
layout.addStretch()
return tab
def _set_region(self, x, y, w, h):
"""Set region values."""
self.x_input.setValue(x)
self.y_input.setValue(y)
self.w_input.setValue(w)
self.h_input.setValue(h)
def _browse_image_file(self):
"""Browse for an image file to test OCR on."""
file_path, _ = QFileDialog.getOpenFileName(
None,
"Select Image File",
"",
"Images (*.png *.jpg *.jpeg *.bmp *.tiff);;All Files (*)"
)
if file_path:
self.selected_file_path = file_path
self.file_path_label.setText(Path(file_path).name)
self.file_path_label.setStyleSheet("color: #4ecdc4; padding: 8px; background-color: #1a1f2e; border-radius: 4px;")
def _run_file_test(self):
"""Run OCR on the selected image file."""
if not hasattr(self, 'selected_file_path') or not self.selected_file_path:
QMessageBox.warning(None, "No File", "Please select an image file first!")
return
backend_map = {
0: 'auto',
1: 'easyocr',
2: 'tesseract',
3: 'paddle'
}
backend = backend_map.get(self.file_backend_combo.currentIndex(), 'auto')
self.file_results_text.setPlainText("Processing...")
self.file_stats_label.setText("Processing...")
# Run OCR in a thread
from threading import Thread
def process_file():
try:
from PIL import Image
import time
start_time = time.time()
# Load image
image = Image.open(self.selected_file_path)
# Try OCR backends
text = ""
backend_used = "none"
if backend in ('auto', 'easyocr'):
try:
import easyocr
reader = easyocr.Reader(['en'], gpu=False, verbose=False)
ocr_result = reader.readtext(
image,
detail=0,
paragraph=True
)
text = '\n'.join(ocr_result)
backend_used = "easyocr"
except Exception as e:
if backend == 'easyocr':
raise e
if not text and backend in ('auto', 'tesseract'):
try:
import pytesseract
text = pytesseract.image_to_string(image)
backend_used = "tesseract"
except Exception as e:
if backend == 'tesseract':
error_msg = str(e)
if "tesseract is not installed" in error_msg.lower() or "not in your path" in error_msg.lower():
raise Exception(
"Tesseract is not installed.\n\n"
"To use Tesseract OCR:\n"
"1. Download from: https://github.com/UB-Mannheim/tesseract/wiki\n"
"2. Install to C:\\Program Files\\Tesseract-OCR\\\n"
"3. Add to PATH or restart EU-Utility\n\n"
"Alternatively, use EasyOCR (auto-installs): pip install easyocr"
)
raise e
processing_time = time.time() - start_time
# Update UI (thread-safe via Qt signals would be better, but this works for simple case)
from PyQt6.QtCore import QMetaObject, Qt, Q_ARG
QMetaObject.invokeMethod(
self.file_results_text,
"setPlainText",
Qt.ConnectionType.QueuedConnection,
Q_ARG(str, text if text else "No text detected")
)
QMetaObject.invokeMethod(
self.file_stats_label,
"setText",
Qt.ConnectionType.QueuedConnection,
Q_ARG(str, f"File: {Path(self.selected_file_path).name} | Backend: {backend_used} | Time: {processing_time:.2f}s")
)
except Exception as e:
from PyQt6.QtCore import QMetaObject, Qt, Q_ARG
QMetaObject.invokeMethod(
self.file_results_text,
"setPlainText",
Qt.ConnectionType.QueuedConnection,
Q_ARG(str, f"Error: {str(e)}")
)
thread = Thread(target=process_file)
thread.daemon = True
thread.start()
def _run_quick_test(self):
"""Run quick OCR test."""
if self.ocr_thread and self.ocr_thread.isRunning():
return
backend_map = {
0: 'auto',
1: 'easyocr',
2: 'tesseract',
3: 'paddle'
}
backend = backend_map.get(self.backend_combo.currentIndex(), 'auto')
self.test_btn.setEnabled(False)
self.test_btn.setText("⏳ Running...")
self.progress_bar.setValue(0)
self.ocr_thread = OCRTestThread(backend=backend)
self.ocr_thread.progress_update.connect(self._update_progress)
self.ocr_thread.result_ready.connect(self._on_ocr_complete)
self.ocr_thread.start()
def _update_progress(self, value, message):
"""Update progress bar."""
self.progress_bar.setValue(value)
self.status_label.setText(message)
def _on_ocr_complete(self, results):
"""Handle OCR completion."""
self.test_btn.setEnabled(True)
self.test_btn.setText("▶ Run OCR Test")
if results['success']:
self.results_text.setPlainText(results['text'])
self.stats_label.setText(
f"Backend: {results['backend_used']} | "
f"Time: {results['processing_time']:.2f}s | "
f"Status: ✅ Success"
)
# Add to history
self._add_to_history(results)
else:
self.results_text.setPlainText(f"Error: {results.get('error', 'Unknown error')}")
self.stats_label.setText(f"Backend: {results.get('backend_used', '-')} | Status: ❌ Failed")
def _run_region_test(self):
"""Run region OCR test."""
region = (
self.x_input.value(),
self.y_input.value(),
self.w_input.value(),
self.h_input.value()
)
self.region_results.setPlainText("Running OCR on region...")
# Use api's ocr_capture if available
try:
result = self.ocr_capture(region=region)
self.region_results.setPlainText(result.get('text', 'No text detected'))
except Exception as e:
self.region_results.setPlainText(f"Error: {e}")
def _save_text(self):
"""Save OCR text to file."""
text = self.results_text.toPlainText()
if not text:
QMessageBox.warning(None, "Save Error", "No text to save!")
return
file_path, _ = QFileDialog.getSaveFileName(
None, "Save OCR Text", "ocr_result.txt", "Text Files (*.txt)"
)
if file_path:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(text)
self.status_label.setText(f"Saved to {file_path}")
def _copy_to_clipboard(self):
"""Copy text to clipboard."""
text = self.results_text.toPlainText()
if text:
self.copy_to_clipboard(text)
self.status_label.setText("Copied to clipboard!")
def _clear_results(self):
"""Clear results."""
self.results_text.clear()
self.stats_label.setText("Backend: - | Time: - | Status: Waiting")
self.progress_bar.setValue(0)
def _add_to_history(self, results):
"""Add result to history."""
from datetime import datetime
entry = {
'time': datetime.now().strftime("%H:%M:%S"),
'backend': results['backend_used'],
'time_taken': results['processing_time'],
'text_preview': results['text'][:100] + "..." if len(results['text']) > 100 else results['text']
}
self.test_history.insert(0, entry)
if len(self.test_history) > self.max_history:
self.test_history = self.test_history[:self.max_history]
self._update_history()
def _update_history(self):
"""Update history display."""
lines = []
for entry in self.test_history:
lines.append(f"[{entry['time']}] {entry['backend']} ({entry['time_taken']:.2f}s)")
lines.append(f" {entry['text_preview']}")
lines.append("")
self.history_text.setPlainText('\n'.join(lines) if lines else "No history yet")
def _clear_history(self):
"""Clear history."""
self.test_history.clear()
self._update_history()
def _detect_display_settings(self):
"""Detect display settings."""
try:
from PyQt6.QtWidgets import QApplication
from PyQt6.QtGui import QScreen
app = QApplication.instance()
if app:
screens = app.screens()
info = []
for i, screen in enumerate(screens):
geo = screen.geometry()
dpi = screen.logicalDotsPerInch()
info.append(f"Screen {i+1}: {geo.width()}x{geo.height()} @ {dpi:.0f} DPI")
self.dpi_label.setText('\n'.join(info))
except Exception as e:
self.dpi_label.setText(f"Error: {e}")
def _check_backends(self):
"""Check OCR backend availability."""
statuses = []
try:
import easyocr
statuses.append("✅ EasyOCR - Available (recommended)")
except ImportError:
statuses.append("❌ EasyOCR - Not installed\n Install: pip install easyocr")
try:
import pytesseract
# Also check if tesseract binary is available
try:
pytesseract.get_tesseract_version()
statuses.append("✅ Tesseract - Available")
except Exception:
statuses.append("⚠️ Tesseract - Python wrapper installed but tesseract binary not found\n Install binary from: https://github.com/UB-Mannheim/tesseract/wiki")
except ImportError:
statuses.append("❌ Tesseract - Not installed\n Install: pip install pytesseract\n Then install binary from: https://github.com/UB-Mannheim/tesseract/wiki")
try:
from paddleocr import PaddleOCR
statuses.append("✅ PaddleOCR - Available")
except ImportError:
statuses.append("❌ PaddleOCR - Not installed\n Install: pip install paddleocr")
try:
from PIL import ImageGrab
statuses.append("✅ Screen Capture - Available")
except ImportError:
statuses.append("❌ Screen Capture - PIL not available")
statuses.append("\n💡 Recommendation: Use EasyOCR - it auto-installs required models")
self.backend_status.setPlainText('\n'.join(statuses))
def shutdown(self):
"""Clean up."""
if self.ocr_thread and self.ocr_thread.isRunning():
self.ocr_thread.wait(1000)
super().shutdown()