medguard / app.py
Haseeb1000's picture
Create app.py
64ddbf8 verified
%%writefile app.py
import gradio as gr
import os, sys, subprocess, re, json, base64, io, contextlib
from io import BytesIO
from PIL import Image
from huggingface_hub import hf_hub_download
# ── HARDWARE ──────────────────────────────────
try:
result = subprocess.run(['nvidia-smi'], capture_output=True)
HAS_GPU = result.returncode == 0
except:
HAS_GPU = False
# ── INSTALL ───────────────────────────────────
try:
import llama_cpp
import argostranslate.package
import argostranslate.translate
except ImportError:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-q',
'argostranslate', 'huggingface_hub', 'pillow'])
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-q',
'llama-cpp-python',
'--extra-index-url',
'https://abetlen.github.io/llama-cpp-python/whl/cu121'
if HAS_GPU else
'https://abetlen.github.io/llama-cpp-python/whl/cpu'
])
import llama_cpp
import argostranslate.package
import argostranslate.translate
from llama_cpp import Llama
from llama_cpp.llama_chat_format import Llava15ChatHandler
LANGUAGES = {
"English": "en", "Urdu": "ur", "Arabic": "ar",
"Hindi": "hi", "French": "fr", "Spanish": "es"
}
# ── LOAD MODEL ────────────────────────────────
def load_model():
print("πŸ“₯ Downloading MedGemma GGUF...")
text_gguf = hf_hub_download(
repo_id="unsloth/medgemma-4b-it-GGUF",
filename="medgemma-4b-it-Q4_K_M.gguf"
)
vision_gguf = hf_hub_download(
repo_id="kelkalot/medgemma-4b-it-GGUF",
filename="mmproj-medgemma-4b-it-F16.gguf"
)
chat_handler = Llava15ChatHandler(clip_model_path=vision_gguf)
return Llama(
model_path=text_gguf,
chat_handler=chat_handler,
chat_format="gemma",
n_ctx=4096,
n_threads=os.cpu_count() or 4,
n_gpu_layers=-1 if HAS_GPU else 0,
verbose=False
)
model = load_model()
# ── TRANSLATION ───────────────────────────────
def ensure_language_downloaded(target_code):
if target_code == "en": return
installed = argostranslate.package.get_installed_packages()
if any(p.to_code == target_code and p.from_code == 'en' for p in installed): return
argostranslate.package.update_package_index()
available = argostranslate.package.get_available_packages()
try:
pkg = next(filter(lambda x: x.from_code == 'en' and x.to_code == target_code, available))
argostranslate.package.install_from_path(pkg.download())
except: pass
def translate_text(text, target_code):
if target_code == "en" or not text or text == "N/A": return text
try: return argostranslate.translate.translate(text, "en", target_code)
except: return text
# ── JSON HELPERS ──────────────────────────────
def fix_json(raw):
raw = re.sub(r'```json|```', '', raw).strip()
raw = re.sub(r':\s*true\b', ': "True"', raw, flags=re.IGNORECASE)
raw = re.sub(r':\s*false\b', ': "False"', raw, flags=re.IGNORECASE)
raw = re.sub(r':\s*high\b', ': "High"', raw, flags=re.IGNORECASE)
raw = re.sub(r':\s*medium\b', ': "Medium"', raw, flags=re.IGNORECASE)
raw = re.sub(r':\s*low\b', ': "Low"', raw, flags=re.IGNORECASE)
return raw
def parse_output(raw):
try: return json.loads(fix_json(raw))
except: pass
try:
match = re.search(r'\{.*?\}', raw, re.DOTALL)
if match: return json.loads(fix_json(match.group()))
except: pass
def extract(p, t, d='N/A'):
m = re.search(p, t, re.IGNORECASE | re.DOTALL)
return m.group(1).strip() if m else d
return {
'claim': extract(r'"claim"\s*:\s*"([^"]+)"', raw),
'verdict': extract(r'"verdict"\s*:\s*["]?([^",}\n]+)', raw),
'reason': extract(r'"reason"\s*:\s*"([^"]+)"', raw),
'doctor_summary': extract(r'"doctor_summary"\s*:\s*"([^"]+)"', raw),
'risk_level': extract(r'"risk_level"\s*:\s*["]?([^",}\n]+)',raw),
'confidence': extract(r'"confidence"\s*:\s*["]?([0-9.]+)', raw, '0.0'),
}
# ── ANALYZE ───────────────────────────────────
def analyze(user_claim, symptoms, age, gender, condition,
language, image):
lang_code = LANGUAGES.get(language, "en")
ensure_language_downloaded(lang_code)
history = f"{age} Years Old {gender}, {condition}"
image_b64 = None
if image is not None:
try:
img = Image.fromarray(image).convert("RGB")
buf = BytesIO()
img.save(buf, format="JPEG")
image_b64 = base64.b64encode(buf.getvalue()).decode('utf-8')
except: pass
raw_prompt = f"""<bos><start_of_turn>user
You are an expert Clinical Triage Specialist and Medical Fact-Checker.
Return ONLY a valid JSON object. No extra text, no markdown outside the JSON.
SCHEMA:
{{"claim":"...","verdict":"True|False|Mixed|Insufficient Evidence","reason":"...","doctor_summary":"...","risk_level":"Low|Medium|High","confidence":"0.0-1.0"}}
EXAMPLE:
{{"claim":"Drinking bleach cures covid","verdict":"False","reason":"Bleach is caustic and toxic with no antiviral properties.","doctor_summary":"HIGH ALERT: toxic ingestion risk. Consult toxicology.","risk_level":"High","confidence":"0.98"}}
NOW EVALUATE:
Symptoms: {symptoms}
Claim: "{user_claim}"
Patient: {history}
Image: {"Provided" if image_b64 else "Not provided"}
OUTPUT JSON:<end_of_turn>
<start_of_turn>model
"""
if not image_b64:
sink = io.StringIO()
with contextlib.redirect_stdout(sink):
response = model.create_completion(
prompt=raw_prompt,
max_tokens=350,
temperature=0.1,
stop=["<end_of_turn>"]
)
raw = response['choices'][0]['text']
else:
content = [
{"type": "text", "text": raw_prompt},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}}
]
sink = io.StringIO()
with contextlib.redirect_stdout(sink):
response = model.create_chat_completion(
messages=[{"role": "user", "content": content}],
max_tokens=350, temperature=0.1, repeat_penalty=1.3
)
raw = response['choices'][0]['message']['content']
parsed = parse_output(raw)
if lang_code != "en":
parsed['reason'] = translate_text(parsed.get('reason', ''), lang_code)
parsed['doctor_summary'] = translate_text(parsed.get('doctor_summary', ''), lang_code)
# ── Format output ─────────────────────────
verdict = parsed.get('verdict', 'N/A').strip().strip('"')
risk = parsed.get('risk_level', 'N/A').strip().strip('"')
reason = parsed.get('reason', 'N/A')
doc_note = parsed.get('doctor_summary', 'N/A')
confidence = parsed.get('confidence', '0.0')
verdict_map = {
"True": "βœ… TRUE",
"False": "❌ FALSE",
"Mixed": "⚠️ MIXED",
"Insufficient Evidence": "πŸ” INSUFFICIENT EVIDENCE"
}
risk_map = {"High": "πŸ”΄ High", "Medium": "🟑 Medium", "Low": "🟒 Low"}
verdict_str = verdict_map.get(verdict, f"❓ {verdict}")
risk_str = risk_map.get(risk, f"βšͺ {risk}")
try:
conf_pct = f"{float(confidence):.0%}"
except:
conf_pct = confidence
return (
verdict_str,
risk_str,
conf_pct,
reason,
doc_note
)
# ── EXAMPLES ──────────────────────────────────
EXAMPLES = [
["Drinking Dettol cures COVID-19", "Fever, cough", 30, "Male", "No chronic illness", "English", None],
["Eating oranges gives vitamin C and boosts immunity","Fatigue, frequent colds", 25, "Female", "No known conditions", "English", None],
["Jeera water permanently cures diabetes", "High blood sugar, fatigue", 70, "Male", "Type 2 Diabetes on Insulin", "English", None],
["Turmeric milk cures pneumonia completely", "Fever, chest pain", 60, "Female", "Hypertension", "English", None],
["Drinking cold water causes cancer", "No symptoms", 40, "Male", "Generally healthy", "English", None],
]
# ── GRADIO UI ─────────────────────────────────
with gr.Blocks(
theme=gr.themes.Soft(primary_hue="blue"),
title="πŸ›‘οΈ MedGuard"
) as demo:
gr.HTML("""
<div style="background:linear-gradient(135deg,#0077b6,#023e8a);
border-radius:16px; padding:32px; text-align:center; margin-bottom:20px;">
<h1 style="color:white; font-size:2.5em; margin:0;">πŸ›‘οΈ MedGuard</h1>
<p style="color:#90e0ef; margin:8px 0 0;">
AI-Powered Medical Misinformation Defense Β· MedGemma 4B-IT Β· Edge AI
</p>
<div style="margin-top:12px;">
<span style="background:rgba(255,255,255,0.15); color:white;
border-radius:20px; padding:4px 12px; font-size:0.8em; margin:3px;">
βš•οΈ MedGemma</span>
<span style="background:rgba(255,255,255,0.15); color:white;
border-radius:20px; padding:4px 12px; font-size:0.8em; margin:3px;">
πŸš€ Edge AI</span>
<span style="background:rgba(255,255,255,0.15); color:white;
border-radius:20px; padding:4px 12px; font-size:0.8em; margin:3px;">
🌍 Multilingual</span>
<span style="background:rgba(255,255,255,0.15); color:white;
border-radius:20px; padding:4px 12px; font-size:0.8em; margin:3px;">
🩺 Triage Ready</span>
</div>
</div>
""")
with gr.Row():
# ── LEFT COLUMN ───────────────────────
with gr.Column(scale=1):
gr.Markdown("### πŸ“‹ Patient Information")
user_claim = gr.Textbox(
label="πŸ” Health Claim to Verify",
placeholder="Paste a WhatsApp health claim, home remedy, or medical advice...",
lines=3
)
symptoms = gr.Textbox(
label="πŸ€’ Patient Symptoms",
placeholder="e.g. fever, fatigue, chest pain",
lines=2
)
with gr.Row():
age = gr.Number(label="πŸŽ‚ Age", value=45, minimum=1, maximum=120)
gender = gr.Dropdown(
label="⚧ Gender",
choices=["Male", "Female", "Other"],
value="Male"
)
condition = gr.Textbox(
label="πŸ₯ Medical History",
value="No known conditions"
)
language = gr.Dropdown(
label="🌍 Output Language",
choices=list(LANGUAGES.keys()),
value="English"
)
image = gr.Image(
label="πŸ–ΌοΈ Upload X-Ray or Medical Image (Optional)",
type="numpy"
)
analyze_btn = gr.Button(
"πŸ›‘οΈ Run MedGuard Analysis",
variant="primary",
size="lg"
)
gr.Markdown("### πŸ’‘ Quick Examples")
gr.Examples(
examples=EXAMPLES,
inputs=[user_claim, symptoms, age, gender, condition, language, image],
label="Click to load"
)
# ── RIGHT COLUMN ──────────────────────
with gr.Column(scale=1):
gr.Markdown("### πŸ“Š MedGuard Analysis Result")
verdict_out = gr.Textbox(label="βš–οΈ Verdict", interactive=False)
risk_out = gr.Textbox(label="🚨 Risk Level", interactive=False)
confidence_out = gr.Textbox(label="πŸ“Š Confidence", interactive=False)
reason_out = gr.Textbox(label="🧠 Medical Reasoning",
lines=6, interactive=False)
doctor_out = gr.Textbox(label="πŸ‘¨β€βš•οΈ Doctor Triage Note",
lines=5, interactive=False)
gr.HTML("""
<div style="background:#fff8e1; border-left:4px solid #ffa000;
border-radius:8px; padding:12px; margin-top:16px;
font-size:0.85em; color:#555;">
⚠️ MedGuard is an AI triage support tool.
It does <b>not</b> replace qualified medical professionals.
Always consult a doctor.
</div>
""")
analyze_btn.click(
fn=analyze,
inputs=[user_claim, symptoms, age, gender,
condition, language, image],
outputs=[verdict_out, risk_out, confidence_out,
reason_out, doctor_out]
)
demo.launch()