"""Lightweight OCR model using Tesseract + RapidOCR fallback. No PyTorch or heavy ML deps — works on HF free tier. """ import logging import os import shutil from pathlib import Path try: import pytesseract from PIL import Image HAS_TESSERACT = True except ImportError: HAS_TESSERACT = False try: from rapidocr_onnxruntime import RapidOCR HAS_RAPIDOCR = True except ImportError: HAS_RAPIDOCR = False class OCRModel: def __init__(self, tesseract_cmd=""): self._rapidocr_engine = None self._tesseract_path = self._find_tesseract(tesseract_cmd) if HAS_TESSERACT else "" logging.getLogger("RapidOCR").disabled = True def _find_tesseract(self, tesseract_cmd=""): env_path = tesseract_cmd.strip() or os.environ.get("TESSERACT_CMD", "").strip() if env_path and Path(env_path).exists(): return env_path cmd = shutil.which("tesseract") if cmd: return cmd for p in (r"C:\Program Files\Tesseract-OCR\tesseract.exe", r"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe", "/usr/bin/tesseract"): if Path(p).exists(): return p return "" def extract_text(self, image_path): """Extract text from image using Tesseract, falling back to RapidOCR.""" text = self._tesseract_ocr(image_path) if text: return text return self._rapidocr(image_path) def _tesseract_ocr(self, image_path): if not HAS_TESSERACT or not self._tesseract_path: return "" pytesseract.pytesseract.tesseract_cmd = self._tesseract_path try: image = Image.open(image_path) return pytesseract.image_to_string(image).strip() except Exception: return "" def _rapidocr(self, image_path): if not HAS_RAPIDOCR: return "" if self._rapidocr_engine is None: self._rapidocr_engine = RapidOCR() try: result = self._rapidocr_engine(str(image_path)) if result and result[0]: texts = [item[1].strip() for item in result[0] if item[1].strip()] return "\n".join(texts).strip() except Exception: pass return ""