ocr: fix PaddleOCR 3.x API + pin surya-ocr to classic local-torch 0.6.4

- PaddleOCR 3.7 dropped use_gpu/use_angle_cls/show_log and raises ValueError for
  unknown ctor args; worker now tries 3.x (device=) then 2.x kwargs and prefers
  .predict() over .ocr(). Verified on a real scanned IT legal page.
- surya-ocr >=~0.15 ('Surya2') rearchitected to a VLM needing an external
  vLLM-in-Docker or llama-server backend (SpawnError in an isolated venv). Pin to
  0.6.4 which runs det+recognition locally on torch, matching the worker's API.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
parent 2be68d4c
...@@ -73,21 +73,44 @@ class PaddleWorker: ...@@ -73,21 +73,44 @@ class PaddleWorker:
def load(self): def load(self):
from paddleocr import PaddleOCR from paddleocr import PaddleOCR
o = self.opts o = self.opts
kw = dict(lang=o.get("lang", "it"), use_angle_cls=True, show_log=False, lang = o.get("lang", "it")
use_gpu=bool(o.get("use_gpu", True))) use_gpu = bool(o.get("use_gpu", True))
dev = "gpu" if use_gpu else "cpu"
extra = {}
if o.get("det_model_dir"): if o.get("det_model_dir"):
kw["det_model_dir"] = o["det_model_dir"] extra["det_model_dir"] = o["det_model_dir"]
if o.get("rec_model_dir"): if o.get("rec_model_dir"):
kw["rec_model_dir"] = o["rec_model_dir"] extra["rec_model_dir"] = o["rec_model_dir"]
self._ocr = _construct_tolerant(PaddleOCR, kw) # PaddleOCR's constructor kwargs changed across 2.x→3.x (use_gpu→device,
if o.get("structure", True): # use_angle_cls→use_textline_orientation, show_log removed). Try newest first,
# peeling to a minimal ctor. It raises ValueError for unknown args (not TypeError).
attempts = [
dict(lang=lang, use_textline_orientation=True, device=dev, **extra), # 3.x
dict(lang=lang, device=dev, **extra), # 3.x minimal
dict(lang=lang, use_angle_cls=True, use_gpu=use_gpu, show_log=False, **extra), # 2.x
dict(lang=lang),
dict(),
]
last = None
for kw in attempts:
try: try:
from paddleocr import PPStructure self._ocr = PaddleOCR(**kw)
self._structure = _construct_tolerant( break
PPStructure, dict(show_log=False, lang=o.get("lang", "it"), except (TypeError, ValueError) as e:
use_gpu=bool(o.get("use_gpu", True)))) last = e
except Exception: if self._ocr is None:
self._structure = None raise last
# PP-Structure moved to PPStructureV3 / paddlex in 3.x; best-effort only.
self._structure = None
if o.get("structure", True):
for modname, cls in (("paddleocr", "PPStructureV3"), ("paddleocr", "PPStructure")):
try:
import importlib
C = getattr(importlib.import_module(modname), cls)
self._structure = C()
break
except Exception:
continue
def ocr(self, img): def ocr(self, img):
import numpy as np import numpy as np
...@@ -111,36 +134,59 @@ class PaddleWorker: ...@@ -111,36 +134,59 @@ class PaddleWorker:
out = [] out = []
ocr = self._ocr ocr = self._ocr
raw = None raw = None
if hasattr(ocr, "ocr"): # 3.x uses .predict() (returns dict-like OCRResult per image); 2.x uses .ocr().
if hasattr(ocr, "predict"):
try:
raw = ocr.predict(bgr)
except Exception:
raw = None
if raw is None and hasattr(ocr, "ocr"):
try: try:
raw = ocr.ocr(bgr, cls=True) raw = ocr.ocr(bgr, cls=True)
except TypeError: except TypeError:
raw = ocr.ocr(bgr) try:
elif hasattr(ocr, "predict"): raw = ocr.ocr(bgr)
raw = ocr.predict(bgr) except Exception:
if not raw: raw = None
return out except Exception:
for page in raw: raw = None
if not page: for page in (raw or []):
if page is None:
continue continue
if isinstance(page, dict): # dict-like result (3.x OCRResult or a plain dict)
texts = page.get("rec_texts") or [] texts = None
polys = page.get("dt_polys") or page.get("rec_polys") or [] try:
scores = page.get("rec_scores") or [] texts = page.get("rec_texts")
except Exception:
texts = None
if texts is not None:
polys = None
for k in ("dt_polys", "rec_polys"):
try:
polys = page.get(k)
except Exception:
polys = None
if polys is not None:
break
try:
scores = page.get("rec_scores") or []
except Exception:
scores = []
for i, t in enumerate(texts): for i, t in enumerate(texts):
box = polys[i] if i < len(polys) else [0, 0, 0, 0] box = polys[i] if (polys is not None and i < len(polys)) else [0, 0, 0, 0]
conf = float(scores[i]) if i < len(scores) else 0.0 conf = float(scores[i]) if i < len(scores) else 0.0
out.append((box, str(t), conf)) out.append((box, str(t), conf))
continue continue
for item in page: # 2.x nested-list result
try: try:
for item in page:
box = item[0]; txt = item[1] box = item[0]; txt = item[1]
if isinstance(txt, (list, tuple)): if isinstance(txt, (list, tuple)):
out.append((box, str(txt[0]), float(txt[1]))) out.append((box, str(txt[0]), float(txt[1])))
else: else:
out.append((box, str(txt), 0.0)) out.append((box, str(txt), 0.0))
except Exception: except Exception:
continue continue
return out return out
def _run_structure(self, bgr): def _run_structure(self, bgr):
......
...@@ -13,5 +13,9 @@ ...@@ -13,5 +13,9 @@
# with coderai's GPLv3); some releases add a revenue-capped commercial clause — check the # with coderai's GPLv3); some releases add a revenue-capped commercial clause — check the
# version you install for commercial use. # version you install for commercial use.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
surya-ocr>=0.6.0 # PIN to a classic (pre-"Surya2") version. surya-ocr >=~0.15 rearchitected to a VLM that
# requires an EXTERNAL vLLM-in-Docker or llama-server backend (SpawnError without them) —
# unusable inside coderai's isolated venv. 0.6.4 runs det+recognition locally on torch,
# matching the RecognitionPredictor(images, langs, det_predictor) API the worker uses.
surya-ocr==0.6.4
pypdfium2>=4.20.0 pypdfium2>=4.20.0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment