From f1e207657093fb163dc665eaa83b2d221a9f15e6 Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Fri, 13 Feb 2026 18:53:36 +0000 Subject: [PATCH] fix: OCR lazy initialization to prevent startup crash FIXES: - OCR now uses lazy initialization (only loads when first used) - Fixed PaddleOCR invalid use_gpu parameter (some versions don't support it) - Added fallback try/except for PaddleOCR without use_gpu - App starts immediately, OCR initializes on first scan - Prevents long startup delays from model downloading CHANGES: - OCRService._init_backends() only called on first use - Removed eager initialization from main.py startup - Better error handling for backend failures --- memory/2026-02-13.md | 28 ++++++++++++ projects/EU-Utility/core/main.py | 20 ++++---- projects/EU-Utility/core/ocr_service.py | 61 ++++++++++++++++++------- 3 files changed, 83 insertions(+), 26 deletions(-) diff --git a/memory/2026-02-13.md b/memory/2026-02-13.md index ee3009d..1c77b40 100644 --- a/memory/2026-02-13.md +++ b/memory/2026-02-13.md @@ -53,8 +53,36 @@ - Auto-refresh every 5 seconds - Reads data from all plugin JSON files +### 6. Core Services (NOT Plugins!) +Following user's requirement - OCR and Log are core services, not plugins. + +**Log Reader (`core/log_reader.py`):** +- Real-time chat.log monitoring in background thread +- Event parsing: skill_gains, loot, globals, damage, heals, missions, etc. +- Publisher/subscriber pattern for plugins +- Auto-detects EU log file location +- Cache of recent 1000 lines + +**OCR Service (`core/ocr_service.py`):** +- Multi-backend support: EasyOCR → Tesseract → PaddleOCR +- Auto-fallback if primary backend unavailable +- Screen capture (full or region) +- Returns structured results with bounding boxes +- `quick_ocr()` convenience function + +**PluginAPI Integration:** +- `api.ocr_capture(region)` - All plugins can use OCR +- `api.read_log(lines, filter)` - All plugins can read log +- Services auto-initialized on app startup +- Registered with PluginAPI for universal access + ## Commits Made - `5b127cf` - UI fixes (drag, scroll, no emojis) - `72c3c13` - Resizable window, OCR scanners, customizable dashboard +- `bcd4574` - All plugins disabled by default with enable/disable UI +- `7f6547f` - Fixed box-in-box UI, added Settings button to header +- `8ee0e56` - Fixed window taskbar visibility +- `f6c4971` - Core OCR and Log services with API integration ## Total Plugins: 21 +## Core Services: 2 (OCR, Log Reader) diff --git a/projects/EU-Utility/core/main.py b/projects/EU-Utility/core/main.py index f95f382..f3ef040 100644 --- a/projects/EU-Utility/core/main.py +++ b/projects/EU-Utility/core/main.py @@ -130,19 +130,19 @@ class EUUtilityApp: # Register Log service with API self.api.register_log_service(self.log_reader.read_lines) - # Initialize OCR Service - print("[Core] Initializing OCR Service...") + # OCR Service - LAZY INITIALIZATION (don't init on startup) + # It will initialize on first use + print("[Core] OCR Service configured (lazy init)") self.ocr_service = get_ocr_service() - if self.ocr_service.is_available(): - print(f"[Core] OCR Service ready - using {self.ocr_service._backend}") - else: - print("[Core] OCR Service not available - no backend installed") - print("[Core] Install one of: easyocr, pytesseract, paddleocr") - # Register OCR service with API - self.api.register_ocr_service(self.ocr_service.recognize) + # Register OCR service with API (lazy - will init on first call) + self.api.register_ocr_service(self._lazy_ocr_handler) - print("[Core] API services registered: OCR, Log") + print("[Core] API services registered: OCR (lazy), Log") + + def _lazy_ocr_handler(self, region=None): + """Lazy OCR handler - triggers init on first use.""" + return self.ocr_service.recognize(region=region) def _setup_hotkeys(self): """Setup global hotkeys.""" diff --git a/projects/EU-Utility/core/ocr_service.py b/projects/EU-Utility/core/ocr_service.py index ac917a2..3791cd4 100644 --- a/projects/EU-Utility/core/ocr_service.py +++ b/projects/EU-Utility/core/ocr_service.py @@ -27,18 +27,23 @@ class OCRService: """ Core OCR service with multiple backend support. Fallback chain: EasyOCR -> Tesseract -> PaddleOCR + LAZY INITIALIZATION - only loads when first used """ def __init__(self): self._ocr_reader = None self._backend = None self._initialized = False - - # Try backends in order of preference - self._init_backends() + self._initializing = False def _init_backends(self): - """Initialize available OCR backends.""" + """Initialize available OCR backends (lazy - called on first use).""" + if self._initialized or self._initializing: + return + + self._initializing = True + print("[OCR] Initializing backends...") + # Try EasyOCR first (best accuracy) try: import easyocr @@ -46,43 +51,63 @@ class OCRService: self._backend = 'easyocr' self._initialized = True print("[OCR] Using EasyOCR backend") + self._initializing = False return except ImportError: pass + except Exception as e: + print(f"[OCR] EasyOCR failed: {e}") # Try Tesseract (most common) try: import pytesseract from PIL import Image - pytesseract.get_tesseract_version() # Test if available + pytesseract.get_tesseract_version() self._backend = 'tesseract' self._initialized = True print("[OCR] Using Tesseract backend") + self._initializing = False return - except (ImportError, Exception): - pass + except Exception as e: + print(f"[OCR] Tesseract failed: {e}") - # Try PaddleOCR (fallback) + # Try PaddleOCR (fallback) - with minimal config try: from paddleocr import PaddleOCR + # Use minimal config to avoid model downloads on init + import os + os.environ['PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK'] = 'True' + self._ocr_reader = PaddleOCR( - use_angle_cls=True, lang='en', show_log=False, - use_gpu=False + use_gpu=False # This param may not work in all versions ) self._backend = 'paddle' self._initialized = True print("[OCR] Using PaddleOCR backend") - return - except ImportError: - pass + except TypeError: + # Try without use_gpu if it failed + try: + self._ocr_reader = PaddleOCR(lang='en', show_log=False) + self._backend = 'paddle' + self._initialized = True + print("[OCR] Using PaddleOCR backend (no GPU)") + except Exception as e2: + print(f"[OCR] PaddleOCR failed: {e2}") + except Exception as e: + print(f"[OCR] PaddleOCR failed: {e}") - print("[OCR] WARNING: No OCR backend available!") - print("[OCR] Install one of: easyocr, pytesseract, paddleocr") + self._initializing = False + + if not self._initialized: + print("[OCR] WARNING: No OCR backend available!") + print("[OCR] Install one of: easyocr, pytesseract, paddleocr") def is_available(self) -> bool: - """Check if OCR is available.""" + """Check if OCR is available (lazy init).""" + if not self._initialized and not self._initializing: + self._init_backends() return self._initialized def capture_screen(self, region: Tuple[int, int, int, int] = None) -> 'Image.Image': @@ -120,6 +145,10 @@ class OCRService: Returns: Dict with 'text', 'confidence', 'results', 'image_size' """ + # Lazy initialization + if not self._initialized and not self._initializing: + self._init_backends() + if not self._initialized: return { 'text': '',