File size: 4,771 Bytes
71337a7
 
 
 
 
 
 
 
8684690
71337a7
 
 
 
3f2297a
71337a7
 
8684690
 
71337a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8764b02
 
 
 
 
3f2297a
71337a7
8764b02
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71337a7
8764b02
 
71337a7
8764b02
3f2297a
71337a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3f2297a
71337a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""
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()