Novoyaz / app.py
ZennyKenny's picture
Update app.py
c9bfe98 verified
import os
import random
import uuid
import json
import time
import asyncio
from threading import Thread
import gradio as gr
import spaces
import torch
import numpy as np
from PIL import Image, ImageOps
# import cv2 # not needed anymore
from transformers import (
Qwen2_5_VLForConditionalGeneration,
AutoProcessor,
TextIteratorStreamer,
)
from transformers.image_utils import load_image
# Optional docling imports (unused now but kept for easy re-enable)
# from docling_core.types.doc import DoclingDocument, DocTagsDocument
import re
import ast
import html
# ---------------------------
# Constants & device
# ---------------------------
MAX_MAX_NEW_TOKENS = 2048
DEFAULT_MAX_NEW_TOKENS = 1024
MAX_INPUT_TOKEN_LENGTH = int(os.getenv("MAX_INPUT_TOKEN_LENGTH", "4096"))
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# ---------------------------
# Load ONLY Typhoon OCR 20B
# ---------------------------
MODEL_ID = "scb10x/typhoon-ocr-20b" # <- 20B model
processor = AutoProcessor.from_pretrained(MODEL_ID, trust_remote_code=True)
model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
MODEL_ID,
trust_remote_code=True,
torch_dtype=torch.float16
).to(device).eval()
# ---------------------------
# (Optional) image helpers
# ---------------------------
def add_random_padding(image, min_percent=0.1, max_percent=0.10):
image = image.convert("RGB")
width, height = image.size
pad_w_percent = random.uniform(min_percent, max_percent)
pad_h_percent = random.uniform(min_percent, max_percent)
pad_w = int(width * pad_w_percent)
pad_h = int(height * pad_h_percent)
corner_pixel = image.getpixel((0, 0))
padded_image = ImageOps.expand(image, border=(pad_w, pad_h, pad_w, pad_h), fill=corner_pixel)
return padded_image
def normalize_values(text, target_max=500):
def normalize_list(values):
max_value = max(values) if values else 1
return [round((v / max_value) * target_max) for v in values]
def process_match(match):
num_list = ast.literal_eval(match.group(0))
normalized = normalize_list(num_list)
return "".join([f"<loc_{num}>" for num in normalized])
pattern = r"\[([\d\.\s,]+)\]"
return re.sub(pattern, process_match, text)
# ---------------------------
# Image generation only
# ---------------------------
@spaces.GPU
def generate_image(
text: str,
image: Image.Image,
max_new_tokens: int = 2048,
temperature: float = 0.1,
top_p: float = 0.9,
top_k: int = 50,
repetition_penalty: float = 1.2,
):
"""Generate OCR/vision response for a single image with Typhoon OCR 20B."""
if image is None:
yield "Please upload an image."
return
images = [image]
messages = [
{
"role": "user",
"content": [{"type": "image"} for _ in images] + [
{"type": "text", "text": text}
]
}
]
prompt = processor.apply_chat_template(messages, add_generation_prompt=True)
inputs = processor(text=prompt, images=images, return_tensors="pt").to(device)
streamer = TextIteratorStreamer(processor, skip_prompt=True, skip_special_tokens=True)
generation_kwargs = {
**inputs,
"streamer": streamer,
"max_new_tokens": max_new_tokens,
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"repetition_penalty": repetition_penalty,
}
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
buffer = ""
for new_text in streamer:
buffer += new_text.replace("<|im_end|>", "")
yield buffer
# ---------------------------
# Minimal UI (Image only)
# ---------------------------
css = """
.submit-btn {
background-color: #2980b9 !important;
color: white !important;
}
.submit-btn:hover {
background-color: #3498db !important;
}
"""
with gr.Blocks(css=css, theme="bethecloud/storj_theme") as demo:
gr.Markdown("# **Typhoon OCR 20B**")
with gr.Row():
with gr.Column():
image_query = gr.Textbox(label="Query Input", placeholder="e.g., \"OCR the image\" or task instruction…")
image_upload = gr.Image(type="pil", label="Image")
image_submit = gr.Button("Submit", elem_classes="submit-btn")
with gr.Accordion("Advanced options", open=False):
max_new_tokens = gr.Slider(label="Max new tokens", minimum=1, maximum=MAX_MAX_NEW_TOKENS, step=1, value=DEFAULT_MAX_NEW_TOKENS)
temperature = gr.Slider(label="Temperature", minimum=0.1, maximum=4.0, step=0.1, value=0.1)
top_p = gr.Slider(label="Top-p (nucleus sampling)", minimum=0.05, maximum=1.0, step=0.05, value=0.9)
top_k = gr.Slider(label="Top-k", minimum=1, maximum=1000, step=1, value=50)
repetition_penalty = gr.Slider(label="Repetition penalty", minimum=1.0, maximum=2.0, step=0.05, value=1.2)
# Right column: ONLY output (no model info, no radios)
with gr.Column():
output = gr.Textbox(label="Output", interactive=False, lines=12, scale=2)
image_submit.click(
fn=generate_image,
inputs=[image_query, image_upload, max_new_tokens, temperature, top_p, top_k, repetition_penalty],
outputs=output
)
if __name__ == "__main__":
demo.queue(max_size=30).launch(share=True, mcp_server=True, ssr_mode=False, show_error=True)