AgenticCoderOfficial's picture
Switch to CPU-only inference (no GPU/ZeroGPU)
bac654f verified
Raw
History Blame Contribute Delete
6.8 kB
import subprocess, sys, os, tempfile
from threading import Thread
from typing import Iterator, Tuple
import queue
import threading
_RUNTIME_PKGS = [
"torch==2.10.0",
"torchvision==0.25.0",
"transformers==4.57.1",
]
print("Installing pinned runtime dependencies...")
subprocess.run(
[sys.executable, "-m", "pip", "install", "--quiet", "--no-cache-dir"] + _RUNTIME_PKGS,
check=True,
)
print("Runtime deps installed.")
import torch
from transformers import AutoModel, AutoTokenizer
import gradio as gr
MODEL_NAME = "baidu/Unlimited-OCR"
print("Loading tokenizer...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
print("Loading model (CPU)...")
model = AutoModel.from_pretrained(
MODEL_NAME,
trust_remote_code=True,
use_safetensors=True,
torch_dtype=torch.float32,
).eval()
print("Model ready (CPU).")
def pdf_to_images(pdf_path: str, dpi: int = 200) -> list[str]:
import fitz
doc = fitz.open(pdf_path)
tmp_dir = tempfile.mkdtemp(prefix="pdf_ocr_")
mat = fitz.Matrix(dpi / 72, dpi / 72)
paths = []
for i, page in enumerate(doc):
out = os.path.join(tmp_dir, f"page_{i + 1:04d}.png")
page.get_pixmap(matrix=mat).save(out)
paths.append(out)
doc.close()
return paths
def _collect_output(out_dir: str) -> str:
result = ""
for fname in sorted(os.listdir(out_dir)):
if fname.endswith((".txt", ".md")):
with open(os.path.join(out_dir, fname), "r", encoding="utf-8") as f:
result += f.read() + "\n"
if not result:
for fname in sorted(os.listdir(out_dir)):
fpath = os.path.join(out_dir, fname)
if os.path.isfile(fpath):
try:
with open(fpath, "r", encoding="utf-8") as f:
result += f.read() + "\n"
except Exception:
pass
return result.strip()
class ThreadTargetedStdout:
def __init__(self, target_thread, q, original_stdout):
self.target_thread = target_thread
self.q = q
self.original_stdout = original_stdout
def write(self, data):
self.original_stdout.write(data)
self.original_stdout.flush()
if threading.current_thread() == self.target_thread:
if data:
lower_data = data.lower()
if "tps:" in lower_data or "tokens/s" in lower_data:
return len(data)
self.q.put(data)
return len(data)
def flush(self):
self.original_stdout.flush()
def __getattr__(self, name):
return getattr(self.original_stdout, name)
def _run_ocr_internal(image_path: str, mode: str, prompt: str) -> str:
out_dir = tempfile.mkdtemp(prefix="ocr_out_")
if mode == "gundam":
base_size, image_size, crop_mode, ngram_window = 1024, 640, True, 128
else:
base_size, image_size, crop_mode, ngram_window = 1024, 1024, False, 128
_infer_kwargs = dict(
prompt=f"<image>{prompt}",
image_file=image_path,
output_path=out_dir,
base_size=base_size,
image_size=image_size,
crop_mode=crop_mode,
max_length=8192,
no_repeat_ngram_size=35,
ngram_window=ngram_window,
save_results=True,
)
q = queue.Queue()
errors = []
def _infer_thread():
try:
model.infer(tokenizer, **_infer_kwargs)
except Exception as e:
errors.append(str(e))
thread = Thread(target=_infer_thread, daemon=True)
original_stdout = sys.stdout
targeted_stdout = ThreadTargetedStdout(thread, q, original_stdout)
sys.stdout = targeted_stdout
accumulated = ""
try:
thread.start()
while thread.is_alive() or not q.empty():
try:
chunk = q.get(timeout=0.02)
accumulated += chunk
except queue.Empty:
continue
finally:
sys.stdout = original_stdout
thread.join()
full_text = _collect_output(out_dir)
if full_text:
return full_text
elif accumulated:
return accumulated
elif errors:
raise RuntimeError(f"OCR failed: {', '.join(errors)}")
return ""
def run_ocr(
image: gr.Image,
mode: str = "gundam",
prompt: str = "document parsing.",
) -> str:
"""
Run OCR on a single image. Returns extracted text.
Args:
image: The image to run OCR on.
mode: 'gundam' for fast mode, 'base' for accurate mode.
prompt: Instruction for the OCR model.
"""
if image is None:
return "Error: No image provided."
tmp_path = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
image.save(tmp_path)
result = _run_ocr_internal(tmp_path, mode, prompt)
os.unlink(tmp_path)
return result
def run_ocr_pdf(
pdf_file: gr.File,
mode: str = "gundam",
prompt: str = "document parsing.",
) -> str:
"""
Run OCR on a PDF document. Converts each page to an image and runs OCR.
Returns concatenated text from all pages.
Args:
pdf_file: The PDF file to run OCR on.
mode: 'gundam' for fast mode, 'base' for accurate mode.
prompt: Instruction for the OCR model.
"""
if pdf_file is None:
return "Error: No PDF file provided."
pages = pdf_to_images(pdf_file, dpi=200)
all_text = []
for i, page_path in enumerate(pages):
text = _run_ocr_internal(page_path, mode, prompt)
all_text.append(f"--- Page {i + 1} ---\n{text}")
os.unlink(page_path)
return "\n\n".join(all_text)
with gr.Blocks(title="Unlimited OCR MCP") as demo:
gr.Markdown("# Unlimited OCR MCP Server")
gr.Markdown("Baidu's Unlimited-OCR model as an MCP tool. Send images or PDFs to extract text.")
with gr.Tab("Image OCR"):
image_input = gr.Image(label="Image", type="pil")
mode_input = gr.Radio(["gundam", "base"], value="gundam", label="Mode")
prompt_input = gr.Textbox(value="document parsing.", label="Prompt")
image_btn = gr.Button("Run OCR")
image_output = gr.Textbox(label="OCR Result", lines=20)
image_btn.click(fn=run_ocr, inputs=[image_input, mode_input, prompt_input], outputs=image_output)
with gr.Tab("PDF OCR"):
pdf_input = gr.File(label="PDF File", file_types=[".pdf"])
pdf_mode = gr.Radio(["gundam", "base"], value="gundam", label="Mode")
pdf_prompt = gr.Textbox(value="document parsing.", label="Prompt")
pdf_btn = gr.Button("Run OCR")
pdf_output = gr.Textbox(label="OCR Result", lines=20)
pdf_btn.click(fn=run_ocr_pdf, inputs=[pdf_input, pdf_mode, pdf_prompt], outputs=pdf_output)
demo.launch(mcp_server=True)