""" 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()