bdtimuhammad commited on
Commit
51ed9c7
·
verified ·
1 Parent(s): bc98225

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -162
app.py CHANGED
@@ -1,178 +1,70 @@
1
- # Remove theme and css from here
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 datetime
12
- from dotenv import load_dotenv
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
- AUDIT_LOG_FILE = "audit_log.csv"
24
- if not os.path.exists(AUDIT_LOG_FILE):
25
- pd.DataFrame(columns=["Timestamp", "Modality", "Audit_Action", "Notes"]).to_csv(AUDIT_LOG_FILE, index=False)
26
-
27
- def log_audit_action(modality, action, notes):
28
- if not action:
29
- return "Please select an Audit Decision."
30
- try:
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 generate_vqa_wrapper(message, history, modality, image):
61
- """Wrapper that passes MedGemma models to the tutor generator."""
62
- medgemma_model, medgemma_tokenizer = loader.get_medgemma()
63
 
64
- for chunk in generate_vqa_response(medgemma_model, medgemma_tokenizer, message, history, modality, image):
65
- yield chunk
66
-
67
- def generate_visual_audit(image, query, current_chat_history):
68
- """Generates the BiomedCLIP heatmap, and optionally overlays MedGemma boxes from chat history."""
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
- # Attempt to draw MedGemma bboxes if present in the last assistant response
76
- if current_chat_history and len(current_chat_history) > 0:
77
- last_item = current_chat_history[-1]
78
- last_response = last_item.get("content", "") if isinstance(last_item, dict) else ""
79
- if last_response and "<loc" in last_response:
80
- heatmap_pil = overlay_medgemma_bboxes(heatmap_pil, last_response)
81
-
82
- return heatmap_pil
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
- with gr.Blocks(theme=gr.themes.Base(), css=custom_css) as demo:
95
- gr.Markdown("# <span class='header-title'>NerdMedica Diagnostics Hub</span>")
96
- gr.Markdown("Orchestrating **MAIRA-2** (Radiology), **MedGemma 1.5 4B** (Council of Experts), and **BiomedCLIP** (Zero-Shot Visual Audit).")
97
 
98
  with gr.Row():
99
- with gr.Column(scale=1, elem_classes="shadow-card"):
100
- modality_dropdown = gr.Dropdown(choices=["Chest X-Ray", "CT Scan", "MRI"], value="Chest X-Ray", label="Select Modality")
101
- image_input = gr.Image(type="pil", label="Upload Medical Scan")
102
- analyze_btn = gr.Button("1. Generate MAIRA-2 Radiology Draft", elem_classes="primary-btn")
103
-
104
- gr.Markdown("### Audit Visualizer (BiomedCLIP & MedGemma)")
105
- visual_output = gr.Image(type="pil", label="Visual Evidence (Heatmap + BBoxes)")
106
- visual_query = gr.Textbox(label="BiomedCLIP Visual Query", placeholder="e.g., 'Consolidation' or 'Pleural Effusion'")
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=1, elem_classes="shadow-card"):
120
- gr.Markdown("### Council of Experts (Socratic Tutor)")
121
- chatbot = gr.Chatbot(type="messages", height=450, label="Senior Clinical Generalist")
122
- msg = gr.Textbox(label="Ask the Council (e.g., 'What justifies this diagnosis?')")
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
- def bot(history, modality, image):
141
- if history is None or len(history) == 0:
142
- yield history
143
- return
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
- # Link both text box submission and "Consult the Council" button to the same pipeline
152
- msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then(
153
- bot, [chatbot, modality_dropdown, image_input], chatbot
154
- )
155
- consult_btn.click(user, [msg, chatbot], [msg, chatbot], queue=False).then(
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
- if __name__ == "__main__":
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
  )