"""Standalone Surya OCR worker (runs in the isolated .venv-ocr). Surya 0.22 pulls transformers 5.x + a llama.cpp backend, which conflicts with the embedding stack (transformers 4.x). To keep those concerns decoupled, this worker runs under its OWN interpreter (backend/.venv-ocr/bin/python) and is invoked as a subprocess by app.ocr. It must NOT import the app package. Usage: python ocr_worker.py Writes: {"lines": [{"text": str, "confidence": float}], "raw_text": str} """ import json import re import sys from PIL import Image def _load_images(path: str): if path.lower().endswith(".pdf"): import pypdfium2 as pdfium pdf = pdfium.PdfDocument(path) return [pdf[i].render(scale=2.0).to_pil().convert("RGB") for i in range(len(pdf))] return [Image.open(path).convert("RGB")] def _strip_html(s: str | None) -> str: return re.sub(r"<[^>]+>", " ", s or "").strip() def main() -> None: in_path, out_path = sys.argv[1], sys.argv[2] from surya.recognition import RecognitionPredictor rec = RecognitionPredictor() pages = rec(_load_images(in_path), full_page=True) lines = [] for page in pages: for block in page.blocks: text = _strip_html(getattr(block, "html", None)) if text: conf = getattr(block, "confidence", 1.0) lines.append({"text": text, "confidence": float(conf if conf is not None else 1.0)}) with open(out_path, "w", encoding="utf-8") as f: json.dump({"lines": lines, "raw_text": "\n".join(l["text"] for l in lines)}, f) if __name__ == "__main__": main()