| """IndicDocParser -- page image in, reading-ordered Markdown and per-block JSON out. |
| |
| import sys |
| from huggingface_hub import snapshot_download |
| |
| repo = snapshot_download("bodhan-ai/indic-doc-parser") |
| sys.path.insert(0, repo) # the code ships in the repo |
| from indic_doc_parser import IndicDocParser |
| |
| parser = IndicDocParser.from_pretrained(repo) |
| print(parser("page.png")) # markdown |
| |
| ``sys.path.insert`` is needed because ``snapshot_download`` returns a cache directory, which is |
| not importable on its own. With the path added, this is ordinary Python -- no |
| ``trust_remote_code``, and nothing of ours to install. |
| |
| The two stages are also public, and each runs on its own: |
| |
| IndicDocLayout(f"{repo}/weights/layout").detect("page.png") # blocks and reading order |
| IndicBlockOCR(f"{repo}/weights/ocr").run("page.png", layout) # layout may be your own |
| |
| ``layout`` there can be a PageResult, a dict, or the path to a layout JSON file, so the stages |
| compose across processes and you can correct a layout before transcribing it. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import importlib |
| from pathlib import Path |
|
|
|
|
| def _preflight() -> None: |
| """One clear error instead of a cascade of ImportErrors from inside transformers. |
| |
| Every failure here is one someone has actually hit: a torch built for a CUDA line the driver |
| cannot run (silently CPU), a torchvision that does not match its torch, or a transformers |
| older than PPDocLayoutV3. |
| """ |
| fix = "./install.sh (or see the Installation section of the model card)" |
| for name, floor in (("torch", (2, 4)), ("transformers", (5, 7))): |
| try: |
| mod = importlib.import_module(name) |
| except ImportError: |
| raise ImportError(f"IndicDocParser needs {name}. Run: {fix}") from None |
| got = tuple(int(p) for p in mod.__version__.split(".")[:2] if p.isdigit()) |
| if got < floor: |
| raise ImportError( |
| f"IndicDocParser needs {name}>={'.'.join(map(str, floor))}, " |
| f"found {mod.__version__}. Run: {fix}" |
| ) |
| |
| |
|
|
|
|
| _preflight() |
|
|
| |
| |
| from idp_offline import IndicBlockOCR, IndicDocLayout |
| from idp_types import CropConfig, DedupConfig, LayoutConfig, RecognizerConfig, TableFormat |
|
|
| |
| |
| __all__ = ["IndicBlockOCR", "IndicDocLayout", "IndicDocParser"] |
|
|
|
|
| class IndicDocParser: |
| """Both stages. Construct with :meth:`from_pretrained`, then call it on a page image.""" |
|
|
| def __init__( |
| self, |
| path: str | Path, |
| device: str = "cuda", |
| table_format: str = "html", |
| dedup_mode: str = "both", |
| contain: float = 0.90, |
| min_px_side: int = 256, |
| max_new_tokens: int = 2048, |
| ) -> None: |
| self.path = Path(path) |
| self._device = device |
| self._layout_cfg = LayoutConfig(device=device) |
| self._dedup = DedupConfig(mode=dedup_mode, contain=contain) |
| self._crop = CropConfig(min_px_side=min_px_side) |
| self._rec_cfg = RecognizerConfig( |
| max_tokens=max_new_tokens, table_format=TableFormat(table_format) |
| ) |
| |
| self._layout = None |
| self._ocr = None |
|
|
| @classmethod |
| def from_pretrained(cls, path: str | Path | None = None, **kwargs) -> IndicDocParser: |
| """Load from a downloaded snapshot. Defaults to the directory this file lives in, which |
| is the snapshot itself -- so ``IndicDocParser.from_pretrained()`` also works.""" |
| return cls(Path(path) if path else Path(__file__).resolve().parent, **kwargs) |
|
|
| |
|
|
| @property |
| def layout(self): |
| if self._layout is None: |
| self._layout = IndicDocLayout( |
| ckpt=str(self.path / "weights" / "layout"), |
| config=self._layout_cfg, |
| dedup=self._dedup, |
| ) |
| return self._layout |
|
|
| @property |
| def recognizer(self): |
| if self._ocr is None: |
| if importlib.util.find_spec("torchvision") is None: |
| raise ImportError( |
| "The recognizer needs torchvision (its image processor uses it); the layout " |
| "stage does not, so detect() still works. Install it together with torch, " |
| "from the same index -- torchvision pins an exact torch version. See " |
| "./install.sh" |
| ) |
| from idp_recognizer import HfRecognizer |
|
|
| self._ocr = IndicBlockOCR( |
| backend=HfRecognizer( |
| ckpt=str(self.path / "weights" / "ocr"), |
| config=self._rec_cfg, |
| device=self._device, |
| ), |
| config=self._rec_cfg, |
| dedup=self._dedup, |
| crop=self._crop, |
| ) |
| return self._ocr |
|
|
| |
|
|
| def detect(self, image_path: str) -> dict: |
| """Stage 1 only -- blocks, labels, reading order. Loads no recognizer.""" |
| return self.layout.detect(image_path).as_record() |
|
|
| def parse(self, image_path: str) -> dict: |
| """Both stages -> ``{image, width, height, blocks, markdown}``.""" |
| page = self.recognizer.run(image_path, self.layout.detect(image_path)) |
| return {**page.as_record(), "markdown": page.markdown} |
|
|
| def __call__(self, image_path: str) -> str: |
| """The markdown for a page. ``parse()`` if you also want the per-block JSON.""" |
| return self.parse(image_path)["markdown"] |
|
|