indic-doc-parser / idp_crops.py
spark-ux's picture
Copy from bodhan-ai/indic-doc-parser
7b2177e verified
Raw
History Blame Contribute Delete
2.27 kB
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/engine/crops.py -- do not edit.
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
# whole install. See indic_doc_parser.py for usage.
"""Turning boxes into the images the recognizer sees.
Split from ``engine.blocks`` because this is the only part that needs PIL -- keeping the
geometry PIL-free is what lets the cleanup rules be tested without an image library.
"""
from __future__ import annotations
import math
from typing import TYPE_CHECKING
from idp_types import Block, CropConfig
from idp_contract import DROP_TYPES, is_transcribed
if TYPE_CHECKING: # pragma: no cover
from PIL.Image import Image
def area_clamp(image: Image, cfg: CropConfig | None = None) -> Image:
"""Scale a crop so its area lands in ``[cfg.min_px, cfg.max_px]``, preserving aspect.
Area rather than a side: pinning a side exploded elongated crops (a 122:1 rule line became
~32k image tokens and wedged the engine).
"""
from PIL import Image as PILImage
cfg = cfg or CropConfig()
width, height = image.size
pixels = width * height
if pixels <= 0:
return image
if pixels < cfg.min_px:
scale = math.sqrt(cfg.min_px / pixels)
elif pixels > cfg.max_px:
scale = math.sqrt(cfg.max_px / pixels)
else:
return image
return image.resize(
(max(1, round(width * scale)), max(1, round(height * scale))), PILImage.LANCZOS
)
def crop_for(block: Block, page: Image, cfg: CropConfig | None = None) -> Image | None:
"""The image for one block, or None if it should not be transcribed."""
if block.type in DROP_TYPES or not is_transcribed(block.label):
return None
cfg = cfg or CropConfig()
width, height = page.size
x0, y0, x1, y1 = (round(v) for v in block.bbox_xyxy)
if cfg.pad_px: # recover glyph edges a tight box clips
x0, y0 = x0 - cfg.pad_px, y0 - cfg.pad_px
x1, y1 = x1 + cfg.pad_px, y1 + cfg.pad_px
x0, y0 = max(0, x0), max(0, y0)
x1, y1 = min(width, x1), min(height, y1)
if x1 <= x0 or y1 <= y0: # rounding can collapse a thin rule to zero width
return None
return page.crop((x0, y0, x1, y1)).convert("RGB")