Update app.py
Browse files
app.py
CHANGED
|
@@ -1,112 +1,123 @@
|
|
| 1 |
-
app.py
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
CLIENT = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
TEXT_ONLY_REPLY = "أنا R-RAY AI تم تدريبي على قواعد بيانات طبية ضخمة. كيف يمكنني مساعدتك اليوم؟"
|
| 18 |
|
| 19 |
-
Strict radiology-report instruction (Arabic). The model MUST follow this and reply only with a
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
2. لا تضف جمل ترويجية أو اعتذارات أو نص خارج إطار التقرير.
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
3. إخراج التقرير يجب أن يحتوي على الأقسام التالية بالترتيب: عنوان الدراسة، تاريخ الاستلام (التاريخ اليومي تلقائيًا)، رقم المريض (إن وُرد)، التاريخ السريري/شكوى المريض (من نص المستخدم إن وُجد)، التقنية (نوع الصور: X-ray, CT, MRI + الإطارات/القطاعات المرفقة)، وصف تفصيلي للنتائج (ملاحظات عامة ومقارنة إن وُجدت معلومات سابقة)، خاتمة/تقرير تشخيصي موجز مع توصية مفيدة واحدة على الأكثر (مثل: متابعة، توصية بإجراء تصوير مقطعي/تصوير رنين، أو استشارة اختصاصي).
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
4. إذا كان هناك أكثر من صورة — اذكر كل صورة مرقمة ووصف كل واحدة تحت قسم "الصور" ثم ادمج النتائج في قسم "النتائج".
|
| 37 |
-
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
البيانات المرسلة: {clinical_text}
|
| 44 |
|
| 45 |
الصور المرسلة: {image_list}
|
| 46 |
|
| 47 |
-
أعد فقط التقرير الطبي بالصيغة المطلوبة. انتهى.
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
def
|
| 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 |
-
# return text output
|
| 92 |
-
return response.text
|
| 93 |
-
except Exception as e:
|
| 94 |
-
return f"خطأ أثناء الاتصال بنموذج Gemini: {e}"
|
| 95 |
-
|
| 96 |
-
Build Gradio UI
|
| 97 |
-
|
| 98 |
-
with gr.Blocks() as demo: gr.Markdown("# R-RAY AI — تقارير الأشعة\nارفع صور شعاعية (X-ray, CT, MRI) ويمكنك أيضًا ارفاق ملاحظات سريرية قصيرة.")
|
| 99 |
-
|
| 100 |
-
with gr.Row():
|
| 101 |
-
image_input = gr.Files(label="تحميل صور (يمكن رفع أكثر من صورة)", file_count="multiple", type="file")
|
| 102 |
-
clinical_input = gr.Textbox(label="معلومات سريرية / تاريخ المريض (اختياري)", lines=4)
|
| 103 |
-
|
| 104 |
-
output = gr.Textbox(label="تقرير الأشعة (الناتج)", lines=20)
|
| 105 |
-
|
| 106 |
-
btn = gr.Button("تحليل وإصدار التقرير")
|
| 107 |
-
btn.click(fn=analyze, inputs=[image_input, clinical_input], outputs=[output])
|
| 108 |
-
|
| 109 |
-
gr.Markdown("**ملاحظة:** هذا النموذج للاختبار/التطوير فقط ولا يغني عن استشارة طبية حقيقية.")
|
| 110 |
-
|
| 111 |
-
if name == "main": demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|
| 112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
# Gradio Hugging Face Space connected to Google Gemini (via google-genai)
|
| 3 |
+
# - Put your Gemini API key into Hugging Face Space secrets with name: GEMINI_API_KEY
|
| 4 |
+
# - Add requirements.txt with: gradio\ngoogle-genai
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import io
|
| 8 |
+
from google import genai
|
| 9 |
+
from google.genai import types
|
| 10 |
+
import gradio as gr
|
| 11 |
+
|
| 12 |
+
# Initialize client using secret from HF environment
|
| 13 |
+
CLIENT = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))
|
| 14 |
+
MODEL = "gemini-2.5-flash" # or change to a model available in your project/region
|
| 15 |
+
|
| 16 |
+
# Fixed short reply for text-only chats
|
| 17 |
TEXT_ONLY_REPLY = "أنا R-RAY AI تم تدريبي على قواعد بيانات طبية ضخمة. كيف يمكنني مساعدتك اليوم؟"
|
| 18 |
|
| 19 |
+
# Strict radiology-report instruction (Arabic). The model MUST follow this and reply only with a
|
| 20 |
+
# structured medical radiology report. Keep it concise, professional, and in Arabic. Do not add
|
| 21 |
+
# extra commentary, safety disclaimers, or step-by-step reasoning in the response.
|
| 22 |
+
RADIology_PROMPT_TEMPLATE = '''أنت "R-RAY AI" — نموذج متخصص في تقارير الأشعة، تدربت على قواعد بيانات طبية ضخمة.
|
| 23 |
+
تعليمات صارمة:
|
| 24 |
+
1) عند استلام صور شعاعية (واحدة أو أكثر) — أجب **فقط** بتقرير طبي احترافي بصيغة تقارير الأشعة المستعملة سريريًا.
|
| 25 |
+
2) لا تضف جمل ترويجية أو اعتذارات أو نص خارج إطار التقرير.
|
| 26 |
+
3) إخراج التقرير يجب أن يحتوي على الأقسام التالية بالترتيب: عنوان الدراسة، تاريخ الاستلام (التاريخ اليومي تلقائيًا)، رقم المريض (إن وُرد)، التاريخ السريري/شكوى المريض (من نص المستخدم إن وُجد)، التقنية (نوع الصور: X-ray, CT, MRI + الإطارات/القطاعات المرفقة)، وصف تفصيلي للنتائج (ملاحظات عامة ومقارنة إن وُجدت معلومات سابقة)، خاتمة/تقرير تشخيصي موجز مع توصية مفيدة واحدة على الأكثر (مثل: متابعة، توصية بإجراء تصوير مقطعي/تصوير رنين، أو استشارة اختصاصي).
|
| 27 |
+
4) إذا كان هناك أكثر من صورة — اذكر كل صورة مرقمة ووصف كل واحدة تحت قسم "الصور" ثم ادمج النتائج في قسم "النتائج".
|
| 28 |
+
5) اللغة: العربية الفصحى طبية. الطول: تقرير كامل ومحترف (لا تقل عن 6 جمل في الوصف ما لم تكن الصور طبيعية تمامًا).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
البيانات المرسلة:
|
| 31 |
+
{clinical_text}
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
الصور المرسلة: {image_list}
|
| 34 |
|
| 35 |
+
أعد فقط التقرير الطبي بالصيغة المطلوبة. انتهى.
|
| 36 |
+
'''
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def make_parts_from_images(image_files):
|
| 40 |
+
parts = []
|
| 41 |
+
for idx, img in enumerate(image_files, start=1):
|
| 42 |
+
# img is a tempfile-like object from Gradio; read bytes
|
| 43 |
+
img_bytes = img.read()
|
| 44 |
+
# prefer from_bytes (supported in google-genai). fallback to from_image if exists.
|
| 45 |
+
try:
|
| 46 |
+
part = types.Part.from_bytes(img_bytes)
|
| 47 |
+
except Exception:
|
| 48 |
+
try:
|
| 49 |
+
part = types.Part.from_image(img_bytes)
|
| 50 |
+
except Exception:
|
| 51 |
+
# if both fail, embed base64 text as a last resort
|
| 52 |
+
import base64
|
| 53 |
+
b64 = base64.b64encode(img_bytes).decode('utf-8')
|
| 54 |
+
part = types.Part.from_text(f"[BASE64_IMAGE_{idx}] {b64}")
|
| 55 |
+
parts.append(part)
|
| 56 |
+
return parts
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def analyze(images, clinical_text):
|
| 60 |
+
clinical_text = (clinical_text or "").strip()
|
| 61 |
+
images = images or []
|
| 62 |
+
|
| 63 |
+
# If no images: provide the fixed text-only reply
|
| 64 |
+
if len(images) == 0:
|
| 65 |
+
# If the user sent text only, return the strict assistant opening line
|
| 66 |
+
return TEXT_ONLY_REPLY
|
| 67 |
+
|
| 68 |
+
# Build list of image names for prompt
|
| 69 |
+
image_names = []
|
| 70 |
+
for i, f in enumerate(images, start=1):
|
| 71 |
+
name = getattr(f, "name", None) or f"image_{i}"
|
| 72 |
+
image_names.append(name)
|
| 73 |
+
image_list_text = ", ".join(image_names)
|
| 74 |
+
|
| 75 |
+
# Prepare prompt (strict)
|
| 76 |
+
prompt_text = RADIology_PROMPT_TEMPLATE.format(
|
| 77 |
+
clinical_text=clinical_text if clinical_text else "(لا توجد معلومات سريرية إضافية)",
|
| 78 |
+
image_list=image_list_text,
|
| 79 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
# Build content parts: first the images, then the instruction as text part
|
| 82 |
+
parts = make_parts_from_images(images)
|
| 83 |
+
parts.append(types.Part.from_text(prompt_text))
|
| 84 |
+
|
| 85 |
+
contents = [
|
| 86 |
+
types.Content(
|
| 87 |
+
role="user",
|
| 88 |
+
parts=parts,
|
| 89 |
+
)
|
| 90 |
+
]
|
| 91 |
+
|
| 92 |
+
# Call Gemini (synchronous)
|
| 93 |
+
try:
|
| 94 |
+
response = CLIENT.models.generate_content(
|
| 95 |
+
model=MODEL,
|
| 96 |
+
contents=contents,
|
| 97 |
+
config=types.GenerateContentConfig(
|
| 98 |
+
thinking_config=types.ThinkingConfig(thinking_budget=-1),
|
| 99 |
+
),
|
| 100 |
+
)
|
| 101 |
+
# return text output
|
| 102 |
+
return response.text
|
| 103 |
+
except Exception as e:
|
| 104 |
+
return f"خطأ أثناء الاتصال بنموذج Gemini: {e}"
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
# Build Gradio UI
|
| 108 |
+
with gr.Blocks() as demo:
|
| 109 |
+
gr.Markdown("# R-RAY AI — تقارير الأشعة\nارفع صور شعاعية (X-ray, CT, MRI) ويمكنك أيضًا ارفاق ملاحظات سريرية قصيرة.")
|
| 110 |
+
|
| 111 |
+
with gr.Row():
|
| 112 |
+
image_input = gr.Files(label="تحميل صور (يمكن رفع أكثر من صورة)", file_count="multiple", type="file")
|
| 113 |
+
clinical_input = gr.Textbox(label="معلومات سريرية / تاريخ المريض (اختياري)", lines=4)
|
| 114 |
+
|
| 115 |
+
output = gr.Textbox(label="تقرير الأشعة (الناتج)", lines=20)
|
| 116 |
+
|
| 117 |
+
btn = gr.Button("تحليل وإصدار التقرير")
|
| 118 |
+
btn.click(fn=analyze, inputs=[image_input, clinical_input], outputs=[output])
|
| 119 |
+
|
| 120 |
+
gr.Markdown("**ملاحظة:** هذا النموذج للاختبار/التطوير فقط ولا يغني عن استشارة طبية حقيقية.")
|
| 121 |
+
|
| 122 |
+
if __name__ == "__main__":
|
| 123 |
+
demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|