Image-Text-to-Text
Transformers
Joblib
Safetensors
nemotron_parse
text-generation
document-parsing
ocr
tables
charts
ground-truth-sft
grpo
verifiable-rewards
routed-refinement
thrisul
conversational
custom_code
Instructions to use sandeeprdy1729/thrisul-parser with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use sandeeprdy1729/thrisul-parser with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="sandeeprdy1729/thrisul-parser", trust_remote_code=True) messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("sandeeprdy1729/thrisul-parser", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use sandeeprdy1729/thrisul-parser with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "sandeeprdy1729/thrisul-parser" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "sandeeprdy1729/thrisul-parser", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/sandeeprdy1729/thrisul-parser
- SGLang
How to use sandeeprdy1729/thrisul-parser with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "sandeeprdy1729/thrisul-parser" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "sandeeprdy1729/thrisul-parser", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "sandeeprdy1729/thrisul-parser" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "sandeeprdy1729/thrisul-parser", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use sandeeprdy1729/thrisul-parser with Docker Model Runner:
docker model run hf.co/sandeeprdy1729/thrisul-parser
Download nemotron_parse/engine/geometry.py from sandeeprdy1729/thrisul-parser: direct link, hf CLI and curl.
- Browser
- Download file 13.5 kB
-
https://huggingface.co/sandeeprdy1729/thrisul-parser/resolve/main/nemotron_parse/engine/geometry.py
- Command line
-
hf download hf://sandeeprdy1729/thrisul-parser/nemotron_parse/engine/geometry.py
-
curl -L -o geometry.py https://huggingface.co/sandeeprdy1729/thrisul-parser/resolve/main/nemotron_parse/engine/geometry.py
13.5 kB
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: OpenMDW-1.1 | |
| from __future__ import annotations | |
| import argparse | |
| import base64 | |
| import json | |
| import math | |
| import mimetypes | |
| import re | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Any | |
| import numpy as np | |
| from PIL import Image | |
| BLOCK_RE = re.compile( | |
| r"<x_(\d+(?:\.\d+)?)><y_(\d+(?:\.\d+)?)>(.*?)" | |
| r"<x_(\d+(?:\.\d+)?)><y_(\d+(?:\.\d+)?)><class_([^>]+)>", | |
| re.MULTILINE | re.DOTALL, | |
| ) | |
| class PageImage: | |
| key: str | |
| image_name: str | |
| image_path: Path | |
| width: int | |
| height: int | |
| category: str | None = None | |
| class CropSpec: | |
| crop_id: str | |
| page_key: str | |
| image_name: str | |
| page_image_path: str | |
| crop_image_name: str | |
| crop_image_path: str | |
| element_index: int | |
| detector_label: str | |
| detector_score: float | |
| detector_bbox_norm: tuple[float, float, float, float] | |
| crop_bbox_pixels: tuple[int, int, int, int] | |
| page_size: tuple[int, int] | |
| def write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| with path.open("w", encoding="utf-8") as f: | |
| for row in rows: | |
| f.write(json.dumps(row, ensure_ascii=False) + "\n") | |
| def normalize_detector_label(label: Any, labels: Any = None) -> str: | |
| if isinstance(label, (int, float, np.integer, np.floating)) and labels is not None: | |
| try: | |
| label_idx = int(label) | |
| if isinstance(labels, dict): | |
| label = labels.get(label_idx, labels.get(str(label_idx), label)) | |
| else: | |
| label = labels[label_idx] | |
| except Exception: | |
| pass | |
| text = str(label).strip().lower() | |
| text = ( | |
| text.replace("&", "and").replace("/", "_").replace("-", "_").replace(" ", "_") | |
| ) | |
| text = text.replace("__", "_") | |
| text = re.sub(r"_+", "_", text).strip("_") | |
| if text in { | |
| "headers_and_footers", | |
| "header_and_footer", | |
| "header_footer", | |
| "headers_footers", | |
| }: | |
| return "header_footer" | |
| if text in { | |
| "header", | |
| "footer", | |
| "page_header", | |
| "page_footer", | |
| "header_image", | |
| "footer_image", | |
| }: | |
| return "header_footer" | |
| if text in {"tables"}: | |
| return "table" | |
| if text in {"charts"}: | |
| return "chart" | |
| if text in { | |
| "texts", | |
| "paragraph", | |
| "paragraphs", | |
| "paragraph_title", | |
| "content", | |
| "abstract", | |
| "references", | |
| "reference", | |
| "reference_content", | |
| "footnote", | |
| "page_number", | |
| "number", | |
| "formula", | |
| "formula_number", | |
| "inline_formula", | |
| "display_formula", | |
| "algorithm", | |
| "aside_text", | |
| "sidebar_text", | |
| "list", | |
| "lists", | |
| "vertical_text", | |
| }: | |
| return "text" | |
| if text in { | |
| "title", | |
| "titles", | |
| "document_title", | |
| "section_header", | |
| "figure_title", | |
| "table_title", | |
| "chart_title", | |
| "figure_table_title", | |
| "figure_caption", | |
| "table_caption", | |
| "image_caption", | |
| }: | |
| return "title" | |
| if text in { | |
| "image", | |
| "images", | |
| "figure", | |
| "figures", | |
| "picture", | |
| "pictures", | |
| "seal", | |
| "stamp", | |
| }: | |
| return "infographic" | |
| if text in {"infographics"}: | |
| return "infographic" | |
| return text | |
| def parse_label_filter(value: str) -> set[str]: | |
| if value.strip().lower() in {"", "*", "all"}: | |
| return set() | |
| return {normalize_detector_label(item) for item in value.split(",") if item.strip()} | |
| def clamp(value: float, low: float = 0.0, high: float = 1.0) -> float: | |
| return min(high, max(low, value)) | |
| def crop_bbox_pixels( | |
| bbox_norm: tuple[float, float, float, float], | |
| width: int, | |
| height: int, | |
| *, | |
| margin_px: int, | |
| margin_ratio: float, | |
| ) -> tuple[int, int, int, int]: | |
| x1, y1, x2, y2 = bbox_norm | |
| px1, py1, px2, py2 = x1 * width, y1 * height, x2 * width, y2 * height | |
| mx = margin_px + (px2 - px1) * margin_ratio | |
| my = margin_px + (py2 - py1) * margin_ratio | |
| left = max(0, math.floor(px1 - mx)) | |
| top = max(0, math.floor(py1 - my)) | |
| right = min(width, math.ceil(px2 + mx)) | |
| bottom = min(height, math.ceil(py2 + my)) | |
| return left, top, right, bottom | |
| def filter_detection_rows( | |
| args: argparse.Namespace, rows: list[dict[str, Any]] | |
| ) -> list[dict[str, Any]]: | |
| include_labels = parse_label_filter(args.include_detector_labels) | |
| filtered_rows = [] | |
| for row in rows: | |
| detections = [] | |
| for det in row.get("detections") or []: | |
| label = normalize_detector_label(det.get("label") or "") | |
| if include_labels and label not in include_labels: | |
| continue | |
| if float(det.get("score") or 0.0) < args.detector_score_min: | |
| continue | |
| kept = dict(det) | |
| kept["label"] = label | |
| detections.append(kept) | |
| if args.detector_order == "spatial": | |
| detections.sort( | |
| key=lambda item: ( | |
| item["bbox_norm"][1], | |
| item["bbox_norm"][0], | |
| -float(item.get("score") or 0.0), | |
| ) | |
| ) | |
| if args.max_elements_per_page > 0: | |
| detections = detections[: args.max_elements_per_page] | |
| out = dict(row) | |
| out["detections"] = detections | |
| filtered_rows.append(out) | |
| return filtered_rows | |
| def make_fallback_detection(page: PageImage) -> dict[str, Any]: | |
| return { | |
| "label": "text", | |
| "score": 0.0, | |
| "bbox_norm": [0.0, 0.0, 1.0, 1.0], | |
| "fallback": True, | |
| } | |
| def build_crops( | |
| args: argparse.Namespace, | |
| pages: list[PageImage], | |
| detections: list[dict[str, Any]], | |
| crops_dir: Path, | |
| crops_manifest_path: Path, | |
| ) -> list[CropSpec]: | |
| pages_by_name = {page.image_name: page for page in pages} | |
| pages_by_key = {page.key: page for page in pages} | |
| crops: list[CropSpec] = [] | |
| for row in detections: | |
| page = pages_by_key.get(str(row.get("key"))) or pages_by_name.get( | |
| str(row.get("image_name")) | |
| ) | |
| if page is None: | |
| continue | |
| items = list(row.get("detections") or []) | |
| if not items and args.fallback_full_page: | |
| items = [make_fallback_detection(page)] | |
| if not items: | |
| continue | |
| with Image.open(page.image_path) as image: | |
| image = image.convert("RGB") | |
| for element_index, det in enumerate(items): | |
| bbox_norm = tuple(float(v) for v in det["bbox_norm"]) | |
| crop_px = crop_bbox_pixels( | |
| bbox_norm, | |
| page.width, | |
| page.height, | |
| margin_px=args.crop_margin_px, | |
| margin_ratio=args.crop_margin_ratio, | |
| ) | |
| left, top, right, bottom = crop_px | |
| if ( | |
| right - left < args.min_crop_size | |
| or bottom - top < args.min_crop_size | |
| ): | |
| continue | |
| crop_image_name = f"{page.key}__pe_{element_index:03d}.jpg" | |
| crop_image_path = crops_dir / crop_image_name | |
| crop_image_path.parent.mkdir(parents=True, exist_ok=True) | |
| if not crop_image_path.is_file() or crop_image_path.stat().st_size == 0: | |
| image.crop((left, top, right, bottom)).save( | |
| crop_image_path, quality=95 | |
| ) | |
| crops.append( | |
| CropSpec( | |
| crop_id=f"{page.key}__pe_{element_index:03d}", | |
| page_key=page.key, | |
| image_name=page.image_name, | |
| page_image_path=str(page.image_path), | |
| crop_image_name=crop_image_name, | |
| crop_image_path=str(crop_image_path), | |
| element_index=element_index, | |
| detector_label=str(det.get("label") or "text"), | |
| detector_score=float(det.get("score") or 0.0), | |
| detector_bbox_norm=bbox_norm, | |
| crop_bbox_pixels=crop_px, | |
| page_size=(page.width, page.height), | |
| ) | |
| ) | |
| write_jsonl(crops_manifest_path, [crop.__dict__ for crop in crops]) | |
| print(f"Prepared {len(crops)} crops under {crops_dir}", flush=True) | |
| return crops | |
| def image_data_url(path: Path) -> str: | |
| mime = mimetypes.guess_type(path.name)[0] or "image/jpeg" | |
| data = base64.b64encode(path.read_bytes()).decode("ascii") | |
| return f"data:{mime};base64,{data}" | |
| def parse_model_detection_predictions( | |
| args: argparse.Namespace, | |
| pages: list[PageImage], | |
| prediction_rows: list[dict[str, Any]], | |
| ) -> list[dict[str, Any]]: | |
| page_by_key = {page.key: page for page in pages} | |
| rows = [] | |
| for prediction in prediction_rows: | |
| page = page_by_key.get(str(prediction.get("key"))) | |
| if page is None: | |
| continue | |
| detections = [] | |
| for match in BLOCK_RE.finditer(str(prediction.get("raw_out") or "")): | |
| x1, y1, _body, x2, y2, cls = match.groups() | |
| bbox_norm = model_bbox_to_image_norm( | |
| (float(x1), float(y1), float(x2), float(y2)), | |
| page.width, | |
| page.height, | |
| coordinate_frame=args.model_coordinate_frame, | |
| canvas_width=args.canvas_width, | |
| canvas_height=args.canvas_height, | |
| pad_position=args.pad_position, | |
| ) | |
| if bbox_norm[2] <= bbox_norm[0] or bbox_norm[3] <= bbox_norm[1]: | |
| continue | |
| detections.append( | |
| { | |
| "label": normalize_detector_label(cls), | |
| "score": 1.0, | |
| "bbox_norm": list(bbox_norm), | |
| "model_class": cls, | |
| "model_order": len(detections), | |
| } | |
| ) | |
| rows.append( | |
| { | |
| "key": page.key, | |
| "image_name": page.image_name, | |
| "image_path": str(page.image_path), | |
| "page_size": [page.width, page.height], | |
| "detector_backend": "model-detection", | |
| "model_coordinate_frame": args.model_coordinate_frame, | |
| "detections": detections, | |
| "detection_error": prediction.get("error"), | |
| } | |
| ) | |
| return filter_detection_rows(args, rows) | |
| def model_canvas_norm_to_image_norm( | |
| bbox: tuple[float, float, float, float], | |
| width: int, | |
| height: int, | |
| *, | |
| canvas_width: float, | |
| canvas_height: float, | |
| pad_position: str, | |
| ) -> tuple[float, float, float, float]: | |
| x1, y1, x2, y2 = bbox | |
| max_coord = max(abs(x1), abs(y1), abs(x2), abs(y2)) | |
| if max_coord > 1.5 and max_coord <= 1000.0: | |
| norm = (x1 / 1000.0, y1 / 1000.0, x2 / 1000.0, y2 / 1000.0) | |
| elif max_coord > 1000.0: | |
| norm = ( | |
| x1 / max(1, width), | |
| y1 / max(1, height), | |
| x2 / max(1, width), | |
| y2 / max(1, height), | |
| ) | |
| else: | |
| norm = (x1, y1, x2, y2) | |
| scale = min(1.0, canvas_width / max(1, width), canvas_height / max(1, height)) | |
| resized_width = width * scale | |
| resized_height = height * scale | |
| if pad_position == "top-left": | |
| pad_x = 0.0 | |
| pad_y = 0.0 | |
| elif pad_position == "bottom-right": | |
| pad_x = canvas_width - resized_width | |
| pad_y = canvas_height - resized_height | |
| else: | |
| pad_x = (canvas_width - resized_width) / 2.0 | |
| pad_y = (canvas_height - resized_height) / 2.0 | |
| out = ( | |
| (norm[0] * canvas_width - pad_x) / max(1.0, resized_width), | |
| (norm[1] * canvas_height - pad_y) / max(1.0, resized_height), | |
| (norm[2] * canvas_width - pad_x) / max(1.0, resized_width), | |
| (norm[3] * canvas_height - pad_y) / max(1.0, resized_height), | |
| ) | |
| left, right = sorted((clamp(out[0]), clamp(out[2]))) | |
| top, bottom = sorted((clamp(out[1]), clamp(out[3]))) | |
| return left, top, right, bottom | |
| def model_bbox_to_image_norm( | |
| bbox: tuple[float, float, float, float], | |
| width: int, | |
| height: int, | |
| *, | |
| coordinate_frame: str, | |
| canvas_width: float, | |
| canvas_height: float, | |
| pad_position: str, | |
| ) -> tuple[float, float, float, float]: | |
| if coordinate_frame in { | |
| "content_normalized", | |
| "original_normalized", | |
| "navit_content", | |
| }: | |
| x1, y1, x2, y2 = bbox | |
| max_coord = max(abs(x1), abs(y1), abs(x2), abs(y2)) | |
| if max_coord > 1.5 and max_coord <= 1000.0: | |
| x1, y1, x2, y2 = (value / 1000.0 for value in (x1, y1, x2, y2)) | |
| elif max_coord > 1000.0: | |
| x1, x2 = x1 / max(1, width), x2 / max(1, width) | |
| y1, y2 = y1 / max(1, height), y2 / max(1, height) | |
| left, right = sorted((clamp(x1), clamp(x2))) | |
| top, bottom = sorted((clamp(y1), clamp(y2))) | |
| return left, top, right, bottom | |
| return model_canvas_norm_to_image_norm( | |
| bbox, | |
| width, | |
| height, | |
| canvas_width=canvas_width, | |
| canvas_height=canvas_height, | |
| pad_position=pad_position, | |
| ) | |