Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,84 +1,68 @@
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
-
|
4 |
-
|
5 |
"""
|
6 |
|
7 |
import gradio as gr
|
8 |
-
import
|
9 |
-
from transformers import Qwen2_5_VLForConditionalGeneration, AutoTokenizer, AutoProcessor
|
10 |
-
from qwen_vl_utils import process_vision_info
|
11 |
from PIL import Image
|
12 |
import json
|
13 |
import time
|
14 |
import os
|
15 |
from typing import Dict, List, Optional, Tuple
|
|
|
|
|
16 |
import warnings
|
17 |
warnings.filterwarnings("ignore")
|
18 |
|
19 |
-
# Global
|
20 |
-
|
21 |
-
|
22 |
-
DEVICE = None
|
23 |
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
if
|
30 |
-
|
31 |
else:
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
global MODEL, PROCESSOR, DEVICE
|
37 |
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
print(f"Using device: {DEVICE}")
|
42 |
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
PROCESSOR = AutoProcessor.from_pretrained(
|
55 |
-
model_name,
|
56 |
-
trust_remote_code=True
|
57 |
-
)
|
58 |
-
|
59 |
-
if DEVICE == "cpu":
|
60 |
-
MODEL = MODEL.to(DEVICE)
|
61 |
-
|
62 |
-
print("Model loaded successfully!")
|
63 |
-
return True
|
64 |
|
65 |
-
|
66 |
-
|
67 |
-
return False
|
68 |
-
|
69 |
-
return True
|
70 |
|
71 |
def create_medical_prompt(clinical_data: Dict, analysis_type: str, focus_areas: str) -> str:
|
72 |
-
"""Create optimized medical analysis prompt"""
|
73 |
|
74 |
-
base_prompt = """You are an expert medical AI assistant specializing in medical image analysis
|
75 |
|
76 |
**ANALYSIS INSTRUCTIONS:**
|
77 |
-
|
78 |
-
- Use clear medical terminology with explanations
|
79 |
-
- Structure your response with clear sections
|
80 |
- Be thorough but concise
|
81 |
-
- Always mention limitations and need for professional consultation
|
|
|
82 |
"""
|
83 |
|
84 |
# Add clinical context if provided
|
@@ -86,51 +70,94 @@ Provide a systematic, professional medical image analysis following these guidel
|
|
86 |
if clinical_data and any(v.strip() for v in clinical_data.values() if v):
|
87 |
clinical_context = "\n**CLINICAL CONTEXT:**\n"
|
88 |
context_items = []
|
89 |
-
if clinical_data.get("age"): context_items.append(f"Age: {clinical_data['age']}")
|
90 |
if clinical_data.get("gender"): context_items.append(f"Gender: {clinical_data['gender']}")
|
91 |
-
if clinical_data.get("symptoms"): context_items.append(f"Symptoms: {clinical_data['symptoms']}")
|
92 |
-
if clinical_data.get("history"): context_items.append(f"History: {clinical_data['history']}")
|
93 |
-
if clinical_data.get("medications"): context_items.append(f"Medications: {clinical_data['medications']}")
|
94 |
|
95 |
clinical_context += "\n".join(f"β’ {item}" for item in context_items) + "\n"
|
96 |
|
97 |
# Analysis type specific instructions
|
98 |
analysis_instructions = {
|
99 |
"Comprehensive": """
|
100 |
-
**PROVIDE COMPREHENSIVE ANALYSIS:**
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
""",
|
107 |
"Quick Assessment": """
|
108 |
-
**PROVIDE QUICK ASSESSMENT:**
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
""",
|
114 |
"Educational": """
|
115 |
**PROVIDE EDUCATIONAL ANALYSIS:**
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
"""
|
121 |
}
|
122 |
|
123 |
focus_instruction = ""
|
124 |
if focus_areas and focus_areas.strip():
|
125 |
-
focus_instruction = f"\n**SPECIAL FOCUS**: Pay particular attention to: {focus_areas}\n"
|
126 |
|
127 |
disclaimer = """
|
128 |
-
**MEDICAL DISCLAIMER**:
|
|
|
129 |
"""
|
130 |
|
131 |
return base_prompt + clinical_context + analysis_instructions.get(analysis_type, analysis_instructions["Comprehensive"]) + focus_instruction + disclaimer
|
132 |
|
133 |
-
def
|
134 |
image: Image.Image,
|
135 |
age: str,
|
136 |
gender: str,
|
@@ -139,17 +166,19 @@ def analyze_medical_image(
|
|
139 |
medications: str,
|
140 |
analysis_type: str,
|
141 |
focus_areas: str,
|
|
|
142 |
progress=gr.Progress()
|
143 |
-
) -> Tuple[str, str]:
|
144 |
-
"""Main analysis function
|
145 |
|
146 |
if image is None:
|
147 |
-
return "β Please upload an image first.", ""
|
148 |
|
149 |
-
#
|
150 |
-
progress(0.1, desc="
|
151 |
-
|
152 |
-
|
|
|
153 |
|
154 |
try:
|
155 |
progress(0.3, desc="Preparing analysis...")
|
@@ -166,110 +195,104 @@ def analyze_medical_image(
|
|
166 |
# Create prompt
|
167 |
prompt = create_medical_prompt(clinical_data, analysis_type, focus_areas)
|
168 |
|
169 |
-
progress(0.5, desc="
|
170 |
-
|
171 |
-
# Prepare messages for model
|
172 |
-
messages = [
|
173 |
-
{
|
174 |
-
"role": "user",
|
175 |
-
"content": [
|
176 |
-
{"type": "image", "image": image},
|
177 |
-
{"type": "text", "text": prompt}
|
178 |
-
]
|
179 |
-
}
|
180 |
-
]
|
181 |
-
|
182 |
-
# Process inputs
|
183 |
-
text = PROCESSOR.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
184 |
-
image_inputs, video_inputs = process_vision_info(messages)
|
185 |
-
|
186 |
-
inputs = PROCESSOR(
|
187 |
-
text=[text],
|
188 |
-
images=image_inputs,
|
189 |
-
videos=video_inputs,
|
190 |
-
padding=True,
|
191 |
-
return_tensors="pt",
|
192 |
-
)
|
193 |
-
|
194 |
-
inputs = inputs.to(DEVICE)
|
195 |
-
|
196 |
-
progress(0.7, desc="Generating analysis...")
|
197 |
-
|
198 |
-
# Generate response with optimized parameters for HF Spaces
|
199 |
-
with torch.no_grad():
|
200 |
-
generated_ids = MODEL.generate(
|
201 |
-
**inputs,
|
202 |
-
max_new_tokens=1024, # Reduced for faster processing
|
203 |
-
do_sample=True,
|
204 |
-
temperature=0.3,
|
205 |
-
top_p=0.8,
|
206 |
-
repetition_penalty=1.1,
|
207 |
-
pad_token_id=PROCESSOR.tokenizer.eos_token_id,
|
208 |
-
eos_token_id=PROCESSOR.tokenizer.eos_token_id,
|
209 |
-
)
|
210 |
|
211 |
-
|
212 |
-
|
213 |
-
for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
|
214 |
-
]
|
215 |
|
216 |
-
response
|
217 |
-
|
218 |
-
skip_special_tokens=True,
|
219 |
-
clean_up_tokenization_spaces=False
|
220 |
-
)[0]
|
221 |
|
222 |
-
progress(
|
223 |
|
224 |
# Create download content
|
225 |
report_data = {
|
226 |
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC"),
|
227 |
-
"model": "
|
228 |
"analysis_type": analysis_type,
|
229 |
"clinical_data": clinical_data,
|
230 |
"focus_areas": focus_areas,
|
231 |
-
"analysis": response
|
232 |
}
|
233 |
|
234 |
download_content = json.dumps(report_data, indent=2)
|
235 |
|
236 |
-
|
|
|
|
|
237 |
|
238 |
except Exception as e:
|
239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
|
241 |
def create_interface():
|
242 |
-
"""Create the Gradio interface"""
|
243 |
|
244 |
# Custom CSS for medical theme
|
245 |
css = """
|
246 |
.gradio-container {
|
247 |
-
max-width:
|
248 |
}
|
249 |
.medical-header {
|
250 |
text-align: center;
|
251 |
-
color: #
|
252 |
margin-bottom: 20px;
|
253 |
}
|
254 |
-
.
|
255 |
-
background-color: #
|
256 |
padding: 15px;
|
257 |
border-radius: 8px;
|
258 |
margin: 10px 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
}
|
260 |
"""
|
261 |
|
262 |
-
with gr.Blocks(css=css, theme=gr.themes.Soft(), title="Medical AI
|
263 |
|
264 |
# Header
|
265 |
gr.HTML("""
|
266 |
<div class="medical-header">
|
267 |
<h1>π₯ Medical Image AI Analyzer</h1>
|
268 |
-
<
|
269 |
-
<p><em>
|
270 |
</div>
|
271 |
""")
|
272 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
273 |
with gr.Row():
|
274 |
# Left column - Inputs
|
275 |
with gr.Column(scale=1):
|
@@ -279,10 +302,10 @@ def create_interface():
|
|
279 |
type="pil",
|
280 |
label="Medical Image",
|
281 |
height=300,
|
282 |
-
sources=["upload", "clipboard"]
|
283 |
)
|
284 |
|
285 |
-
gr.Markdown("*Supported: X-rays, CT, MRI, photographs, microscopy, etc.*")
|
286 |
|
287 |
gr.Markdown("## π Clinical Information")
|
288 |
|
@@ -328,12 +351,12 @@ def create_interface():
|
|
328 |
|
329 |
focus_areas = gr.Textbox(
|
330 |
label="Focus Areas (Optional)",
|
331 |
-
placeholder="e.g., cardiac,
|
332 |
info="Specific areas to emphasize in analysis"
|
333 |
)
|
334 |
|
335 |
analyze_btn = gr.Button(
|
336 |
-
"π¬ Analyze
|
337 |
variant="primary",
|
338 |
size="lg"
|
339 |
)
|
@@ -344,8 +367,8 @@ def create_interface():
|
|
344 |
|
345 |
analysis_output = gr.Textbox(
|
346 |
label="Medical Analysis",
|
347 |
-
lines=
|
348 |
-
max_lines=
|
349 |
show_copy_button=True,
|
350 |
placeholder="Analysis results will appear here after processing..."
|
351 |
)
|
@@ -358,33 +381,49 @@ def create_interface():
|
|
358 |
# Hidden component to store download content
|
359 |
download_content = gr.Textbox(visible=False)
|
360 |
|
361 |
-
#
|
362 |
-
with gr.Accordion("π‘
|
363 |
gr.Markdown("""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
364 |
### π **Supported Medical Images:**
|
365 |
- **Radiology**: X-rays, CT scans, MRI images, Ultrasound
|
366 |
-
- **Pathology**: Histological slides, Cytology specimens
|
367 |
- **Dermatology**: Skin lesions, Rashes, Clinical photos
|
368 |
- **Ophthalmology**: Fundus photos, OCT images
|
369 |
- **Clinical Photography**: Wound assessment, Physical findings
|
|
|
370 |
|
371 |
-
###
|
372 |
-
-
|
373 |
-
-
|
374 |
-
-
|
375 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
376 |
|
377 |
-
###
|
378 |
-
-
|
379 |
-
-
|
380 |
-
-
|
381 |
-
- Results should be validated by medical professionals
|
382 |
""")
|
383 |
|
384 |
# Footer
|
385 |
gr.HTML("""
|
386 |
<div style="text-align: center; margin-top: 20px; padding: 15px; background-color: #fff3cd; border-radius: 8px;">
|
387 |
-
<strong>β οΈ Medical Disclaimer:</strong> This AI tool is for educational purposes only.
|
388 |
It should never replace professional medical diagnosis or treatment.
|
389 |
Always consult qualified healthcare providers for medical decisions.
|
390 |
</div>
|
@@ -393,19 +432,33 @@ def create_interface():
|
|
393 |
# Event handlers
|
394 |
def create_download_file(content):
|
395 |
if content:
|
396 |
-
filename = f"
|
397 |
with open(filename, "w") as f:
|
398 |
f.write(content)
|
399 |
return gr.File(value=filename, visible=True)
|
400 |
return gr.File(visible=False)
|
401 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
402 |
analyze_btn.click(
|
403 |
-
fn=
|
404 |
inputs=[
|
405 |
image_input, age_input, gender_input, symptoms_input,
|
406 |
-
history_input, medications_input, analysis_type, focus_areas
|
407 |
],
|
408 |
-
outputs=[analysis_output, download_content]
|
409 |
).then(
|
410 |
fn=create_download_file,
|
411 |
inputs=[download_content],
|
@@ -415,61 +468,17 @@ def create_interface():
|
|
415 |
return interface
|
416 |
|
417 |
if __name__ == "__main__":
|
418 |
-
|
419 |
-
print("
|
420 |
-
load_model_cached()
|
421 |
|
422 |
# Create and launch interface
|
423 |
interface = create_interface()
|
424 |
|
425 |
-
# Launch with
|
426 |
interface.launch(
|
427 |
server_name="0.0.0.0",
|
428 |
server_port=7860,
|
429 |
share=False,
|
430 |
show_error=True,
|
431 |
quiet=False
|
432 |
-
)
|
433 |
-
|
434 |
-
"""
|
435 |
-
HUGGING FACE SPACE CONFIGURATION:
|
436 |
-
|
437 |
-
1. Create requirements.txt:
|
438 |
-
torch
|
439 |
-
torchvision
|
440 |
-
transformers>=4.37.0
|
441 |
-
accelerate
|
442 |
-
pillow
|
443 |
-
gradio
|
444 |
-
qwen-vl-utils
|
445 |
-
spaces
|
446 |
-
|
447 |
-
2. Create app.py (this file)
|
448 |
-
|
449 |
-
3. Create README.md:
|
450 |
-
---
|
451 |
-
title: Medical Image AI Analyzer
|
452 |
-
emoji: π₯
|
453 |
-
colorFrom: blue
|
454 |
-
colorTo: green
|
455 |
-
sdk: gradio
|
456 |
-
sdk_version: 4.44.0
|
457 |
-
app_file: app.py
|
458 |
-
pinned: false
|
459 |
-
license: apache-2.0
|
460 |
-
---
|
461 |
-
|
462 |
-
4. Optional - Create .gitattributes:
|
463 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
464 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
465 |
-
|
466 |
-
DEPLOYMENT FEATURES:
|
467 |
-
β
Optimized for Hugging Face Spaces
|
468 |
-
β
Efficient memory usage
|
469 |
-
β
Progress indicators
|
470 |
-
β
Professional medical interface
|
471 |
-
β
Download analysis reports
|
472 |
-
β
Mobile-responsive design
|
473 |
-
β
Error handling and validation
|
474 |
-
β
Medical disclaimer compliance
|
475 |
-
"""
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
+
Gemini Vision Pro Medical Image Analysis - Gradio Interface
|
4 |
+
Lightweight alternative using Google's Gemini Vision Pro API
|
5 |
"""
|
6 |
|
7 |
import gradio as gr
|
8 |
+
import google.generativeai as genai
|
|
|
|
|
9 |
from PIL import Image
|
10 |
import json
|
11 |
import time
|
12 |
import os
|
13 |
from typing import Dict, List, Optional, Tuple
|
14 |
+
import base64
|
15 |
+
import io
|
16 |
import warnings
|
17 |
warnings.filterwarnings("ignore")
|
18 |
|
19 |
+
# Global configuration
|
20 |
+
GEMINI_MODEL = None
|
21 |
+
API_KEY = None
|
|
|
22 |
|
23 |
+
def setup_gemini(api_key: str = None):
|
24 |
+
"""Setup Gemini Vision Pro with API key"""
|
25 |
+
global GEMINI_MODEL, API_KEY
|
26 |
+
|
27 |
+
# Try to get API key from environment or parameter
|
28 |
+
if api_key:
|
29 |
+
API_KEY = api_key
|
30 |
else:
|
31 |
+
API_KEY = os.getenv('GOOGLE_API_KEY') or os.getenv('GEMINI_API_KEY')
|
32 |
+
|
33 |
+
if not API_KEY:
|
34 |
+
return False, "β No API key provided. Please set GOOGLE_API_KEY environment variable or enter it in the interface."
|
|
|
35 |
|
36 |
+
try:
|
37 |
+
# Configure the API
|
38 |
+
genai.configure(api_key=API_KEY)
|
|
|
39 |
|
40 |
+
# Initialize the model
|
41 |
+
GEMINI_MODEL = genai.GenerativeModel('gemini-1.5-pro')
|
42 |
+
|
43 |
+
# Test the connection
|
44 |
+
test_response = GEMINI_MODEL.generate_content("Hello, can you help with medical image analysis?")
|
45 |
+
|
46 |
+
if test_response and test_response.text:
|
47 |
+
return True, "β
Gemini Vision Pro connected successfully!"
|
48 |
+
else:
|
49 |
+
return False, "β Failed to connect to Gemini API"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
+
except Exception as e:
|
52 |
+
return False, f"β Gemini setup failed: {str(e)}"
|
|
|
|
|
|
|
53 |
|
54 |
def create_medical_prompt(clinical_data: Dict, analysis_type: str, focus_areas: str) -> str:
|
55 |
+
"""Create optimized medical analysis prompt for Gemini"""
|
56 |
|
57 |
+
base_prompt = """You are an expert medical AI assistant specializing in medical image analysis. You have extensive training across radiology, pathology, dermatology, ophthalmology, and clinical medicine.
|
58 |
|
59 |
**ANALYSIS INSTRUCTIONS:**
|
60 |
+
Analyze this medical image systematically and professionally:
|
61 |
+
- Use clear medical terminology with explanations for complex terms
|
62 |
+
- Structure your response with clear sections and headers
|
63 |
- Be thorough but concise
|
64 |
+
- Always mention limitations and emphasize the need for professional medical consultation
|
65 |
+
- Focus on observable findings rather than definitive diagnoses
|
66 |
"""
|
67 |
|
68 |
# Add clinical context if provided
|
|
|
70 |
if clinical_data and any(v.strip() for v in clinical_data.values() if v):
|
71 |
clinical_context = "\n**CLINICAL CONTEXT:**\n"
|
72 |
context_items = []
|
73 |
+
if clinical_data.get("age"): context_items.append(f"Patient Age: {clinical_data['age']}")
|
74 |
if clinical_data.get("gender"): context_items.append(f"Gender: {clinical_data['gender']}")
|
75 |
+
if clinical_data.get("symptoms"): context_items.append(f"Presenting Symptoms: {clinical_data['symptoms']}")
|
76 |
+
if clinical_data.get("history"): context_items.append(f"Medical History: {clinical_data['history']}")
|
77 |
+
if clinical_data.get("medications"): context_items.append(f"Current Medications: {clinical_data['medications']}")
|
78 |
|
79 |
clinical_context += "\n".join(f"β’ {item}" for item in context_items) + "\n"
|
80 |
|
81 |
# Analysis type specific instructions
|
82 |
analysis_instructions = {
|
83 |
"Comprehensive": """
|
84 |
+
**PROVIDE COMPREHENSIVE ANALYSIS WITH THESE SECTIONS:**
|
85 |
+
|
86 |
+
## 1. IMAGE ASSESSMENT
|
87 |
+
- Image type, quality, and technical adequacy
|
88 |
+
- Anatomical structures and regions visible
|
89 |
+
- Any artifacts or limitations
|
90 |
+
|
91 |
+
## 2. CLINICAL FINDINGS
|
92 |
+
- Normal anatomical structures observed
|
93 |
+
- Abnormal findings or variations from normal
|
94 |
+
- Specific measurements or quantitative observations if applicable
|
95 |
+
|
96 |
+
## 3. CLINICAL INTERPRETATION
|
97 |
+
- Significance of the findings
|
98 |
+
- Differential diagnostic considerations
|
99 |
+
- Correlation with provided clinical history
|
100 |
+
|
101 |
+
## 4. RECOMMENDATIONS
|
102 |
+
- Suggested next steps or additional imaging
|
103 |
+
- Clinical correlation recommendations
|
104 |
+
- Follow-up suggestions
|
105 |
+
|
106 |
+
## 5. LIMITATIONS & DISCLAIMERS
|
107 |
+
- What cannot be determined from this image alone
|
108 |
+
- Need for clinical correlation and professional evaluation
|
109 |
""",
|
110 |
"Quick Assessment": """
|
111 |
+
**PROVIDE FOCUSED QUICK ASSESSMENT:**
|
112 |
+
|
113 |
+
## KEY FINDINGS
|
114 |
+
- Most significant observations
|
115 |
+
- Normal vs abnormal structures
|
116 |
+
|
117 |
+
## CLINICAL IMPRESSION
|
118 |
+
- Primary considerations based on image
|
119 |
+
- Any urgent findings that require immediate attention
|
120 |
+
|
121 |
+
## IMMEDIATE RECOMMENDATIONS
|
122 |
+
- Essential next steps
|
123 |
+
- Urgency level assessment
|
124 |
+
|
125 |
+
## LIMITATIONS
|
126 |
+
- Important caveats about this assessment
|
127 |
""",
|
128 |
"Educational": """
|
129 |
**PROVIDE EDUCATIONAL ANALYSIS:**
|
130 |
+
|
131 |
+
## LEARNING OBJECTIVES
|
132 |
+
- Key educational points from this case
|
133 |
+
- Important anatomical or pathological concepts
|
134 |
+
|
135 |
+
## NORMAL vs ABNORMAL
|
136 |
+
- Clear explanation of what's normal in this image
|
137 |
+
- Detailed description of any abnormal findings
|
138 |
+
|
139 |
+
## CLINICAL CORRELATION
|
140 |
+
- How image findings relate to symptoms/history
|
141 |
+
- Real-world clinical significance
|
142 |
+
|
143 |
+
## TEACHING PEARLS
|
144 |
+
- Important concepts this case demonstrates
|
145 |
+
- Common pitfalls or considerations
|
146 |
"""
|
147 |
}
|
148 |
|
149 |
focus_instruction = ""
|
150 |
if focus_areas and focus_areas.strip():
|
151 |
+
focus_instruction = f"\n**SPECIAL FOCUS AREAS**: Pay particular attention to: {focus_areas}\n"
|
152 |
|
153 |
disclaimer = """
|
154 |
+
**IMPORTANT MEDICAL DISCLAIMER**:
|
155 |
+
This AI-powered analysis is for educational and research purposes only. It should never replace professional medical diagnosis, treatment, or consultation with qualified healthcare providers. Always seek professional medical advice for any health concerns or medical decisions.
|
156 |
"""
|
157 |
|
158 |
return base_prompt + clinical_context + analysis_instructions.get(analysis_type, analysis_instructions["Comprehensive"]) + focus_instruction + disclaimer
|
159 |
|
160 |
+
def analyze_medical_image_gemini(
|
161 |
image: Image.Image,
|
162 |
age: str,
|
163 |
gender: str,
|
|
|
166 |
medications: str,
|
167 |
analysis_type: str,
|
168 |
focus_areas: str,
|
169 |
+
api_key: str,
|
170 |
progress=gr.Progress()
|
171 |
+
) -> Tuple[str, str, str]:
|
172 |
+
"""Main analysis function using Gemini Vision Pro"""
|
173 |
|
174 |
if image is None:
|
175 |
+
return "β Please upload an image first.", "", "β No image provided"
|
176 |
|
177 |
+
# Setup Gemini if needed
|
178 |
+
progress(0.1, desc="Connecting to Gemini...")
|
179 |
+
success, status = setup_gemini(api_key)
|
180 |
+
if not success:
|
181 |
+
return status, "", status
|
182 |
|
183 |
try:
|
184 |
progress(0.3, desc="Preparing analysis...")
|
|
|
195 |
# Create prompt
|
196 |
prompt = create_medical_prompt(clinical_data, analysis_type, focus_areas)
|
197 |
|
198 |
+
progress(0.5, desc="Analyzing image with Gemini...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
|
200 |
+
# Generate analysis using Gemini Vision Pro
|
201 |
+
response = GEMINI_MODEL.generate_content([prompt, image])
|
|
|
|
|
202 |
|
203 |
+
if not response or not response.text:
|
204 |
+
return "β No response received from Gemini API", "", "β Analysis failed"
|
|
|
|
|
|
|
205 |
|
206 |
+
progress(0.9, desc="Preparing results...")
|
207 |
|
208 |
# Create download content
|
209 |
report_data = {
|
210 |
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC"),
|
211 |
+
"model": "Google Gemini-1.5-Pro Vision",
|
212 |
"analysis_type": analysis_type,
|
213 |
"clinical_data": clinical_data,
|
214 |
"focus_areas": focus_areas,
|
215 |
+
"analysis": response.text
|
216 |
}
|
217 |
|
218 |
download_content = json.dumps(report_data, indent=2)
|
219 |
|
220 |
+
progress(1.0, desc="Analysis complete!")
|
221 |
+
|
222 |
+
return response.text, download_content, "β
Analysis completed successfully"
|
223 |
|
224 |
except Exception as e:
|
225 |
+
error_msg = f"β Analysis failed: {str(e)}"
|
226 |
+
if "API_KEY" in str(e):
|
227 |
+
error_msg += "\n\nπ‘ Tip: Make sure your Google API key is valid and has access to Gemini API"
|
228 |
+
elif "quota" in str(e).lower():
|
229 |
+
error_msg += "\n\nπ‘ Tip: You may have exceeded your API quota. Check your Google Cloud Console"
|
230 |
+
elif "safety" in str(e).lower():
|
231 |
+
error_msg += "\n\nπ‘ Tip: The image may have been blocked by safety filters. Try a different medical image"
|
232 |
+
|
233 |
+
return error_msg, "", error_msg
|
234 |
|
235 |
def create_interface():
|
236 |
+
"""Create the Gradio interface for Gemini Vision Pro"""
|
237 |
|
238 |
# Custom CSS for medical theme
|
239 |
css = """
|
240 |
.gradio-container {
|
241 |
+
max-width: 1400px !important;
|
242 |
}
|
243 |
.medical-header {
|
244 |
text-align: center;
|
245 |
+
color: #1a73e8;
|
246 |
margin-bottom: 20px;
|
247 |
}
|
248 |
+
.api-section {
|
249 |
+
background-color: #e8f0fe;
|
250 |
padding: 15px;
|
251 |
border-radius: 8px;
|
252 |
margin: 10px 0;
|
253 |
+
border-left: 4px solid #1a73e8;
|
254 |
+
}
|
255 |
+
.status-success {
|
256 |
+
color: #137333;
|
257 |
+
font-weight: 500;
|
258 |
+
}
|
259 |
+
.status-error {
|
260 |
+
color: #d93025;
|
261 |
+
font-weight: 500;
|
262 |
}
|
263 |
"""
|
264 |
|
265 |
+
with gr.Blocks(css=css, theme=gr.themes.Soft(), title="Gemini Medical AI") as interface:
|
266 |
|
267 |
# Header
|
268 |
gr.HTML("""
|
269 |
<div class="medical-header">
|
270 |
<h1>π₯ Medical Image AI Analyzer</h1>
|
271 |
+
<h2>π€ Powered by Google Gemini Vision Pro</h2>
|
272 |
+
<p><em>Fast, efficient medical image analysis using Google's latest AI</em></p>
|
273 |
</div>
|
274 |
""")
|
275 |
|
276 |
+
# API Configuration Section
|
277 |
+
with gr.Accordion("π API Configuration", open=True):
|
278 |
+
gr.Markdown("""
|
279 |
+
### Google Gemini API Setup
|
280 |
+
You need a Google API key to use Gemini Vision Pro. Get one from [Google AI Studio](https://makersuite.google.com/app/apikey).
|
281 |
+
""")
|
282 |
+
|
283 |
+
api_key_input = gr.Textbox(
|
284 |
+
label="Google API Key",
|
285 |
+
type="password",
|
286 |
+
placeholder="Enter your Google API key here...",
|
287 |
+
info="Your API key is not stored and only used for this session"
|
288 |
+
)
|
289 |
+
|
290 |
+
status_display = gr.Textbox(
|
291 |
+
label="Connection Status",
|
292 |
+
value="β³ Enter API key to connect",
|
293 |
+
interactive=False
|
294 |
+
)
|
295 |
+
|
296 |
with gr.Row():
|
297 |
# Left column - Inputs
|
298 |
with gr.Column(scale=1):
|
|
|
302 |
type="pil",
|
303 |
label="Medical Image",
|
304 |
height=300,
|
305 |
+
sources=["upload", "clipboard", "webcam"]
|
306 |
)
|
307 |
|
308 |
+
gr.Markdown("*Supported: X-rays, CT, MRI, photographs, microscopy, dermatology images, etc.*")
|
309 |
|
310 |
gr.Markdown("## π Clinical Information")
|
311 |
|
|
|
351 |
|
352 |
focus_areas = gr.Textbox(
|
353 |
label="Focus Areas (Optional)",
|
354 |
+
placeholder="e.g., cardiac silhouette, lung fields, bone density",
|
355 |
info="Specific areas to emphasize in analysis"
|
356 |
)
|
357 |
|
358 |
analyze_btn = gr.Button(
|
359 |
+
"π¬ Analyze with Gemini",
|
360 |
variant="primary",
|
361 |
size="lg"
|
362 |
)
|
|
|
367 |
|
368 |
analysis_output = gr.Textbox(
|
369 |
label="Medical Analysis",
|
370 |
+
lines=25,
|
371 |
+
max_lines=35,
|
372 |
show_copy_button=True,
|
373 |
placeholder="Analysis results will appear here after processing..."
|
374 |
)
|
|
|
381 |
# Hidden component to store download content
|
382 |
download_content = gr.Textbox(visible=False)
|
383 |
|
384 |
+
# Information sections
|
385 |
+
with gr.Accordion("π‘ About Gemini Vision Pro", open=False):
|
386 |
gr.Markdown("""
|
387 |
+
### π **Advantages of Gemini Vision Pro:**
|
388 |
+
- **Fast Processing**: No local model loading - results in seconds
|
389 |
+
- **Low Resource Usage**: Runs via API calls, minimal local computing needed
|
390 |
+
- **High Quality**: Google's latest multimodal AI model
|
391 |
+
- **Always Updated**: Access to the latest model improvements
|
392 |
+
- **Reliable**: Enterprise-grade infrastructure
|
393 |
+
|
394 |
### π **Supported Medical Images:**
|
395 |
- **Radiology**: X-rays, CT scans, MRI images, Ultrasound
|
396 |
+
- **Pathology**: Histological slides, Cytology specimens
|
397 |
- **Dermatology**: Skin lesions, Rashes, Clinical photos
|
398 |
- **Ophthalmology**: Fundus photos, OCT images
|
399 |
- **Clinical Photography**: Wound assessment, Physical findings
|
400 |
+
- **Microscopy**: Cellular and tissue analysis
|
401 |
|
402 |
+
### π° **Cost Information:**
|
403 |
+
- Gemini Vision Pro uses pay-per-use pricing
|
404 |
+
- Typically very affordable for individual analyses
|
405 |
+
- Check [Google AI Pricing](https://ai.google.dev/pricing) for current rates
|
406 |
+
""")
|
407 |
+
|
408 |
+
with gr.Accordion("π Tips for Better Results", open=False):
|
409 |
+
gr.Markdown("""
|
410 |
+
### π― **Optimization Tips:**
|
411 |
+
- **Provide clinical context**: Age, symptoms, and history significantly improve accuracy
|
412 |
+
- **Use specific focus areas**: "cardiac silhouette, pulmonary vessels" vs just "chest"
|
413 |
+
- **High-quality images**: Clear, well-lit, properly oriented images work best
|
414 |
+
- **Appropriate image size**: Gemini works well with various image sizes
|
415 |
+
- **Choose right analysis type**: Comprehensive for complex cases, Quick for screening
|
416 |
|
417 |
+
### π **API Key Security:**
|
418 |
+
- Your API key is only used for this session and not stored
|
419 |
+
- Consider using environment variables for production deployments
|
420 |
+
- Monitor your API usage in Google Cloud Console
|
|
|
421 |
""")
|
422 |
|
423 |
# Footer
|
424 |
gr.HTML("""
|
425 |
<div style="text-align: center; margin-top: 20px; padding: 15px; background-color: #fff3cd; border-radius: 8px;">
|
426 |
+
<strong>β οΈ Medical Disclaimer:</strong> This AI tool is for educational and research purposes only.
|
427 |
It should never replace professional medical diagnosis or treatment.
|
428 |
Always consult qualified healthcare providers for medical decisions.
|
429 |
</div>
|
|
|
432 |
# Event handlers
|
433 |
def create_download_file(content):
|
434 |
if content:
|
435 |
+
filename = f"gemini_medical_analysis_{int(time.time())}.json"
|
436 |
with open(filename, "w") as f:
|
437 |
f.write(content)
|
438 |
return gr.File(value=filename, visible=True)
|
439 |
return gr.File(visible=False)
|
440 |
|
441 |
+
def test_api_connection(api_key):
|
442 |
+
if not api_key:
|
443 |
+
return "β³ Enter API key to connect"
|
444 |
+
success, status = setup_gemini(api_key)
|
445 |
+
return status
|
446 |
+
|
447 |
+
# API key testing
|
448 |
+
api_key_input.change(
|
449 |
+
fn=test_api_connection,
|
450 |
+
inputs=[api_key_input],
|
451 |
+
outputs=[status_display]
|
452 |
+
)
|
453 |
+
|
454 |
+
# Main analysis
|
455 |
analyze_btn.click(
|
456 |
+
fn=analyze_medical_image_gemini,
|
457 |
inputs=[
|
458 |
image_input, age_input, gender_input, symptoms_input,
|
459 |
+
history_input, medications_input, analysis_type, focus_areas, api_key_input
|
460 |
],
|
461 |
+
outputs=[analysis_output, download_content, status_display]
|
462 |
).then(
|
463 |
fn=create_download_file,
|
464 |
inputs=[download_content],
|
|
|
468 |
return interface
|
469 |
|
470 |
if __name__ == "__main__":
|
471 |
+
print("π₯ Initializing Gemini Medical AI Analyzer...")
|
472 |
+
print("π No local model loading required - using Google Gemini Vision Pro API")
|
|
|
473 |
|
474 |
# Create and launch interface
|
475 |
interface = create_interface()
|
476 |
|
477 |
+
# Launch with optimized settings
|
478 |
interface.launch(
|
479 |
server_name="0.0.0.0",
|
480 |
server_port=7860,
|
481 |
share=False,
|
482 |
show_error=True,
|
483 |
quiet=False
|
484 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|