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
This commit is contained in:
LemonNexus 2026-02-13 18:53:36 +00:00
parent f6c4971826
commit f1e2076570
3 changed files with 83 additions and 26 deletions

View File

@ -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)

View File

@ -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."""

View File

@ -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': '',