File size: 2,044 Bytes
af24ae8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
"""OCR engine abstraction with EasyOCR backend for Persian/English invoices."""

from __future__ import annotations

import logging
from io import BytesIO
from pathlib import Path
from typing import TYPE_CHECKING

import numpy as np
from PIL import Image

if TYPE_CHECKING:
    from numpy.typing import NDArray

logger = logging.getLogger(__name__)

_reader = None


def _get_reader():
    global _reader
    if _reader is None:
        import easyocr

        logger.info("Initializing EasyOCR reader (fa + en)...")
        _reader = easyocr.Reader(["fa", "en"], gpu=False, verbose=False)
    return _reader


def load_image(source: str | Path | bytes | Image.Image) -> Image.Image:
    if isinstance(source, Image.Image):
        return source.convert("RGB")
    if isinstance(source, bytes):
        return Image.open(BytesIO(source)).convert("RGB")
    path = Path(source)
    if path.suffix.lower() == ".pdf":
        from pdf2image import convert_from_path

        pages = convert_from_path(str(path), dpi=200, first_page=1, last_page=1)
        return pages[0].convert("RGB")
    return Image.open(path).convert("RGB")


def image_to_array(image: Image.Image) -> "NDArray[np.uint8]":
    return np.array(image)


def extract_text(image: Image.Image) -> tuple[str, list[dict]]:
    """Run OCR and return full text plus structured bounding-box results."""
    reader = _get_reader()
    arr = image_to_array(image)
    results = reader.readtext(arr, detail=1, paragraph=False)

    lines: list[str] = []
    structured: list[dict] = []
    for bbox, text, confidence in results:
        cleaned = text.strip()
        if not cleaned:
            continue
        lines.append(cleaned)
        structured.append(
            {
                "text": cleaned,
                "confidence": float(confidence),
                "bbox": [[float(p[0]), float(p[1])] for p in bbox],
            }
        )

    full_text = "\n".join(lines)
    return full_text, structured