File size: 3,985 Bytes
8c3e275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from __future__ import annotations

import shutil
import subprocess
import tempfile
from pathlib import Path

import cv2
import numpy as np


TESSERACT_CANDIDATES = [
    r"C:\Program Files\Tesseract-OCR\tesseract.exe",
    r"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe",
]


class PrintedOCR:
    def __init__(self, tesseract_cmd: str | None = None) -> None:
        if tesseract_cmd is not None:
            resolved = tesseract_cmd
        else:
            resolved = shutil.which("tesseract")
            if resolved is None:
                for c in TESSERACT_CANDIDATES:
                    if Path(c).exists():
                        resolved = c
                        break
            if resolved is None:
                raise RuntimeError(
                    "Tesseract not found. Install Tesseract OCR from "
                    "https://github.com/UB-Mannheim/tesseract/wiki "
                    "or ensure it is in your PATH."
                )
        self.tesseract_cmd = resolved

    def recognize(self, image: np.ndarray) -> str:
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
            tmp_path = tmp.name
            cv2.imwrite(tmp_path, image)

        try:
            result = subprocess.run(
                [self.tesseract_cmd, tmp_path, "stdout", "-l", "eng"],
                capture_output=True,
                text=True,
                timeout=30,
            )
            if result.returncode != 0:
                raise RuntimeError(
                    f"Tesseract failed (code {result.returncode}): {result.stderr.strip()}"
                )
            return result.stdout.strip()
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def detect_barcodes(self, image: np.ndarray) -> list[dict]:
        results = []
        try:
            from pyzbar.pyzbar import decode as pyzbar_decode
            gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if image.ndim == 3 else image
            barcodes = pyzbar_decode(gray)
            for barcode in barcodes:
                results.append({
                    "data": barcode.data.decode("utf-8"),
                    "type": barcode.type,
                    "rect": {
                        "x": barcode.rect.left,
                        "y": barcode.rect.top,
                        "w": barcode.rect.width,
                        "h": barcode.rect.height,
                    },
                })
        except ImportError:
            pass
        except Exception as e:
            print(f"Barcode detection failed: {e}")
        return results

    def detect_tables(self, image: np.ndarray) -> list[list[str]]:
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if image.ndim == 3 else image
        binary = cv2.adaptiveThreshold(
            ~gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, -2,
        )

        horizontal = binary.copy()
        h_size = horizontal.shape[1] // 30
        h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (h_size, 1))
        horizontal = cv2.erode(horizontal, h_kernel)
        horizontal = cv2.dilate(horizontal, h_kernel)

        vertical = binary.copy()
        v_size = vertical.shape[0] // 30
        v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, v_size))
        vertical = cv2.erode(vertical, v_kernel)
        vertical = cv2.dilate(vertical, v_kernel)

        table_mask = cv2.add(horizontal, vertical)
        contours, _ = cv2.findContours(table_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        tables = []
        for contour in contours:
            x, y, w, h = cv2.boundingRect(contour)
            if w > 50 and h > 50:
                cell = gray[y:y+h, x:x+w]
                try:
                    text = self.recognize(cell)
                    if text.strip():
                        tables.append(text.strip().split("\n"))
                except Exception:
                    pass

        return tables