OCR engine using PaddleOCR; often better for mixed Chinese/English than Tesseract. Requires: paddleocr, paddlepaddle (or paddlepaddle-gpu).
| 23 | |
| 24 | |
| 25 | class PaddleOCRAdapter: |
| 26 | """ |
| 27 | OCR engine using PaddleOCR; often better for mixed Chinese/English than Tesseract. |
| 28 | Requires: paddleocr, paddlepaddle (or paddlepaddle-gpu). |
| 29 | """ |
| 30 | |
| 31 | def __init__(self, use_angle_cls: bool = True, lang: str = "ch"): |
| 32 | if PaddleOCR is None: |
| 33 | raise ImportError( |
| 34 | "Install PaddleOCR: pip install paddleocr paddlepaddle (or paddlepaddle-gpu)" |
| 35 | ) |
| 36 | try: |
| 37 | self._engine = PaddleOCR(use_angle_cls=use_angle_cls, lang=lang) |
| 38 | except AttributeError as e: |
| 39 | if "set_optimization_level" in str(e): |
| 40 | raise RuntimeError( |
| 41 | "PaddleOCR incompatible with this PaddlePaddle (missing set_optimization_level).\n" |
| 42 | "Install PaddlePaddle 3.x and PaddleOCR 3.x:\n" |
| 43 | " pip uninstall paddleocr paddlepaddle paddlepaddle-gpu paddlex -y\n" |
| 44 | " pip install \"paddlepaddle>=3.0\" paddleocr # CPU\n" |
| 45 | " # GPU: pip install paddlepaddle-gpu paddleocr\n" |
| 46 | "See README Optional PaddleOCR section." |
| 47 | ) from e |
| 48 | raise |
| 49 | |
| 50 | def _parse_result(self, result: Any) -> List[TextBlock]: |
| 51 | """Parse PaddleOCR 2.x or 3.x result into list of TextBlock.""" |
| 52 | text_blocks: List[TextBlock] = [] |
| 53 | |
| 54 | if not result: |
| 55 | return text_blocks |
| 56 | |
| 57 | # Normalize to list (single image may return one object or dict key 0) |
| 58 | if not isinstance(result, list): |
| 59 | if isinstance(result, dict): |
| 60 | first_val = result.get(0) or (list(result.values())[0] if result else None) |
| 61 | if first_val is None: |
| 62 | return text_blocks |
| 63 | result = [first_val] |
| 64 | else: |
| 65 | result = [result] |
| 66 | |
| 67 | # PaddleOCR 3.x: list of PaddleX OCRResult (dict-like: rec_polys, rec_texts, rec_scores) |
| 68 | if isinstance(result, list) and len(result) > 0: |
| 69 | first = result[0] |
| 70 | get = getattr(first, "get", None) if not isinstance(first, dict) else first.get |
| 71 | if get is not None and callable(get): |
| 72 | rec_polys = get("rec_polys") or get("dt_polys") or [] |
| 73 | rec_texts = get("rec_texts") or [] |
| 74 | rec_scores = get("rec_scores") or [] |
| 75 | if isinstance(rec_texts, (list, tuple)) and ( |
| 76 | isinstance(rec_polys, (list, tuple)) |
| 77 | or (hasattr(rec_polys, "__iter__") and not isinstance(rec_polys, (str, bytes))) |
| 78 | ): |
| 79 | for i, poly in enumerate(rec_polys): |
| 80 | text = (rec_texts[i] if i < len(rec_texts) else "") |
| 81 | if isinstance(text, (list, tuple)): |
| 82 | text = (text[0] or "") if text else "" |