1193 lines
44 KiB
Python
1193 lines
44 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.
|
|
"""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
|
|
QLabel, QPushButton, QComboBox, QCheckBox,
|
|
QSpinBox, QGroupBox, QSplitter, QFrame,
|
|
QTabWidget, QLineEdit, QProgressBar,
|
|
QFileDialog, QMessageBox, QTableWidget,
|
|
QTableWidgetItem, QHeaderView
|
|
)
|
|
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
|
|
import numpy as np
|
|
self.progress_update.emit(50, "Loading EasyOCR...")
|
|
reader = easyocr.Reader(['en'], gpu=False, verbose=False)
|
|
self.progress_update.emit(70, "Processing with EasyOCR...")
|
|
# Convert PIL Image to numpy array
|
|
screenshot_np = np.array(screenshot)
|
|
ocr_result = reader.readtext(
|
|
screenshot_np,
|
|
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
|
|
import numpy as np
|
|
self.progress_update.emit(70, "Processing with PaddleOCR...")
|
|
# Try with show_log, fall back without for compatibility
|
|
try:
|
|
ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log=False)
|
|
except TypeError:
|
|
ocr = PaddleOCR(use_angle_cls=True, lang='en')
|
|
# Convert PIL to numpy
|
|
screenshot_np = np.array(screenshot)
|
|
result = ocr.ocr(screenshot_np, cls=True)
|
|
if result and result[0]:
|
|
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"
|
|
|
|
# Dependencies for OCR functionality
|
|
dependencies = {
|
|
'pip': ['pillow', 'numpy'],
|
|
'optional': {
|
|
'easyocr': 'Best OCR accuracy, auto-downloads models',
|
|
'pytesseract': 'Alternative OCR engine',
|
|
'paddleocr': 'Advanced OCR with layout detection'
|
|
}
|
|
}
|
|
|
|
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 5: Skills Parser (NEW)
|
|
tabs.addTab(self._create_skills_parser_tab(), "Skills Parser")
|
|
|
|
# Tab 6: 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_skills_parser_tab(self):
|
|
"""Create skills parser tab for testing skill window OCR."""
|
|
tab = QWidget()
|
|
layout = QVBoxLayout(tab)
|
|
|
|
info = QLabel("📊 Skills Window Parser - Extract skills from the EU Skills window")
|
|
info.setStyleSheet("color: #888;")
|
|
layout.addWidget(info)
|
|
|
|
# Instructions
|
|
instructions = QLabel(
|
|
"1. Open your Skills window in Entropia Universe\n"
|
|
"2. Click 'Capture Skills Window' below\n"
|
|
"3. View parsed skills in the table"
|
|
)
|
|
instructions.setStyleSheet("color: #666; font-size: 11px;")
|
|
layout.addWidget(instructions)
|
|
|
|
# Capture button
|
|
capture_btn = QPushButton("📷 Capture Skills Window")
|
|
capture_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #ff8c42;
|
|
color: #141f23;
|
|
font-weight: bold;
|
|
padding: 12px;
|
|
font-size: 14px;
|
|
}
|
|
""")
|
|
capture_btn.clicked.connect(self._capture_and_parse_skills)
|
|
layout.addWidget(capture_btn)
|
|
|
|
# Results table
|
|
from PyQt6.QtWidgets import QTableWidget, QTableWidgetItem, QHeaderView
|
|
|
|
self.skills_table = QTableWidget()
|
|
self.skills_table.setColumnCount(3)
|
|
self.skills_table.setHorizontalHeaderLabels(["Skill Name", "Rank", "Points"])
|
|
self.skills_table.horizontalHeader().setStretchLastSection(True)
|
|
self.skills_table.setStyleSheet("""
|
|
QTableWidget {
|
|
background-color: #0d1117;
|
|
border: 1px solid #333;
|
|
}
|
|
QTableWidget::item {
|
|
padding: 6px;
|
|
color: #c9d1d9;
|
|
}
|
|
QHeaderView::section {
|
|
background-color: #1a1f2e;
|
|
color: #ff8c42;
|
|
padding: 8px;
|
|
font-weight: bold;
|
|
}
|
|
""")
|
|
layout.addWidget(self.skills_table, 1)
|
|
|
|
# Stats label
|
|
self.skills_stats_label = QLabel("No skills captured yet")
|
|
self.skills_stats_label.setStyleSheet("color: #888;")
|
|
layout.addWidget(self.skills_stats_label)
|
|
|
|
# Raw text view
|
|
raw_group = QGroupBox("Raw OCR Text (for debugging)")
|
|
raw_layout = QVBoxLayout(raw_group)
|
|
|
|
self.skills_raw_text = QTextEdit()
|
|
self.skills_raw_text.setReadOnly(True)
|
|
self.skills_raw_text.setMaximumHeight(150)
|
|
self.skills_raw_text.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: #0d1117;
|
|
color: #666;
|
|
font-family: Consolas, monospace;
|
|
font-size: 10px;
|
|
}
|
|
""")
|
|
raw_layout.addWidget(self.skills_raw_text)
|
|
layout.addWidget(raw_group)
|
|
|
|
layout.addStretch()
|
|
return tab
|
|
|
|
def _capture_and_parse_skills(self):
|
|
"""Capture screen and parse skills."""
|
|
from PyQt6.QtCore import Qt
|
|
|
|
self.skills_stats_label.setText("Capturing...")
|
|
self.skills_stats_label.setStyleSheet("color: #4ecdc4;")
|
|
|
|
# Run in thread to not block UI
|
|
from threading import Thread
|
|
|
|
def capture_and_parse():
|
|
try:
|
|
from PIL import ImageGrab
|
|
import re
|
|
from datetime import datetime
|
|
|
|
# Capture screen
|
|
screenshot = ImageGrab.grab()
|
|
|
|
# Run OCR
|
|
text = ""
|
|
try:
|
|
import easyocr
|
|
reader = easyocr.Reader(['en'], gpu=False, verbose=False)
|
|
import numpy as np
|
|
result = reader.readtext(np.array(screenshot), detail=0, paragraph=False)
|
|
text = '\n'.join(result)
|
|
except Exception as e:
|
|
# Fallback to raw OCR
|
|
text = str(e)
|
|
|
|
# Parse skills
|
|
skills = self._parse_skills_from_text(text)
|
|
|
|
# Update UI
|
|
from PyQt6.QtCore import QMetaObject, Qt, Q_ARG
|
|
QMetaObject.invokeMethod(
|
|
self.skills_raw_text,
|
|
"setPlainText",
|
|
Qt.ConnectionType.QueuedConnection,
|
|
Q_ARG(str, text)
|
|
)
|
|
|
|
# Update table
|
|
QMetaObject.invokeMethod(
|
|
self, "_update_skills_table",
|
|
Qt.ConnectionType.QueuedConnection,
|
|
Q_ARG(object, skills)
|
|
)
|
|
|
|
# Update stats
|
|
stats_text = f"Found {len(skills)} skills"
|
|
QMetaObject.invokeMethod(
|
|
self.skills_stats_label,
|
|
"setText",
|
|
Qt.ConnectionType.QueuedConnection,
|
|
Q_ARG(str, stats_text)
|
|
)
|
|
QMetaObject.invokeMethod(
|
|
self.skills_stats_label,
|
|
"setStyleSheet",
|
|
Qt.ConnectionType.QueuedConnection,
|
|
Q_ARG(str, "color: #4ecdc4;")
|
|
)
|
|
|
|
except Exception as e:
|
|
from PyQt6.QtCore import QMetaObject, Qt, Q_ARG
|
|
QMetaObject.invokeMethod(
|
|
self.skills_stats_label,
|
|
"setText",
|
|
Qt.ConnectionType.QueuedConnection,
|
|
Q_ARG(str, f"Error: {str(e)}")
|
|
)
|
|
QMetaObject.invokeMethod(
|
|
self.skills_stats_label,
|
|
"setStyleSheet",
|
|
Qt.ConnectionType.QueuedConnection,
|
|
Q_ARG(str, "color: #ff6b6b;")
|
|
)
|
|
|
|
thread = Thread(target=capture_and_parse)
|
|
thread.daemon = True
|
|
thread.start()
|
|
|
|
def _parse_skills_from_text(self, text):
|
|
"""Parse skills from OCR text."""
|
|
skills = {}
|
|
|
|
# Ranks in Entropia Universe
|
|
RANKS = [
|
|
'Newbie', 'Inept', 'Beginner', 'Amateur', 'Average',
|
|
'Skilled', 'Expert', 'Professional', 'Master', 'Grand Master',
|
|
'Champion', 'Legendary', 'Guru', 'Astonishing', 'Remarkable',
|
|
'Outstanding', 'Marvelous', 'Prodigious', 'Amazing', 'Incredible', 'Awesome'
|
|
]
|
|
rank_pattern = '|'.join(RANKS)
|
|
|
|
# Clean text
|
|
text = text.replace('SKILLS', '').replace('ALL CATEGORIES', '')
|
|
text = text.replace('SKILL NAME', '').replace('RANK', '').replace('POINTS', '')
|
|
|
|
lines = text.split('\n')
|
|
|
|
for line in lines:
|
|
line = line.strip()
|
|
if not line or len(line) < 10:
|
|
continue
|
|
|
|
# Pattern: SkillName Rank Points
|
|
match = re.search(
|
|
rf'([A-Za-z][A-Za-z\s]{{2,50}}?)\s+({rank_pattern})\s+(\d{{1,6}})(?:\s|$)',
|
|
line, re.IGNORECASE
|
|
)
|
|
|
|
if match:
|
|
skill_name = match.group(1).strip()
|
|
rank = match.group(2)
|
|
points = int(match.group(3))
|
|
|
|
if points > 0 and skill_name:
|
|
skills[skill_name] = {'rank': rank, 'points': points}
|
|
|
|
return skills
|
|
|
|
def _update_skills_table(self, skills):
|
|
"""Update the skills table with parsed data."""
|
|
self.skills_table.setRowCount(len(skills))
|
|
|
|
for i, (skill_name, data) in enumerate(sorted(skills.items())):
|
|
self.skills_table.setItem(i, 0, QTableWidgetItem(skill_name))
|
|
self.skills_table.setItem(i, 1, QTableWidgetItem(data['rank']))
|
|
self.skills_table.setItem(i, 2, QTableWidgetItem(str(data['points'])))
|
|
|
|
self.skills_table.resizeColumnsToContents()
|
|
|
|
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(200)
|
|
self._check_backends()
|
|
backend_layout.addWidget(self.backend_status)
|
|
|
|
# Install buttons
|
|
install_layout = QHBoxLayout()
|
|
|
|
install_easyocr_btn = QPushButton("📦 Install EasyOCR")
|
|
install_easyocr_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #4ecdc4;
|
|
color: #141f23;
|
|
font-weight: bold;
|
|
padding: 8px;
|
|
}
|
|
""")
|
|
install_easyocr_btn.clicked.connect(self._install_easyocr)
|
|
install_layout.addWidget(install_easyocr_btn)
|
|
|
|
install_tesseract_btn = QPushButton("📦 Install Tesseract Package")
|
|
install_tesseract_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #ff8c42;
|
|
color: #141f23;
|
|
font-weight: bold;
|
|
padding: 8px;
|
|
}
|
|
""")
|
|
install_tesseract_btn.clicked.connect(self._install_pytesseract)
|
|
install_layout.addWidget(install_tesseract_btn)
|
|
|
|
detect_tesseract_btn = QPushButton("🔍 Auto-Detect Tesseract")
|
|
detect_tesseract_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #a0aec0;
|
|
color: #141f23;
|
|
font-weight: bold;
|
|
padding: 8px;
|
|
}
|
|
""")
|
|
detect_tesseract_btn.clicked.connect(self._auto_detect_tesseract)
|
|
install_layout.addWidget(detect_tesseract_btn)
|
|
|
|
install_paddle_btn = QPushButton("📦 Install PaddleOCR")
|
|
install_paddle_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #4a5568;
|
|
color: white;
|
|
font-weight: bold;
|
|
padding: 8px;
|
|
}
|
|
""")
|
|
install_paddle_btn.clicked.connect(self._install_paddleocr)
|
|
install_layout.addWidget(install_paddle_btn)
|
|
|
|
backend_layout.addLayout(install_layout)
|
|
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
|
|
import numpy as np
|
|
reader = easyocr.Reader(['en'], gpu=False, verbose=False)
|
|
# Convert PIL Image to numpy array for EasyOCR
|
|
image_np = np.array(image)
|
|
ocr_result = reader.readtext(
|
|
image_np,
|
|
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
|
|
|
|
if not text and backend in ('auto', 'paddle'):
|
|
try:
|
|
from paddleocr import PaddleOCR
|
|
# Try without show_log argument for compatibility
|
|
try:
|
|
ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log=False)
|
|
except TypeError:
|
|
# Older version without show_log
|
|
ocr = PaddleOCR(use_angle_cls=True, lang='en')
|
|
# Convert PIL to numpy for PaddleOCR
|
|
import numpy as np
|
|
image_np = np.array(image)
|
|
result = ocr.ocr(image_np, cls=True)
|
|
if result and result[0]:
|
|
texts = [line[1][0] for line in result[0]]
|
|
text = '\n'.join(texts)
|
|
backend_used = "paddleocr"
|
|
except Exception as e:
|
|
if backend == 'paddle':
|
|
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 _install_easyocr(self):
|
|
"""Install EasyOCR backend."""
|
|
from core.ocr_backend_manager import get_ocr_backend_manager
|
|
manager = get_ocr_backend_manager()
|
|
success, message = manager.install_backend('easyocr')
|
|
if success:
|
|
self.notify_success("Installation Complete", message)
|
|
else:
|
|
self.notify_error("Installation Failed", message)
|
|
self._check_backends()
|
|
|
|
def _install_pytesseract(self):
|
|
"""Install pytesseract Python package."""
|
|
from core.ocr_backend_manager import get_ocr_backend_manager
|
|
manager = get_ocr_backend_manager()
|
|
success, message = manager.install_backend('tesseract')
|
|
if success:
|
|
self.notify_success("Installation Complete", message + "\n\nNote: You also need to install the Tesseract binary from:\nhttps://github.com/UB-Mannheim/tesseract/wiki")
|
|
else:
|
|
self.notify_error("Installation Failed", message)
|
|
self._check_backends()
|
|
|
|
def _install_paddleocr(self):
|
|
"""Install PaddleOCR backend."""
|
|
from core.ocr_backend_manager import get_ocr_backend_manager
|
|
manager = get_ocr_backend_manager()
|
|
success, message = manager.install_backend('paddleocr')
|
|
if success:
|
|
self.notify_success("Installation Complete", message)
|
|
else:
|
|
self.notify_error("Installation Failed", message)
|
|
self._check_backends()
|
|
|
|
def _auto_detect_tesseract(self):
|
|
"""Auto-detect Tesseract from registry/paths."""
|
|
from core.ocr_backend_manager import get_ocr_backend_manager
|
|
manager = get_ocr_backend_manager()
|
|
if manager.auto_configure_tesseract():
|
|
self.notify_success("Tesseract Found", f"Auto-configured Tesseract at:\n{manager.backends['tesseract']['path']}")
|
|
else:
|
|
self.notify_warning("Not Found", "Could not find Tesseract installation.\n\nPlease install from:\nhttps://github.com/UB-Mannheim/tesseract/wiki")
|
|
self._check_backends()
|
|
|
|
def _check_backends(self):
|
|
"""Check OCR backend availability with install buttons."""
|
|
from core.ocr_backend_manager import get_ocr_backend_manager
|
|
|
|
manager = get_ocr_backend_manager()
|
|
statuses = []
|
|
|
|
# EasyOCR
|
|
easyocr_status = manager.get_backend_status('easyocr')
|
|
if easyocr_status['available']:
|
|
statuses.append("✅ EasyOCR - Available (recommended)")
|
|
else:
|
|
statuses.append("❌ EasyOCR - Not installed\n Click 'Install EasyOCR' button below")
|
|
|
|
# Tesseract
|
|
tesseract_status = manager.get_backend_status('tesseract')
|
|
if tesseract_status['available']:
|
|
path_info = f" at {tesseract_status['path']}" if tesseract_status['path'] else ""
|
|
statuses.append(f"✅ Tesseract - Available{path_info}")
|
|
elif tesseract_status['installed']:
|
|
statuses.append("⚠️ Tesseract - Python package installed but binary not found\n Click 'Auto-Detect Tesseract' or install binary from:\n https://github.com/UB-Mannheim/tesseract/wiki")
|
|
else:
|
|
statuses.append("❌ Tesseract - Not installed\n Click 'Install Tesseract Package' then install binary")
|
|
|
|
# PaddleOCR
|
|
paddle_status = manager.get_backend_status('paddleocr')
|
|
if paddle_status['available']:
|
|
statuses.append("✅ PaddleOCR - Available")
|
|
else:
|
|
statuses.append("❌ PaddleOCR - Not installed\n Click 'Install PaddleOCR' button below")
|
|
|
|
statuses.append("\n💡 Recommendation: EasyOCR is easiest (auto-downloads 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()
|