OCR / app.py
arthur0leywin's picture
Upload app.py
d3e878e verified
Raw
History Blame Contribute Delete
6.7 kB
"""
๐Ÿ”ฑ Imperial OCR Space โ€” GLM-OCR Edition
ู†ู…ูˆุฐุฌ: zai-org/GLM-OCR
ูŠุฏุนู…: Text Recognition / Formula / Table
API ู…ุชูˆุงูู‚ ู…ุน ุงู„ุจูˆุช: /predict(b64_img, lang) โ†’ text
"""
import os
import gc
import base64
import tempfile
from io import BytesIO
from threading import Thread
import gradio as gr
import torch
from PIL import Image, ImageOps
from transformers import (
AutoProcessor,
AutoModelForImageTextToText,
TextIteratorStreamer,
)
# โ”€โ”€โ”€ ุฅุนุฏุงุฏุงุช ุงู„ู†ู…ูˆุฐุฌ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
MODEL_PATH = "zai-org/GLM-OCR"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"๐Ÿ”ฑ OCR Space โ€” ุฌู‡ุงุฒ: {device}", flush=True)
processor = AutoProcessor.from_pretrained(MODEL_PATH, trust_remote_code=True)
model = AutoModelForImageTextToText.from_pretrained(
MODEL_PATH,
torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
device_map="auto",
trust_remote_code=True,
).eval()
# โ”€โ”€โ”€ ุฎุฑูŠุทุฉ ุงู„ู…ู‡ุงู… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
TASK_MAP = {
"text": "Text Recognition:",
"formula": "Formula Recognition:",
"table": "Table Recognition:",
# ุงู„ู„ุบุงุช โ†’ ู†ูุณ ู…ู‡ู…ุฉ Text (GLM-OCR multilingual)
"korean": "Text Recognition:",
"japanese":"Text Recognition:",
"chinese": "Text Recognition:",
"english": "Text Recognition:",
"arabic": "Text Recognition:",
"spanish": "Text Recognition:",
}
MAX_NEW_TOKENS = 4096
# โ”€โ”€โ”€ ุงู„ุฏุงู„ุฉ ุงู„ุฑุฆูŠุณูŠุฉ (ู…ุชูˆุงูู‚ุฉ ู…ุน ุงู„ุจูˆุช ุงู„ู‚ุฏูŠู…) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def extract_text(b64_img: str, lang: str = "korean") -> str:
"""
ุงู„ุฅุฏุฎุงู„ : base64 string ู„ู„ุตูˆุฑุฉ (ู…ุน ุฃูˆ ุจุฏูˆู† data URI header)
ุงู„ุฎุฑูˆุฌ : ุงู„ู†ุต ุงู„ู…ุณุชุฎุฑุฌ ูƒู€ string
lang ูŠู‚ุจู„: korean/japanese/chinese/english/arabic/spanish/text/formula/table
"""
tmp_path = None
try:
# โ”€โ”€ ุชู†ุธูŠู ุงู„ู€ base64 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if "," in b64_img:
b64_img = b64_img.split(",", 1)[1]
img_bytes = base64.b64decode(b64_img)
img = Image.open(BytesIO(img_bytes)).convert("RGB")
img = ImageOps.exif_transpose(img)
# โ”€โ”€ ุญูุธ ู…ุคู‚ุช (GLM-OCR ูŠุญุชุงุฌ path) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:
img.save(tmp.name, "PNG")
tmp_path = tmp.name
# โ”€โ”€ ุงุฎุชูŠุงุฑ ุงู„ู€ prompt โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
prompt = TASK_MAP.get(lang.lower().strip(), "Text Recognition:")
messages = [{
"role": "user",
"content": [
{"type": "image", "url": tmp_path},
{"type": "text", "text": prompt},
],
}]
inputs = processor.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_dict=True,
return_tensors="pt",
)
inputs.pop("token_type_ids", None)
inputs = {k: v.to(model.device) if hasattr(v, "to") else v
for k, v in inputs.items()}
# โ”€โ”€ ุชูˆู„ูŠุฏ ู…ุน Streamer ู„ุชุฌู†ุจ ุงู„ู€ timeout โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
streamer = TextIteratorStreamer(
processor.tokenizer if hasattr(processor, "tokenizer") else processor,
skip_prompt=True,
skip_special_tokens=True,
)
gen_kwargs = {**inputs, "streamer": streamer, "max_new_tokens": MAX_NEW_TOKENS}
gen_error = {"e": None}
def _run():
try:
model.generate(**gen_kwargs)
except Exception as e:
gen_error["e"] = e
try: streamer.end()
except: pass
t = Thread(target=_run, daemon=True)
t.start()
output = ""
for chunk in streamer:
output += chunk
t.join(timeout=2.0)
if gen_error["e"]:
return f"Error: {gen_error['e']}"
return output.strip() if output.strip() else ""
except Exception as e:
return f"Error: {e}"
finally:
if tmp_path and os.path.exists(tmp_path):
try: os.unlink(tmp_path)
except: pass
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
# โ”€โ”€โ”€ ูˆุงุฌู‡ุฉ Gradio (ู„ู„ุงุฎุชุจุงุฑ ุงู„ูŠุฏูˆูŠ + API) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
with gr.Blocks(title="๐Ÿ”ฑ Imperial OCR โ€” GLM") as demo:
gr.Markdown("## ๐Ÿ”ฑ Imperial OCR Space\n**GLM-OCR** โ€” ูŠุฏุนู…: ูƒูˆุฑูŠ / ูŠุงุจุงู†ูŠ / ุตูŠู†ูŠ / ุฅู†ุฌู„ูŠุฒูŠ")
with gr.Row():
with gr.Column():
inp_img = gr.Image(type="pil", label="ุงู„ุตูˆุฑุฉ")
inp_lang = gr.Dropdown(
choices=["korean","japanese","chinese","english","arabic","spanish","formula","table"],
value="korean",
label="ุงู„ู„ุบุฉ / ุงู„ู…ู‡ู…ุฉ"
)
btn = gr.Button("ุงุณุชุฎุฑุงุฌ ุงู„ู†ุต ๐Ÿ”")
with gr.Column():
out_text = gr.Textbox(label="ุงู„ู†ุต ุงู„ู…ุณุชุฎุฑุฌ", lines=20)
# ุฒุฑ ู„ู„ูˆุงุฌู‡ุฉ ุงู„ู…ุฑุฆูŠุฉ ูู‚ุท
def run_ui(img: Image.Image, lang: str) -> str:
if img is None:
return "โŒ ุงุฑูุน ุตูˆุฑุฉ ุฃูˆู„ุงู‹"
buf = BytesIO()
img.save(buf, format="PNG")
b64 = base64.b64encode(buf.getvalue()).decode()
return extract_text(b64, lang)
btn.click(fn=run_ui, inputs=[inp_img, inp_lang], outputs=out_text)
# โ”€โ”€ API ู…ุชูˆุงูู‚ ู…ุน ุงู„ุจูˆุช (ู†ูุณ ุงู„ู€ endpoint ุงู„ู‚ุฏูŠู…) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
gr.Interface(
fn=extract_text,
inputs=[
gr.Textbox(label="b64_img"),
gr.Textbox(label="lang", value="korean"),
],
outputs=gr.Textbox(label="text"),
api_name="predict",
title="API",
description="ู…ุชูˆุงูู‚ ู…ุน ุงู„ุจูˆุช โ€” ูŠู‚ุจู„ base64 ูˆูŠุฑุฌุน ู†ุต",
)
if __name__ == "__main__":
demo.queue(max_size=10).launch(
server_name="0.0.0.0",
server_port=7860,
show_error=True,
)