MIC / app.py
WafaaFraih's picture
Update app.py
8764b02 verified
Raw
History Blame Contribute Delete
4.77 kB
"""
Medical Image Captioning — Qwen2.5-VL-7B + LoRA (correctness-focused checkpoint)
عشان ده يشتغل على HF Spaces مجانًا، بيستخدم ZeroGPU (GPU مشترك بيتاجَّر
بس وقت التنفيذ الفعلي). لازم الـ Space نفسه يتظبط بـ SDK: Gradio +
Hardware: ZeroGPU (مجاني، بس محتاج تفعيله من إعدادات الـ Space).
"""
import os
import spaces
import torch
import gradio as gr
from PIL import Image
from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
from peft import PeftModel
HF_TOKEN = os.environ.get("HF_TOKEN") # اتحطت كـ Secret في إعدادات الـ Space
BASE_MODEL_ID = "Qwen/Qwen2.5-VL-7B-Instruct"
ADAPTER_ID = "WafaaFraih/qwen25vl_correctness_checkpoint"
PROMPTS = {
"Generic": "Describe this image.",
"Clinical": (
"You are a medical imaging expert. Describe this medical image "
"with precise clinical terminology. Be concise and factual."
),
"Chain-of-thought": (
"You are a medical imaging expert. "
"Step 1: Identify the imaging modality. "
"Step 2: Identify the anatomical region. "
"Step 3: Describe the key findings. "
"Now write one concise clinical caption."
),
}
# ⚠️ ZeroGPU: مكتبة `spaces` بتعترض حتى عمليات تحميل الـ safetensors
# العادية لو حصلت برّه دالة معلَّمة بـ @spaces.GPU. عشان كده لازم
# التحميل كله (base model + adapter) يبقى جوه الدالة، مش هنا.
processor = None
model = None
def _ensure_loaded():
"""يحمّل الموديل والـ adapter مرة واحدة بس، أول ما حد يستخدم الـ demo."""
global processor, model
if model is not None:
return
print("▶ Loading base model + LoRA adapter (first request only)...")
processor = AutoProcessor.from_pretrained(BASE_MODEL_ID)
base_model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
BASE_MODEL_ID,
torch_dtype=torch.float16,
)
loaded = PeftModel.from_pretrained(base_model, ADAPTER_ID, token=HF_TOKEN)
loaded.eval()
loaded.to("cuda")
model = loaded
print("✅ Model ready on GPU.")
@spaces.GPU(duration=120)
def caption_image(image: Image.Image, prompt_style: str):
_ensure_loaded()
if image is None:
return "⚠️ Please upload a medical image first."
image = image.convert("RGB")
prompt_text = PROMPTS[prompt_style]
messages = [
{
"role": "user",
"content": [
{"type": "image"},
{"type": "text", "text": prompt_text},
],
}
]
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = processor(text=[text], images=[image], return_tensors="pt").to("cuda")
with torch.no_grad():
output_ids = model.generate(
**inputs,
max_new_tokens=128,
do_sample=False,
)
generated = processor.batch_decode(
output_ids[:, inputs["input_ids"].shape[1]:],
skip_special_tokens=True,
)[0].strip()
return generated
with gr.Blocks(title="Medical Image Captioning — Qwen2.5-VL (LoRA fine-tuned)") as demo:
gr.Markdown(
"""
# 🩺 Medical Image Captioning
**Qwen2.5-VL-7B**, LoRA fine-tuned on ROCO + MultiCare with a
correctness-focused training recipe (see the accompanying paper
for the full evaluation, including known limitations).
⚠️ **Research demo only.** Captions are automatically generated
and are **not validated for clinical or diagnostic use**. Do not
use this tool to inform real medical decisions.
"""
)
with gr.Row():
with gr.Column():
image_input = gr.Image(type="pil", label="Upload a medical image")
prompt_choice = gr.Radio(
choices=list(PROMPTS.keys()),
value="Clinical",
label="Prompt style",
)
run_button = gr.Button("Generate caption", variant="primary")
with gr.Column():
output_text = gr.Textbox(label="Generated caption", lines=6)
run_button.click(fn=caption_image, inputs=[image_input, prompt_choice], outputs=output_text)
gr.Markdown(
"""
---
Model card and training details: see the linked paper and the
[adapter repository](https://huggingface.co/WafaaFraih/qwen25vl_correctness_checkpoint).
Base model: [Qwen2.5-VL-7B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct).
"""
)
if __name__ == "__main__":
demo.launch()