Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,178 +1,70 @@
|
|
| 1 |
-
|
| 2 |
-
with gr.Blocks() as demo:
|
| 3 |
-
gr.HTML("<div id='header'><h1 style='color:white; font-family:Consolas;'>🧬 MediVance LLC | NerdMedica Council</h1></div>")
|
| 4 |
-
# ... rest of your UI code ...
|
| 5 |
-
import gradio as gr
|
| 6 |
-
from loader import ModelLoader
|
| 7 |
-
from inference import generate_maira2_report, generate_biomedclip_heatmap, overlay_medgemma_bboxes
|
| 8 |
-
from tutor import generate_vqa_response
|
| 9 |
-
import pandas as pd
|
| 10 |
import os
|
| 11 |
-
import
|
| 12 |
-
from
|
| 13 |
-
|
| 14 |
-
# Security: Ensure dotenv keys are safely loaded early
|
| 15 |
-
load_dotenv()
|
| 16 |
-
|
| 17 |
-
# --- Initialization ---
|
| 18 |
-
print("Initializing Triple-Model NerdMedica Platform...")
|
| 19 |
-
loader = ModelLoader()
|
| 20 |
-
# Pre-load MedGemma 1.5 4B and BiomedCLIP into VRAM
|
| 21 |
-
loader.init_startup_models()
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
df = pd.read_csv(AUDIT_LOG_FILE)
|
| 32 |
-
new_entry = {
|
| 33 |
-
"Timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 34 |
-
"Modality": modality,
|
| 35 |
-
"Audit_Action": action,
|
| 36 |
-
"Notes": notes
|
| 37 |
-
}
|
| 38 |
-
df = pd.concat([df, pd.DataFrame([new_entry])], ignore_index=True)
|
| 39 |
-
df.to_csv(AUDIT_LOG_FILE, index=False)
|
| 40 |
-
return gr.update(value=f"Successfully logged from Doctor-in-the-Loop UI:\nDecision: {action}\nNotes: {notes}")
|
| 41 |
-
except Exception as e:
|
| 42 |
-
return gr.update(value=f"Log Error: {e}")
|
| 43 |
-
|
| 44 |
-
def process_modality_routing(image, modality):
|
| 45 |
-
"""Routes the image to MAIRA-2 if X-Ray, else assigns text fallback."""
|
| 46 |
-
if image is None:
|
| 47 |
-
return "No image uploaded. Operating in text-only Clinical Generalist mode. Ask your question in the Socratic Tutor."
|
| 48 |
-
|
| 49 |
-
if modality == "Chest X-Ray" or modality == "X-Ray":
|
| 50 |
-
loader.load_maira2_lazy()
|
| 51 |
-
maira2_model, maira2_processor = loader.get_maira2()
|
| 52 |
-
report = generate_maira2_report(maira2_model, maira2_processor, image)
|
| 53 |
-
|
| 54 |
-
# Offload after generation to save memory for subsequent MedGemma/BiomedCLIP calls
|
| 55 |
-
loader.clear_vram()
|
| 56 |
-
return report
|
| 57 |
-
else:
|
| 58 |
-
return f"MAIRA-2 is specialized for Chest X-Rays.\n\nFor {modality}, please consult the MedGemma Socratic Tutor directly for an interactive VQA assessment."
|
| 59 |
|
| 60 |
-
def
|
| 61 |
-
|
| 62 |
-
medgemma_model, medgemma_tokenizer = loader.get_medgemma()
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
if image is None:
|
| 70 |
-
return None
|
| 71 |
-
|
| 72 |
-
biomed_model, biomed_preprocess, biomed_tokenizer = loader.get_biomedclip()
|
| 73 |
-
heatmap_pil = generate_biomedclip_heatmap(biomed_model, biomed_preprocess, biomed_tokenizer, image, query)
|
| 74 |
|
| 75 |
-
#
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
return
|
| 83 |
-
|
| 84 |
-
custom_css = """
|
| 85 |
-
body, .gradio-container { background-color: #0A0E1A !important; color: #D1D5DB !important; font-family: 'Inter', sans-serif; }
|
| 86 |
-
.header-title { border-bottom: 2px solid #E5534B; padding-bottom: 10px; color: #D1D5DB !important; }
|
| 87 |
-
h1, h2, h3, p, span, label, .markdown-text { color: #D1D5DB !important; }
|
| 88 |
-
.primary-btn { background: linear-gradient(90deg, #E5534B, #A855F7) !important; border: none !important; color: white !important; font-weight: bold !important; transition: all 0.3s ease; }
|
| 89 |
-
.primary-btn:hover { opacity: 0.9 !important; transform: translateY(-1px); }
|
| 90 |
-
.shadow-card { background-color: #111827 !important; border: 1px solid #1F2937 !important; border-radius: 8px !important; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.6) !important; padding: 15px; margin-bottom: 10px; }
|
| 91 |
-
.textbox textarea { background-color: #1F2937 !important; color: #D1D5DB !important; border: 1px solid #374151 !important; border-radius: 6px !important; }
|
| 92 |
-
"""
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
gr.
|
| 97 |
|
| 98 |
with gr.Row():
|
| 99 |
-
with gr.Column(scale=1
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
visual_btn = gr.Button("2. Generate Visual Confirmation", elem_classes="primary-btn")
|
| 108 |
-
|
| 109 |
-
with gr.Accordion("Doctor-In-The-Loop Audit Logging", open=False):
|
| 110 |
-
audit_decision = gr.Radio(["Approved", "Corrected", "Flagged (Hallucination)"], label="Human Audit Decision")
|
| 111 |
-
audit_notes = gr.Textbox(label="Corrections / Notes")
|
| 112 |
-
log_btn = gr.Button("Save to Audit Log", elem_classes="primary-btn")
|
| 113 |
-
log_status = gr.Markdown("")
|
| 114 |
-
|
| 115 |
-
with gr.Column(scale=1, elem_classes="shadow-card"):
|
| 116 |
-
gr.Markdown("### Clinical Evaluation Draft")
|
| 117 |
-
draft_output = gr.Textbox(lines=25, interactive=True, label="Radiology / Clinical Draft (Editable)")
|
| 118 |
|
| 119 |
-
with gr.Column(scale=
|
| 120 |
-
gr.
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
consult_btn = gr.Button("Consult the Council", elem_classes="primary-btn")
|
| 124 |
-
clear = gr.ClearButton([msg, chatbot])
|
| 125 |
-
|
| 126 |
-
# Event Wiring
|
| 127 |
-
analyze_btn.click(
|
| 128 |
-
process_modality_routing,
|
| 129 |
-
inputs=[image_input, modality_dropdown],
|
| 130 |
-
outputs=[draft_output]
|
| 131 |
-
)
|
| 132 |
-
|
| 133 |
-
def user(user_message, history):
|
| 134 |
-
if history is None:
|
| 135 |
-
history = []
|
| 136 |
-
if user_message.strip() != "":
|
| 137 |
-
history.append({"role": "user", "content": user_message})
|
| 138 |
-
return "", history
|
| 139 |
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
user_message = history[-1]["content"]
|
| 145 |
-
bot_generator = generate_vqa_wrapper(user_message, history[:-1], modality, image)
|
| 146 |
-
history.append({"role": "assistant", "content": ""})
|
| 147 |
-
for chunk in bot_generator:
|
| 148 |
-
history[-1]["content"] = chunk
|
| 149 |
-
yield history
|
| 150 |
|
| 151 |
-
#
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
bot, [chatbot, modality_dropdown, image_input], chatbot
|
| 157 |
-
)
|
| 158 |
-
|
| 159 |
-
visual_btn.click(
|
| 160 |
-
generate_visual_audit,
|
| 161 |
-
inputs=[image_input, visual_query, chatbot],
|
| 162 |
-
outputs=[visual_output]
|
| 163 |
-
)
|
| 164 |
-
|
| 165 |
-
log_btn.click(
|
| 166 |
-
log_audit_action,
|
| 167 |
-
inputs=[modality_dropdown, audit_decision, audit_notes],
|
| 168 |
-
outputs=[log_status]
|
| 169 |
)
|
| 170 |
|
| 171 |
-
|
| 172 |
-
demo.launch(ssr_mode=False)
|
| 173 |
-
# Pass them into launch() instead
|
| 174 |
demo.launch(
|
| 175 |
-
ssr_mode=False,
|
| 176 |
-
theme=gr.themes.Base(),
|
| 177 |
css=CSS
|
| 178 |
)
|
|
|
|
| 1 |
+
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import os
|
| 3 |
+
from loader import loader
|
| 4 |
+
from tutor import run_council_deliberation
|
| 5 |
+
from inference import generate_universal_heatmap
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
+
# NerdMedica Dark Tech CSS
|
| 8 |
+
CSS = """
|
| 9 |
+
body, .gradio-container { background-color: #0A0E1A !important; color: #D1D5DB !important; }
|
| 10 |
+
.gr-button-primary { background: linear-gradient(90deg, #E5534B, #A855F7) !important; border: none !important; border-radius: 8px !important; color: white !important; font-weight: bold !important; }
|
| 11 |
+
#header { border-bottom: 2px solid #E5534B; padding-bottom: 15px; margin-bottom: 25px; }
|
| 12 |
+
.message.user { background-color: #1F2937 !important; border-left: 4px solid #E5534B !important; }
|
| 13 |
+
.message.bot { background-color: #111827 !important; border-left: 4px solid #3B82F6 !important; }
|
| 14 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
def medical_audit_pipeline(image, modality, question, history):
|
| 17 |
+
loader.clear_vram()
|
|
|
|
| 18 |
|
| 19 |
+
# 1. Visual Evidence (If image exists)
|
| 20 |
+
heatmap = None
|
| 21 |
+
if image is not None:
|
| 22 |
+
clip_model, preprocess = loader.load_biomed_clip()
|
| 23 |
+
heatmap = generate_universal_heatmap(image, question, clip_model, preprocess)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
+
# 2. Council Deliberation
|
| 26 |
+
response = run_council_deliberation(image, modality, question)
|
| 27 |
+
|
| 28 |
+
# Append to Chat History
|
| 29 |
+
history.append({"role": "user", "content": question})
|
| 30 |
+
history.append({"role": "assistant", "content": response})
|
| 31 |
+
|
| 32 |
+
return history, heatmap, response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
# Gradio 6.0: We build the blocks without passing theme/css here
|
| 35 |
+
with gr.Blocks() as demo:
|
| 36 |
+
gr.HTML("<div id='header'><h1 style='color:white; font-family:Consolas;'>🧬 MediVance LLC | NerdMedica Council</h1></div>")
|
| 37 |
|
| 38 |
with gr.Row():
|
| 39 |
+
with gr.Column(scale=1):
|
| 40 |
+
img_in = gr.Image(type="pil", label="Medical Scan (Optional)")
|
| 41 |
+
modality_drop = gr.Dropdown(
|
| 42 |
+
["General Inquiry", "Chest X-Ray", "CT Scan", "MRI", "Pathology"],
|
| 43 |
+
value="General Inquiry",
|
| 44 |
+
label="Modality Focus"
|
| 45 |
+
)
|
| 46 |
+
heatmap_out = gr.Image(label="Visual Evidence Audit")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
+
with gr.Column(scale=2):
|
| 49 |
+
chat = gr.Chatbot(label="Council Deliberation", type="messages", height=500)
|
| 50 |
+
query = gr.Textbox(placeholder="Ask about 'Isolated Hypertension' or specific scan findings...", label="Doctor's Inquiry")
|
| 51 |
+
btn = gr.Button("Consult the Council", variant="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
+
with gr.Accordion("🩺 Official Clinical Audit Report", open=False):
|
| 54 |
+
audit_box = gr.Textbox(label="Final Finding (Editable)", interactive=True, lines=4)
|
| 55 |
+
status = gr.Radio(["Approved", "Corrected", "Flagged"], label="Clinical Signature")
|
| 56 |
+
gr.Button("Archive to MediVance API", variant="secondary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
+
# Wire up the button
|
| 59 |
+
btn.click(
|
| 60 |
+
fn=medical_audit_pipeline,
|
| 61 |
+
inputs=[img_in, modality_drop, query, chat],
|
| 62 |
+
outputs=[chat, heatmap_out, audit_box]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
)
|
| 64 |
|
| 65 |
+
# Gradio 6.0: Theme and CSS MUST be passed in the launch method
|
|
|
|
|
|
|
| 66 |
demo.launch(
|
| 67 |
+
ssr_mode=False,
|
| 68 |
+
theme=gr.themes.Base(),
|
| 69 |
css=CSS
|
| 70 |
)
|