Image-Text-to-Text
Transformers
Safetensors
qwen3_5
text-generation
ocr
document-ai
indic-ocr
medical-ocr
handwriting-recognition
table-recognition
latex-ocr
mixture-of-experts
branch-train-mix
conversational
Instructions to use dipankar500/NetraOCR-1.0 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use dipankar500/NetraOCR-1.0 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="dipankar500/NetraOCR-1.0") 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 AutoProcessor, AutoModelForSeq2SeqLM processor = AutoProcessor.from_pretrained("dipankar500/NetraOCR-1.0") model = AutoModelForSeq2SeqLM.from_pretrained("dipankar500/NetraOCR-1.0", device_map="auto") 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?"} ] }, ] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use dipankar500/NetraOCR-1.0 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "dipankar500/NetraOCR-1.0" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "dipankar500/NetraOCR-1.0", "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/dipankar500/NetraOCR-1.0
- SGLang
How to use dipankar500/NetraOCR-1.0 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 "dipankar500/NetraOCR-1.0" \ --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": "dipankar500/NetraOCR-1.0", "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 "dipankar500/NetraOCR-1.0" \ --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": "dipankar500/NetraOCR-1.0", "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 dipankar500/NetraOCR-1.0 with Docker Model Runner:
docker model run hf.co/dipankar500/NetraOCR-1.0
Download processing_netra.py from dipankar500/NetraOCR-1.0: direct link, hf CLI and curl.
- Browser
- Download file 6.71 kB
-
https://huggingface.co/dipankar500/NetraOCR-1.0/resolve/main/processing_netra.py
- Command line
-
hf download hf://dipankar500/NetraOCR-1.0/processing_netra.py
-
curl -L -o processing_netra.py https://huggingface.co/dipankar500/NetraOCR-1.0/resolve/main/processing_netra.py
6.71 kB
| """NetraOCR processor: Qwen2VL image processing + the NetraOCR tokenizer + dual-vision placeholder expansion. | |
| Each image placeholder `<|image_pad|>` in the chat-templated prompt is expanded to | |
| `frames * (t*h*w / merge²)` tokens (frames = 2 in dual-vision mode). `mm_token_type_ids` marks them, | |
| which the model needs for its 3D rotary positions. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import torch | |
| from transformers import AutoImageProcessor, PreTrainedTokenizerFast | |
| class NetraProcessor: | |
| def __init__(self, tokenizer, image_processor, frames: int = 2): | |
| self.tokenizer = tokenizer | |
| self.image_processor = image_processor | |
| self.frames = frames | |
| self.image_token_id = tokenizer.convert_tokens_to_ids("<|image_pad|>") | |
| def from_pretrained(cls, path: str, frames: int = 2) -> "NetraProcessor": | |
| # Load tokenizer.json verbatim. AutoTokenizer may rebuild a model-specific class and drop or | |
| # alter the appended Indic tokens (the same failure mode found with the Sarvam tokenizer). | |
| cfg = json.load(open(os.path.join(path, "tokenizer_config.json"), encoding="utf-8")) | |
| tok = PreTrainedTokenizerFast( | |
| tokenizer_file=os.path.join(path, "tokenizer.json"), | |
| eos_token=cfg.get("eos_token", "<|im_end|>"), | |
| pad_token=cfg.get("pad_token", "<|endoftext|>"), | |
| ) | |
| tok.padding_side = "left" | |
| tmpl = os.path.join(path, "chat_template.jinja") | |
| if os.path.exists(tmpl): | |
| tok.chat_template = open(tmpl, encoding="utf-8").read() | |
| return cls(tok, AutoImageProcessor.from_pretrained(path), frames) | |
| def prompt(self, instruction: str, n_images: int = 1) -> str: | |
| content = [{"type": "image"}] * n_images + [{"type": "text", "text": instruction}] | |
| return self.tokenizer.apply_chat_template([{"role": "user", "content": content}], add_generation_prompt=True, | |
| tokenize=False, enable_thinking=False) | |
| def __call__(self, texts: list[str], images: list, images_kwargs: dict | None = None, | |
| targets: list[str] | None = None) -> dict: | |
| img = self.image_processor(images=images, return_tensors="pt", **(images_kwargs or {})) | |
| grid = img["image_grid_thw"] | |
| merge = self.image_processor.merge_size | |
| counts = (grid.prod(-1) // merge**2 * self.frames).tolist() | |
| rows, label_rows, k = [], [], 0 | |
| for i, text in enumerate(texts): | |
| ids = self.tokenizer(text, add_special_tokens=False)["input_ids"] | |
| expanded = [] | |
| for t in ids: | |
| if t == self.image_token_id: | |
| expanded.extend([self.image_token_id] * counts[k]) | |
| k += 1 | |
| else: | |
| expanded.append(t) | |
| labels = [-100] * len(expanded) | |
| if targets is not None: | |
| tgt = self.tokenizer(targets[i], add_special_tokens=False)["input_ids"] + [self.tokenizer.eos_token_id] | |
| expanded = expanded + tgt | |
| labels = labels + tgt | |
| rows.append(expanded) | |
| label_rows.append(labels) | |
| if k != len(counts): | |
| raise ValueError(f"{len(counts)} images but {k} <|image_pad|> placeholders in the prompts") | |
| width = max(len(r) for r in rows) | |
| pad = self.tokenizer.pad_token_id | |
| input_ids = torch.full((len(rows), width), pad, dtype=torch.long) | |
| attention = torch.zeros((len(rows), width), dtype=torch.long) | |
| labels = torch.full((len(rows), width), -100, dtype=torch.long) | |
| for i, (r, lab) in enumerate(zip(rows, label_rows)): # left padding | |
| input_ids[i, width - len(r):] = torch.tensor(r) | |
| attention[i, width - len(r):] = 1 | |
| labels[i, width - len(r):] = torch.tensor(lab) | |
| out = { | |
| "input_ids": input_ids, | |
| "attention_mask": attention, | |
| "mm_token_type_ids": (input_ids == self.image_token_id).int() * attention.int(), | |
| "pixel_values": img["pixel_values"], | |
| "image_grid_thw": grid, | |
| } | |
| if targets is not None: | |
| out["labels"] = labels | |
| return out | |
| def decode(self, ids) -> str: | |
| return self.tokenizer.decode(ids, skip_special_tokens=True) | |
| def split_page_regions(img, max_regions: int = 10, min_band_px: int = 140) -> list: | |
| """Split a dense full-page document/ledger image into horizontal text-band crops using ink projection valleys. | |
| Each band is Tight-cropped horizontally around ink and ready for `area_clamp`, giving small handwritten or | |
| multi-column ledger lines 4-8x more pixels per character than single-pass whole-page downsampling. | |
| """ | |
| import numpy as np | |
| from PIL import Image | |
| rgb = img.convert("RGB") | |
| w, h = rgb.size | |
| if h < min_band_px * 2: | |
| return [rgb] | |
| gray = np.asarray(rgb.convert("L"), dtype=np.float32) | |
| bg = float(np.percentile(gray, 85)) | |
| ink = (bg - gray) > max(18.0, 0.12 * bg) | |
| row_ink = ink.mean(axis=1) | |
| # Smooth row ink profile with a 9-px box filter to locate inter-line valleys | |
| kernel = np.ones(9, dtype=np.float32) / 9.0 | |
| smooth = np.convolve(row_ink, kernel, mode="same") | |
| active = np.where(smooth > 0.008)[0] | |
| if len(active) < 20: | |
| return [rgb] | |
| y_top, y_bot = max(0, int(active[0]) - 12), min(h, int(active[-1]) + 12) | |
| span = y_bot - y_top | |
| if span < min_band_px * 2: | |
| return [rgb.crop((0, y_top, w, y_bot))] | |
| target_bands = min(max_regions, max(2, int(round(span / max(min_band_px, span / max_regions))))) | |
| step = span / target_bands | |
| cuts = [y_top] | |
| for b_idx in range(1, target_bands): | |
| nominal = int(y_top + b_idx * step) | |
| win_lo = max(cuts[-1] + min_band_px // 2, nominal - int(step * 0.35)) | |
| win_hi = min(y_bot - min_band_px // 2, nominal + int(step * 0.35)) | |
| if win_hi > win_lo: | |
| valley = win_lo + int(np.argmin(smooth[win_lo:win_hi])) | |
| else: | |
| valley = nominal | |
| cuts.append(valley) | |
| cuts.append(y_bot) | |
| crops = [] | |
| for y0, y1 in zip(cuts[:-1], cuts[1:]): | |
| pad0, pad1 = max(0, y0 - 8), min(h, y1 + 8) | |
| band_ink = ink[pad0:pad1] | |
| if band_ink.mean() < 0.004: | |
| continue | |
| col_active = np.where(band_ink.mean(axis=0) > 0.003)[0] | |
| if len(col_active) >= 10: | |
| x0 = max(0, int(col_active[0]) - 12) | |
| x1 = min(w, int(col_active[-1]) + 12) | |
| else: | |
| x0, x1 = 0, w | |
| if (pad1 - pad0) >= 24 and (x1 - x0) >= 48: | |
| crops.append(rgb.crop((x0, pad0, x1, pad1))) | |
| return crops if crops else [rgb] | |