NaserNajeh's picture
Update app.py
4dffb27 verified
raw
history blame
5.87 kB
import os, io, json, traceback
import gradio as gr
import fitz # PyMuPDF
from PIL import Image
import spaces # مطلوب ل ZeroGPU
from huggingface_hub import hf_hub_download
# ===== إعدادات النموذج =====
HOROOF_MODEL_NAME = os.environ.get("HOROOF_MODEL", "NaserNajeh/Horoof")
# تحميل كسول لتقليل زمن الإقلاع
_model = None
_tokenizer = None
_image_processor = None
def _load_clean_config(repo_id: str):
"""تحميل config.json وإزالة أي quantization_config لتفادي bitsandbytes."""
from transformers import AutoConfig
try:
cfg_path = hf_hub_download(repo_id=repo_id, filename="config.json")
with open(cfg_path, "r", encoding="utf-8") as f:
cfg_json = json.load(f)
# أزل أي مفاتيح قد تفرض bnb
cfg_json.pop("quantization_config", None)
cfg_json.pop("load_in_4bit", None)
cfg_json.pop("load_in_8bit", None)
return AutoConfig.from_dict(cfg_json)
except Exception:
# احتياطي: لو فشل، خذ الإعدادات الافتراضية
return AutoConfig.from_pretrained(repo_id)
def load_horoof():
"""تحميل نموذج Horoof (Qwen2-VL) على الـGPU عند أول استدعاء فقط، بدون bitsandbytes/torchvision."""
global _model, _tokenizer, _image_processor
if _model is not None:
return
try:
import torch
from transformers import Qwen2VLForConditionalGeneration, AutoTokenizer, AutoImageProcessor
if not torch.cuda.is_available():
raise AssertionError("هذه النسخة تتطلب GPU (CUDA) مفعّل على الـSpace.")
# إعدادات خفيفة: نمنع أي كمّية كي لا يُستدعى bitsandbytes
cfg = _load_clean_config(HOROOF_MODEL_NAME)
_tokenizer = AutoTokenizer.from_pretrained(HOROOF_MODEL_NAME, trust_remote_code=False)
_image_processor = AutoImageProcessor.from_pretrained(HOROOF_MODEL_NAME, trust_remote_code=False)
_model = Qwen2VLForConditionalGeneration.from_pretrained(
HOROOF_MODEL_NAME,
config=cfg,
torch_dtype=torch.float16, # fp16 على الـGPU
).to("cuda")
except Exception as e:
raise RuntimeError(f"تعذّر تحميل النموذج: {e}")
def pdf_to_images(pdf_bytes: bytes, dpi: int = 220, max_pages: int = 0):
"""تحويل PDF إلى قائمة صور PIL."""
pages_imgs = []
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
total = doc.page_count
n_pages = total if (not max_pages or max_pages <= 0) else min(max_pages, total)
for i in range(n_pages):
page = doc.load_page(i)
pix = page.get_pixmap(dpi=dpi, alpha=False)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
pages_imgs.append((i + 1, img))
doc.close()
return pages_imgs
def ocr_page_with_horoof(pil_img: Image.Image, max_new_tokens: int = 1200) -> str:
"""تشغيل Horoof على صورة صفحة واحدة (بدون quantization)."""
load_horoof()
import torch
# رسالة محادثة متوافقة مع Qwen2-VL
messages = [
{
"role": "user",
"content": [
{"type": "image", "image": pil_img},
{"type": "text", "text": "اقرأ النص العربي في الصورة كما هو دون أي تعديل أو تفسير."},
],
}
]
# قالب المحادثة كنص (بدون تقطيع) ثم نقاطّع
prompt = _tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
# مدخلات الصورة + النص
vision_inputs = _image_processor(images=pil_img, return_tensors="pt")
text_inputs = _tokenizer([prompt], return_tensors="pt")
inputs = {**vision_inputs, **text_inputs}
inputs = {k: (v.to("cuda") if hasattr(v, "to") else v) for k, v in inputs.items()}
# توليد
output_ids = _model.generate(**inputs, max_new_tokens=max_new_tokens)
text = _tokenizer.batch_decode(output_ids, skip_special_tokens=True)[0]
return (text or "").strip()
@spaces.GPU # ضروري ل ZeroGPU: يجعل الاستدعاء يحجز GPU
def ocr_pdf(pdf_file, dpi, limit_pages):
"""الدالة الرئيسة التي يستدعيها Gradio."""
if pdf_file is None:
return "لم يتم رفع ملف."
try:
pdf_bytes = pdf_file.read() if hasattr(pdf_file, "read") else pdf_file
limit = int(limit_pages) if limit_pages else 1 # صفحة واحدة افتراضًا للاختبار
pages = pdf_to_images(pdf_bytes, dpi=int(dpi), max_pages=limit)
if not pages:
return "لا توجد صفحات."
out = []
for idx, img in pages:
txt = ocr_page_with_horoof(img)
out.append(f"--- صفحة {idx} ---\n{txt}")
return "\n\n".join(out)
except AssertionError as ae:
return f"⚠️ {ae}"
except Exception as e:
traceback.print_exc()
return f"حدث خطأ: {repr(e)}"
with gr.Blocks(title="Horoof OCR (ZeroGPU)") as demo:
gr.Markdown("### Horoof OCR على ZeroGPU (Qwen2-VL) — بدون bitsandbytes/torchvision.")
pdf_in = gr.File(label="ارفع ملف PDF", file_types=[".pdf"], type="binary")
dpi = gr.Slider(150, 300, value=220, step=10, label="دقة التحويل (DPI)")
limit_pages = gr.Number(value=1, precision=0, label="عدد الصفحات (للاختبار؛ زِد لاحقًا)")
run_btn = gr.Button("بدء التحويل")
out = gr.Textbox(label="النص المستخرج", lines=24)
demo.queue()
run_btn.click(fn=ocr_pdf, inputs=[pdf_in, dpi, limit_pages], outputs=out, api_name="ocr_pdf")
if __name__ == "__main__":
demo.launch()