Download src/endopath/classical_ocr.py from reversely/provinans: direct link, hf CLI and curl.
- Browser
- Download file 3.1 kB
-
https://huggingface.co/spaces/reversely/provinans/resolve/main/src/endopath/classical_ocr.py
- Command line
-
hf download hf://spaces/reversely/provinans/src/endopath/classical_ocr.py
-
curl -L -o classical_ocr.py https://huggingface.co/spaces/reversely/provinans/resolve/main/src/endopath/classical_ocr.py
3.1 kB
| """Classical OCR page reader: a `textsource.PageReader` that transcribes rendered pages on CPU with | |
| Tesseract and makes no model call. | |
| Shared by the two CPU backends: #84 pairs it with rules-only extraction, #85 with a local LLM extractor, | |
| so it is defined once here and reused rather than duplicated. The default engine runs Tesseract through | |
| `pytesseract`, which needs the `tesseract` binary installed on the host. The engine is injectable | |
| (`ocr_page`), so a test passes a fake and a deployment could swap in another classical engine (PaddleOCR) | |
| without touching the reader. | |
| """ | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from typing import Callable, Optional | |
| # The same paragraph spacing reocr.py joins pages with, so the concatenated text reads as one document | |
| # and an evidence quote verifies as a verbatim substring of exactly this stored text (invariant 2). | |
| PAGE_SEPARATOR = "\n\n" | |
| def tesseract_ocr(path: Path) -> str: | |
| """Transcribe one page image with Tesseract. Imports `pytesseract` and Pillow lazily, so importing | |
| this module needs neither the packages nor the tesseract binary until a page is actually read.""" | |
| import pytesseract | |
| from PIL import Image | |
| with Image.open(path) as image: | |
| return pytesseract.image_to_string(image) | |
| class ClassicalPageReader: | |
| """A `textsource.PageReader` that OCRs a report's rendered pages on CPU and joins them into one report | |
| text, with no model call. The CPU backends read pages through this in place of the vision-model | |
| reader. `ocr_page` transcribes one page image and defaults to Tesseract; a test injects a fake.""" | |
| def __init__( | |
| self, | |
| *, | |
| ocr_page: Optional[Callable[[Path], str]] = None, | |
| page_separator: str = PAGE_SEPARATOR, | |
| ) -> None: | |
| self._ocr_page = ocr_page or tesseract_ocr | |
| self._page_separator = page_separator | |
| def read(self, page_images: list[Path]) -> str: | |
| parts: list[str] = [] | |
| for path in page_images: | |
| text = self._ocr_page(Path(path)).strip() | |
| if text: | |
| parts.append(text) | |
| return self._page_separator.join(parts) | |
| def tesseract_word_boxes(path: Path) -> tuple[list[dict], int, int]: | |
| """The words Tesseract reads on a page, each with its pixel box, plus the image size. Lazy imports, | |
| so importing this module needs neither pytesseract nor the tesseract binary until a page is read.""" | |
| import pytesseract | |
| from PIL import Image | |
| from pytesseract import Output | |
| with Image.open(path) as image: | |
| width, height = image.size | |
| data = pytesseract.image_to_data(image, output_type=Output.DICT) | |
| words: list[dict] = [] | |
| for i, text in enumerate(data["text"]): | |
| if text and text.strip(): | |
| words.append( | |
| { | |
| "text": text, | |
| "left": data["left"][i], | |
| "top": data["top"][i], | |
| "width": data["width"][i], | |
| "height": data["height"][i], | |
| } | |
| ) | |
| return words, width, height | |