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:
parent
f6c4971826
commit
f1e2076570
|
|
@ -53,8 +53,36 @@
|
||||||
- Auto-refresh every 5 seconds
|
- Auto-refresh every 5 seconds
|
||||||
- Reads data from all plugin JSON files
|
- 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
|
## Commits Made
|
||||||
- `5b127cf` - UI fixes (drag, scroll, no emojis)
|
- `5b127cf` - UI fixes (drag, scroll, no emojis)
|
||||||
- `72c3c13` - Resizable window, OCR scanners, customizable dashboard
|
- `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
|
## Total Plugins: 21
|
||||||
|
## Core Services: 2 (OCR, Log Reader)
|
||||||
|
|
|
||||||
|
|
@ -130,19 +130,19 @@ class EUUtilityApp:
|
||||||
# Register Log service with API
|
# Register Log service with API
|
||||||
self.api.register_log_service(self.log_reader.read_lines)
|
self.api.register_log_service(self.log_reader.read_lines)
|
||||||
|
|
||||||
# Initialize OCR Service
|
# OCR Service - LAZY INITIALIZATION (don't init on startup)
|
||||||
print("[Core] Initializing OCR Service...")
|
# It will initialize on first use
|
||||||
|
print("[Core] OCR Service configured (lazy init)")
|
||||||
self.ocr_service = get_ocr_service()
|
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
|
# Register OCR service with API (lazy - will init on first call)
|
||||||
self.api.register_ocr_service(self.ocr_service.recognize)
|
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):
|
def _setup_hotkeys(self):
|
||||||
"""Setup global hotkeys."""
|
"""Setup global hotkeys."""
|
||||||
|
|
|
||||||
|
|
@ -27,18 +27,23 @@ class OCRService:
|
||||||
"""
|
"""
|
||||||
Core OCR service with multiple backend support.
|
Core OCR service with multiple backend support.
|
||||||
Fallback chain: EasyOCR -> Tesseract -> PaddleOCR
|
Fallback chain: EasyOCR -> Tesseract -> PaddleOCR
|
||||||
|
LAZY INITIALIZATION - only loads when first used
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._ocr_reader = None
|
self._ocr_reader = None
|
||||||
self._backend = None
|
self._backend = None
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
|
self._initializing = False
|
||||||
# Try backends in order of preference
|
|
||||||
self._init_backends()
|
|
||||||
|
|
||||||
def _init_backends(self):
|
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 EasyOCR first (best accuracy)
|
||||||
try:
|
try:
|
||||||
import easyocr
|
import easyocr
|
||||||
|
|
@ -46,43 +51,63 @@ class OCRService:
|
||||||
self._backend = 'easyocr'
|
self._backend = 'easyocr'
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
print("[OCR] Using EasyOCR backend")
|
print("[OCR] Using EasyOCR backend")
|
||||||
|
self._initializing = False
|
||||||
return
|
return
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[OCR] EasyOCR failed: {e}")
|
||||||
|
|
||||||
# Try Tesseract (most common)
|
# Try Tesseract (most common)
|
||||||
try:
|
try:
|
||||||
import pytesseract
|
import pytesseract
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
pytesseract.get_tesseract_version() # Test if available
|
pytesseract.get_tesseract_version()
|
||||||
self._backend = 'tesseract'
|
self._backend = 'tesseract'
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
print("[OCR] Using Tesseract backend")
|
print("[OCR] Using Tesseract backend")
|
||||||
|
self._initializing = False
|
||||||
return
|
return
|
||||||
except (ImportError, Exception):
|
except Exception as e:
|
||||||
pass
|
print(f"[OCR] Tesseract failed: {e}")
|
||||||
|
|
||||||
# Try PaddleOCR (fallback)
|
# Try PaddleOCR (fallback) - with minimal config
|
||||||
try:
|
try:
|
||||||
from paddleocr import PaddleOCR
|
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(
|
self._ocr_reader = PaddleOCR(
|
||||||
use_angle_cls=True,
|
|
||||||
lang='en',
|
lang='en',
|
||||||
show_log=False,
|
show_log=False,
|
||||||
use_gpu=False
|
use_gpu=False # This param may not work in all versions
|
||||||
)
|
)
|
||||||
self._backend = 'paddle'
|
self._backend = 'paddle'
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
print("[OCR] Using PaddleOCR backend")
|
print("[OCR] Using PaddleOCR backend")
|
||||||
return
|
except TypeError:
|
||||||
except ImportError:
|
# Try without use_gpu if it failed
|
||||||
pass
|
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!")
|
self._initializing = False
|
||||||
print("[OCR] Install one of: easyocr, pytesseract, paddleocr")
|
|
||||||
|
if not self._initialized:
|
||||||
|
print("[OCR] WARNING: No OCR backend available!")
|
||||||
|
print("[OCR] Install one of: easyocr, pytesseract, paddleocr")
|
||||||
|
|
||||||
def is_available(self) -> bool:
|
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
|
return self._initialized
|
||||||
|
|
||||||
def capture_screen(self, region: Tuple[int, int, int, int] = None) -> 'Image.Image':
|
def capture_screen(self, region: Tuple[int, int, int, int] = None) -> 'Image.Image':
|
||||||
|
|
@ -120,6 +145,10 @@ class OCRService:
|
||||||
Returns:
|
Returns:
|
||||||
Dict with 'text', 'confidence', 'results', 'image_size'
|
Dict with 'text', 'confidence', 'results', 'image_size'
|
||||||
"""
|
"""
|
||||||
|
# Lazy initialization
|
||||||
|
if not self._initialized and not self._initializing:
|
||||||
|
self._init_backends()
|
||||||
|
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
return {
|
return {
|
||||||
'text': '',
|
'text': '',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue