|
|
""" |
|
|
OCR Engine Factory |
|
|
|
|
|
Provides convenient functions to create and manage OCR engines. |
|
|
Handles fallback logic and singleton management. |
|
|
""" |
|
|
|
|
|
from typing import Optional, Dict |
|
|
from loguru import logger |
|
|
|
|
|
from .base import OCREngine, OCRConfig |
|
|
from .paddle_ocr import PaddleOCREngine, HAS_PADDLEOCR |
|
|
from .tesseract_ocr import TesseractOCREngine, HAS_TESSERACT |
|
|
|
|
|
|
|
|
|
|
|
_ocr_engines: Dict[str, OCREngine] = {} |
|
|
|
|
|
|
|
|
def create_ocr_engine( |
|
|
engine_type: str = "auto", |
|
|
config: Optional[OCRConfig] = None, |
|
|
initialize: bool = True, |
|
|
) -> OCREngine: |
|
|
""" |
|
|
Create an OCR engine instance. |
|
|
|
|
|
Args: |
|
|
engine_type: Engine type: "paddle", "paddleocr", "tesseract", or "auto" |
|
|
config: OCR configuration |
|
|
initialize: Whether to initialize the engine immediately |
|
|
|
|
|
Returns: |
|
|
OCREngine instance |
|
|
|
|
|
Raises: |
|
|
RuntimeError: If no OCR engine is available |
|
|
""" |
|
|
if config is None: |
|
|
config = OCRConfig() |
|
|
|
|
|
|
|
|
if engine_type == "paddleocr": |
|
|
engine_type = "paddle" |
|
|
|
|
|
|
|
|
if engine_type == "auto": |
|
|
if HAS_PADDLEOCR: |
|
|
engine_type = "paddle" |
|
|
logger.info("Auto-selected PaddleOCR engine") |
|
|
elif HAS_TESSERACT: |
|
|
engine_type = "tesseract" |
|
|
logger.info("Auto-selected Tesseract engine") |
|
|
else: |
|
|
raise RuntimeError( |
|
|
"No OCR engine available. Install one of: " |
|
|
"pip install paddleocr paddlepaddle-gpu OR " |
|
|
"pip install pytesseract (+ apt-get install tesseract-ocr)" |
|
|
) |
|
|
|
|
|
|
|
|
if engine_type == "paddle": |
|
|
if not HAS_PADDLEOCR: |
|
|
raise RuntimeError( |
|
|
"PaddleOCR not installed. Install with: " |
|
|
"pip install paddleocr paddlepaddle-gpu" |
|
|
) |
|
|
engine = PaddleOCREngine(config) |
|
|
|
|
|
elif engine_type == "tesseract": |
|
|
if not HAS_TESSERACT: |
|
|
raise RuntimeError( |
|
|
"Tesseract not installed. Install with: " |
|
|
"pip install pytesseract (+ apt-get install tesseract-ocr)" |
|
|
) |
|
|
engine = TesseractOCREngine(config) |
|
|
|
|
|
else: |
|
|
raise ValueError(f"Unknown engine type: {engine_type}") |
|
|
|
|
|
|
|
|
if initialize: |
|
|
engine.initialize() |
|
|
|
|
|
return engine |
|
|
|
|
|
|
|
|
def get_ocr_engine( |
|
|
engine_type: str = "auto", |
|
|
config: Optional[OCRConfig] = None, |
|
|
) -> OCREngine: |
|
|
""" |
|
|
Get or create an OCR engine singleton. |
|
|
|
|
|
Reuses existing engine instances for efficiency. |
|
|
|
|
|
Args: |
|
|
engine_type: Engine type: "paddle", "paddleocr", "tesseract", or "auto" |
|
|
config: OCR configuration (only used for new instances) |
|
|
|
|
|
Returns: |
|
|
OCREngine instance |
|
|
""" |
|
|
global _ocr_engines |
|
|
|
|
|
|
|
|
if engine_type == "paddleocr": |
|
|
engine_type = "paddle" |
|
|
|
|
|
|
|
|
if engine_type == "auto": |
|
|
if HAS_PADDLEOCR: |
|
|
engine_type = "paddle" |
|
|
elif HAS_TESSERACT: |
|
|
engine_type = "tesseract" |
|
|
else: |
|
|
raise RuntimeError("No OCR engine available") |
|
|
|
|
|
|
|
|
if engine_type in _ocr_engines: |
|
|
return _ocr_engines[engine_type] |
|
|
|
|
|
|
|
|
engine = create_ocr_engine(engine_type, config, initialize=True) |
|
|
_ocr_engines[engine_type] = engine |
|
|
|
|
|
return engine |
|
|
|
|
|
|
|
|
def get_available_engines() -> Dict[str, bool]: |
|
|
""" |
|
|
Get availability status of OCR engines. |
|
|
|
|
|
Returns: |
|
|
Dict mapping engine name to availability |
|
|
""" |
|
|
return { |
|
|
"paddle": HAS_PADDLEOCR, |
|
|
"tesseract": HAS_TESSERACT, |
|
|
} |
|
|
|
|
|
|
|
|
def clear_engines(): |
|
|
"""Clear all cached OCR engine instances.""" |
|
|
global _ocr_engines |
|
|
_ocr_engines.clear() |
|
|
logger.debug("Cleared OCR engine cache") |
|
|
|
|
|
|
|
|
class OCREngineManager: |
|
|
""" |
|
|
Context manager for OCR engine lifecycle. |
|
|
|
|
|
Example: |
|
|
with OCREngineManager("paddle") as engine: |
|
|
result = engine.recognize(image) |
|
|
""" |
|
|
|
|
|
def __init__( |
|
|
self, |
|
|
engine_type: str = "auto", |
|
|
config: Optional[OCRConfig] = None, |
|
|
use_singleton: bool = True, |
|
|
): |
|
|
""" |
|
|
Initialize OCR engine manager. |
|
|
|
|
|
Args: |
|
|
engine_type: Engine type |
|
|
config: OCR configuration |
|
|
use_singleton: Whether to use singleton instance |
|
|
""" |
|
|
self.engine_type = engine_type |
|
|
self.config = config |
|
|
self.use_singleton = use_singleton |
|
|
self._engine: Optional[OCREngine] = None |
|
|
self._owned = False |
|
|
|
|
|
def __enter__(self) -> OCREngine: |
|
|
"""Enter context and return engine.""" |
|
|
if self.use_singleton: |
|
|
self._engine = get_ocr_engine(self.engine_type, self.config) |
|
|
self._owned = False |
|
|
else: |
|
|
self._engine = create_ocr_engine(self.engine_type, self.config) |
|
|
self._owned = True |
|
|
|
|
|
return self._engine |
|
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb): |
|
|
"""Exit context.""" |
|
|
|
|
|
if self._owned and self._engine: |
|
|
|
|
|
pass |
|
|
self._engine = None |
|
|
return False |
|
|
|