Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
-
Gemini Vision Pro Medical Image Analysis - Gradio Interface
|
4 |
-
|
5 |
"""
|
6 |
|
7 |
import gradio as gr
|
@@ -10,127 +10,48 @@ from PIL import Image
|
|
10 |
import json
|
11 |
import time
|
12 |
import os
|
13 |
-
from typing import Dict,
|
14 |
-
import
|
15 |
-
import io
|
16 |
import warnings
|
|
|
17 |
warnings.filterwarnings("ignore")
|
18 |
|
19 |
-
#
|
20 |
-
|
21 |
-
API_KEY = None
|
22 |
|
23 |
-
|
24 |
-
|
25 |
-
# Try multiple possible secret names
|
26 |
-
possible_keys = [
|
27 |
-
'GEMINI_API_KEY'
|
28 |
-
]
|
29 |
-
|
30 |
-
for key_name in possible_keys:
|
31 |
-
# First try HuggingFace secrets (if running on HF Spaces)
|
32 |
-
try:
|
33 |
-
from huggingface_hub import HfFolder
|
34 |
-
token = HfFolder.get_token()
|
35 |
-
if token:
|
36 |
-
# This is a simplified approach - in HF Spaces, secrets are usually available as env vars
|
37 |
-
api_key = os.getenv(key_name)
|
38 |
-
if api_key:
|
39 |
-
return api_key
|
40 |
-
except:
|
41 |
-
pass
|
42 |
-
|
43 |
-
# Try regular environment variables
|
44 |
-
api_key = os.getenv(key_name)
|
45 |
-
if api_key:
|
46 |
-
return api_key
|
47 |
-
|
48 |
-
return None
|
49 |
|
50 |
-
def setup_gemini(
|
51 |
-
"""
|
52 |
-
global GEMINI_MODEL
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
API_KEY = api_key.strip()
|
57 |
-
else:
|
58 |
-
API_KEY = get_api_key_from_secrets()
|
59 |
-
|
60 |
-
if not API_KEY:
|
61 |
-
return False, "❌ No API key found. Please:\n1. Set GOOGLE_API_KEY in HuggingFace Spaces secrets"
|
62 |
-
|
63 |
try:
|
64 |
-
|
65 |
-
genai.
|
66 |
-
|
67 |
-
|
68 |
-
GEMINI_MODEL = genai.GenerativeModel(
|
69 |
-
'gemini-1.5-pro'
|
70 |
-
)
|
71 |
-
|
72 |
-
# Test the connection with a simple query
|
73 |
-
test_response = GEMINI_MODEL.generate_content(
|
74 |
-
"Respond with just 'OK' to confirm you can analyze medical images.",
|
75 |
-
generation_config=genai.types.GenerationConfig(
|
76 |
-
temperature=0.1,
|
77 |
-
max_output_tokens=10
|
78 |
-
)
|
79 |
-
)
|
80 |
-
|
81 |
if test_response and test_response.text:
|
82 |
return True, "✅ Gemini Vision Pro connected successfully!"
|
83 |
else:
|
84 |
-
return False, "❌ Failed to
|
85 |
-
|
86 |
except Exception as e:
|
87 |
-
|
88 |
-
|
89 |
-
# Provide specific guidance based on error type
|
90 |
-
if "429" in str(e) or "quota" in str(e).lower():
|
91 |
-
error_msg += "\n\n💡 **Quota Exceeded Solutions:**\n"
|
92 |
-
error_msg += "• Check your Google Cloud Console billing\n"
|
93 |
-
error_msg += "• Verify your API key has sufficient quota\n"
|
94 |
-
error_msg += "• Try again in a few minutes (rate limiting)\n"
|
95 |
-
error_msg += "• Consider upgrading your Google AI plan\n"
|
96 |
-
error_msg += "🔗 Check usage: https://console.cloud.google.com/"
|
97 |
-
|
98 |
-
elif "403" in str(e) or "permission" in str(e).lower():
|
99 |
-
error_msg += "\n\n💡 **Permission Error Solutions:**\n"
|
100 |
-
error_msg += "• Ensure Gemini API is enabled in your project\n"
|
101 |
-
error_msg += "• Check if your API key has proper permissions\n"
|
102 |
-
error_msg += "• Verify your Google Cloud project settings"
|
103 |
-
|
104 |
-
elif "401" in str(e) or "unauthorized" in str(e).lower():
|
105 |
-
error_msg += "\n\n💡 **Authentication Error:**\n"
|
106 |
-
error_msg += "• Double-check your API key is correct\n"
|
107 |
-
error_msg += "• Ensure no extra spaces in the API key\n"
|
108 |
-
error_msg += "• Generate a new API key if needed"
|
109 |
-
|
110 |
-
elif "safety" in str(e).lower() or "blocked" in str(e).lower():
|
111 |
-
error_msg += "\n\n💡 **Content Safety Issue:**\n"
|
112 |
-
error_msg += "• The image may have triggered safety filters\n"
|
113 |
-
error_msg += "• Try a different medical image\n"
|
114 |
-
error_msg += "• Ensure the image is clearly medical in nature"
|
115 |
-
|
116 |
-
return False, error_msg
|
117 |
|
118 |
def create_medical_prompt(clinical_data: Dict, analysis_type: str, focus_areas: str) -> str:
|
119 |
"""Create optimized medical analysis prompt for Gemini"""
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
**ANALYSIS INSTRUCTIONS
|
124 |
-
|
125 |
-
-
|
126 |
-
-
|
127 |
-
-
|
128 |
-
-
|
129 |
-
|
130 |
-
- This is for educational and research purposes only
|
131 |
-
"""
|
132 |
-
|
133 |
-
# Add clinical context if provided
|
134 |
clinical_context = ""
|
135 |
if clinical_data and any(v.strip() for v in clinical_data.values() if v):
|
136 |
clinical_context = "\n**CLINICAL CONTEXT:**\n"
|
@@ -140,87 +61,76 @@ Analyze this medical image systematically and professionally:
|
|
140 |
if clinical_data.get("symptoms"): context_items.append(f"Presenting Symptoms: {clinical_data['symptoms']}")
|
141 |
if clinical_data.get("history"): context_items.append(f"Medical History: {clinical_data['history']}")
|
142 |
if clinical_data.get("medications"): context_items.append(f"Current Medications: {clinical_data['medications']}")
|
143 |
-
|
144 |
clinical_context += "\n".join(f"• {item}" for item in context_items) + "\n"
|
145 |
-
|
146 |
-
# Analysis type specific instructions
|
147 |
analysis_instructions = {
|
148 |
"Comprehensive": """
|
149 |
**PROVIDE COMPREHENSIVE ANALYSIS WITH THESE SECTIONS:**
|
150 |
-
|
151 |
## 1. IMAGE ASSESSMENT
|
152 |
- Image type, quality, and technical adequacy
|
153 |
- Anatomical structures and regions visible
|
154 |
- Any artifacts or limitations
|
155 |
-
|
156 |
## 2. CLINICAL FINDINGS
|
157 |
- Normal anatomical structures observed
|
158 |
- Abnormal findings or variations from normal
|
159 |
- Specific measurements or quantitative observations if applicable
|
160 |
-
|
161 |
## 3. CLINICAL INTERPRETATION
|
162 |
- Significance of the findings
|
163 |
- Differential diagnostic considerations
|
164 |
- Correlation with provided clinical history
|
165 |
-
|
166 |
## 4. RECOMMENDATIONS
|
167 |
- Suggested next steps or additional imaging
|
168 |
- Clinical correlation recommendations
|
169 |
- Follow-up suggestions
|
170 |
-
|
171 |
## 5. LIMITATIONS & DISCLAIMERS
|
172 |
- What cannot be determined from this image alone
|
173 |
- Need for clinical correlation and professional evaluation
|
174 |
""",
|
175 |
"Quick Assessment": """
|
176 |
**PROVIDE FOCUSED QUICK ASSESSMENT:**
|
177 |
-
|
178 |
## KEY FINDINGS
|
179 |
- Most significant observations
|
180 |
- Normal vs abnormal structures
|
181 |
-
|
182 |
## CLINICAL IMPRESSION
|
183 |
- Primary considerations based on image
|
184 |
- Any urgent findings that require immediate attention
|
185 |
-
|
186 |
## IMMEDIATE RECOMMENDATIONS
|
187 |
- Essential next steps
|
188 |
- Urgency level assessment
|
189 |
-
|
190 |
## LIMITATIONS
|
191 |
- Important caveats about this assessment
|
192 |
""",
|
193 |
"Educational": """
|
194 |
**PROVIDE EDUCATIONAL ANALYSIS:**
|
195 |
-
|
196 |
## LEARNING OBJECTIVES
|
197 |
- Key educational points from this case
|
198 |
- Important anatomical or pathological concepts
|
199 |
-
|
200 |
## NORMAL vs ABNORMAL
|
201 |
- Clear explanation of what's normal in this image
|
202 |
- Detailed description of any abnormal findings
|
203 |
-
|
204 |
## CLINICAL CORRELATION
|
205 |
- How image findings relate to symptoms/history
|
206 |
- Real-world clinical significance
|
207 |
-
|
208 |
## TEACHING PEARLS
|
209 |
- Important concepts this case demonstrates
|
210 |
- Common pitfalls or considerations
|
211 |
"""
|
212 |
}
|
213 |
-
|
214 |
focus_instruction = ""
|
215 |
if focus_areas and focus_areas.strip():
|
216 |
focus_instruction = f"\n**SPECIAL FOCUS AREAS**: Pay particular attention to: {focus_areas}\n"
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
|
|
|
|
|
|
|
|
|
|
224 |
|
225 |
def analyze_medical_image_gemini(
|
226 |
image: Image.Image,
|
@@ -231,397 +141,86 @@ def analyze_medical_image_gemini(
|
|
231 |
medications: str,
|
232 |
analysis_type: str,
|
233 |
focus_areas: str,
|
234 |
-
api_key: str,
|
235 |
progress=gr.Progress()
|
236 |
) -> Tuple[str, str, str]:
|
237 |
"""Main analysis function using Gemini Vision Pro"""
|
238 |
-
|
239 |
if image is None:
|
240 |
return "❌ Please upload an image first.", "", "❌ No image provided"
|
241 |
-
|
242 |
-
# Setup Gemini if needed
|
243 |
progress(0.1, desc="Connecting to Gemini...")
|
244 |
-
success, status = setup_gemini(
|
245 |
if not success:
|
246 |
return status, "", status
|
247 |
-
|
248 |
try:
|
249 |
progress(0.3, desc="Preparing analysis...")
|
250 |
-
|
251 |
-
# Prepare clinical data
|
252 |
clinical_data = {
|
253 |
-
"age": age.strip()
|
254 |
-
"gender": gender
|
255 |
-
"symptoms": symptoms.strip()
|
256 |
-
"history": history.strip()
|
257 |
-
"medications": medications.strip()
|
258 |
}
|
259 |
-
|
260 |
-
# Create prompt
|
261 |
prompt = create_medical_prompt(clinical_data, analysis_type, focus_areas)
|
262 |
-
|
263 |
progress(0.5, desc="Analyzing image with Gemini...")
|
264 |
-
|
265 |
-
# Configure generation parameters for better results
|
266 |
-
generation_config = genai.types.GenerationConfig(
|
267 |
-
temperature=0.3, # Lower temperature for more consistent medical analysis
|
268 |
-
max_output_tokens=2048, # Sufficient for detailed analysis
|
269 |
-
top_p=0.8,
|
270 |
-
top_k=40
|
271 |
-
)
|
272 |
-
|
273 |
-
# Generate analysis using Gemini Vision Pro
|
274 |
-
response = GEMINI_MODEL.generate_content(
|
275 |
-
[prompt, image],
|
276 |
-
generation_config=generation_config
|
277 |
-
)
|
278 |
-
|
279 |
if not response or not response.text:
|
280 |
-
return "❌ No response received from Gemini API
|
281 |
-
|
282 |
progress(0.9, desc="Preparing results...")
|
283 |
-
|
284 |
-
# Create download content
|
285 |
report_data = {
|
286 |
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC"),
|
287 |
"model": "Google Gemini-1.5-Pro Vision",
|
288 |
"analysis_type": analysis_type,
|
289 |
"clinical_data": clinical_data,
|
290 |
-
"focus_areas": focus_areas
|
291 |
"analysis": response.text
|
292 |
}
|
293 |
-
|
294 |
download_content = json.dumps(report_data, indent=2)
|
295 |
-
|
296 |
progress(1.0, desc="Analysis complete!")
|
297 |
-
|
298 |
return response.text, download_content, "✅ Analysis completed successfully"
|
299 |
-
|
300 |
except Exception as e:
|
301 |
error_msg = f"❌ Analysis failed: {str(e)}"
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
error_msg += "\n\n💡
|
306 |
-
|
307 |
-
error_msg += "
|
308 |
-
error_msg += "• Consider upgrading your plan"
|
309 |
-
|
310 |
-
elif "safety" in str(e).lower() or "blocked" in str(e).lower():
|
311 |
-
error_msg += "\n\n💡 **Safety Filter:** The image was blocked. Try:\n"
|
312 |
-
error_msg += "• A different medical image\n"
|
313 |
-
error_msg += "• Ensuring the image is clearly medical\n"
|
314 |
-
error_msg += "• Removing any potentially sensitive content"
|
315 |
-
|
316 |
-
elif "401" in str(e) or "403" in str(e):
|
317 |
-
error_msg += "\n\n💡 **Authentication Error:** Check your API key"
|
318 |
-
|
319 |
return error_msg, "", error_msg
|
320 |
|
321 |
def create_interface():
|
322 |
"""Create the Gradio interface for Gemini Vision Pro"""
|
323 |
-
|
324 |
-
# Custom CSS for medical theme
|
325 |
css = """
|
326 |
-
.gradio-container {
|
327 |
-
|
328 |
-
}
|
329 |
-
.
|
330 |
-
text-align: center;
|
331 |
-
color: #1a73e8;
|
332 |
-
margin-bottom: 20px;
|
333 |
-
}
|
334 |
-
.api-section {
|
335 |
-
background-color: #e8f0fe;
|
336 |
-
padding: 15px;
|
337 |
-
border-radius: 8px;
|
338 |
-
margin: 10px 0;
|
339 |
-
border-left: 4px solid #1a73e8;
|
340 |
-
}
|
341 |
-
.status-success {
|
342 |
-
color: #137333;
|
343 |
-
font-weight: 500;
|
344 |
-
}
|
345 |
-
.status-error {
|
346 |
-
color: #d93025;
|
347 |
-
font-weight: 500;
|
348 |
-
}
|
349 |
-
.quota-info {
|
350 |
-
background-color: #fff3cd;
|
351 |
-
padding: 10px;
|
352 |
-
border-radius: 5px;
|
353 |
-
margin: 10px 0;
|
354 |
-
border-left: 3px solid #ffc107;
|
355 |
-
}
|
356 |
"""
|
357 |
-
|
358 |
with gr.Blocks(css=css, theme=gr.themes.Soft(), title="Gemini Medical AI") as interface:
|
359 |
-
|
360 |
-
# Header
|
361 |
-
gr.HTML("""
|
362 |
-
<div class="medical-header">
|
363 |
-
<h1>🏥 Medical Image AI Analyzer</h1>
|
364 |
-
<h2>🤖 Powered by Google Gemini Vision Pro</h2>
|
365 |
-
<p><em>Fast, efficient medical image analysis using Google's latest AI</em></p>
|
366 |
-
</div>
|
367 |
-
""")
|
368 |
-
|
369 |
-
# Check for existing API key on startup
|
370 |
-
initial_api_key = get_api_key_from_secrets()
|
371 |
-
|
372 |
-
# API Configuration Section
|
373 |
-
with gr.Accordion("🔑 API Configuration", open=not bool(initial_api_key)):
|
374 |
-
if initial_api_key:
|
375 |
-
gr.Markdown("""
|
376 |
-
### ✅ API Key Found in Secrets
|
377 |
-
An API key was found in the environment/secrets.
|
378 |
-
""")
|
379 |
-
else:
|
380 |
-
gr.Markdown("""
|
381 |
-
### 🔐 Google Gemini API Setup Required
|
382 |
-
You need a Google API key to use Gemini Vision Pro.
|
383 |
-
|
384 |
-
**For HuggingFace Spaces:** Add your API key as a secret named `GOOGLE_API_KEY`
|
385 |
-
**For local use:** Set the `GOOGLE_API_KEY` environment variable or enter it below
|
386 |
-
|
387 |
-
🔗 Get your API key from: [Google AI Studio](https://makersuite.google.com/app/apikey)
|
388 |
-
""")
|
389 |
-
|
390 |
-
gr.HTML("""
|
391 |
-
<div class="quota-info">
|
392 |
-
<strong>💰 Important:</strong> Make sure your Google API key has sufficient quota and billing is set up.
|
393 |
-
The 429 error indicates quota limits have been exceeded.
|
394 |
-
</div>
|
395 |
-
""")
|
396 |
-
|
397 |
-
api_key_input = gr.Textbox(
|
398 |
-
label="Google API Key (Optional Override)",
|
399 |
-
type="password",
|
400 |
-
placeholder="Enter your Google API key here to override secrets...",
|
401 |
-
info="Leave empty to use API key from secrets/environment",
|
402 |
-
value=""
|
403 |
-
)
|
404 |
-
|
405 |
-
status_display = gr.Textbox(
|
406 |
-
label="Connection Status",
|
407 |
-
value="⏳ Testing connection..." if initial_api_key else "⏳ Enter API key to connect",
|
408 |
-
interactive=False
|
409 |
-
)
|
410 |
-
|
411 |
with gr.Row():
|
412 |
-
|
413 |
-
|
414 |
-
gr.
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
height=300,
|
420 |
-
sources=["upload", "clipboard", "webcam"]
|
421 |
-
)
|
422 |
-
|
423 |
-
gr.Markdown("*Supported: X-rays, CT, MRI, photographs, microscopy, dermatology images, etc.*")
|
424 |
-
|
425 |
-
gr.Markdown("## 📋 Clinical Information")
|
426 |
-
|
427 |
-
with gr.Group():
|
428 |
-
with gr.Row():
|
429 |
-
age_input = gr.Textbox(
|
430 |
-
label="Patient Age",
|
431 |
-
placeholder="e.g., 45 years",
|
432 |
-
max_lines=1
|
433 |
-
)
|
434 |
-
gender_input = gr.Dropdown(
|
435 |
-
choices=["", "Male", "Female", "Other"],
|
436 |
-
label="Gender",
|
437 |
-
value=""
|
438 |
-
)
|
439 |
-
|
440 |
-
symptoms_input = gr.Textbox(
|
441 |
-
label="Chief Complaint / Symptoms",
|
442 |
-
placeholder="e.g., Chest pain, shortness of breath for 3 days",
|
443 |
-
lines=2
|
444 |
-
)
|
445 |
-
|
446 |
-
history_input = gr.Textbox(
|
447 |
-
label="Medical History",
|
448 |
-
placeholder="e.g., Hypertension, diabetes, previous surgeries",
|
449 |
-
lines=2
|
450 |
-
)
|
451 |
-
|
452 |
-
medications_input = gr.Textbox(
|
453 |
-
label="Current Medications",
|
454 |
-
placeholder="e.g., Metformin, Lisinopril, Aspirin",
|
455 |
-
lines=2
|
456 |
-
)
|
457 |
-
|
458 |
-
gr.Markdown("## ⚙️ Analysis Settings")
|
459 |
-
|
460 |
analysis_type = gr.Radio(
|
461 |
choices=["Comprehensive", "Quick Assessment", "Educational"],
|
462 |
-
label="Analysis Type"
|
463 |
-
value="Comprehensive",
|
464 |
-
info="Choose the depth and focus of analysis"
|
465 |
-
)
|
466 |
-
|
467 |
-
focus_areas = gr.Textbox(
|
468 |
-
label="Focus Areas (Optional)",
|
469 |
-
placeholder="e.g., cardiac silhouette, lung fields, bone density",
|
470 |
-
info="Specific areas to emphasize in analysis"
|
471 |
)
|
472 |
-
|
473 |
-
analyze_btn = gr.Button(
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
)
|
478 |
-
|
479 |
-
# Right column - Results
|
480 |
-
with gr.Column(scale=1):
|
481 |
-
gr.Markdown("## 🤖 AI Analysis Results")
|
482 |
-
|
483 |
-
analysis_output = gr.Textbox(
|
484 |
-
label="Medical Analysis",
|
485 |
-
lines=25,
|
486 |
-
max_lines=35,
|
487 |
-
show_copy_button=True,
|
488 |
-
placeholder="Analysis results will appear here after processing..."
|
489 |
-
)
|
490 |
-
|
491 |
-
download_file = gr.File(
|
492 |
-
label="📥 Download Analysis Report",
|
493 |
-
visible=False
|
494 |
-
)
|
495 |
-
|
496 |
-
# Hidden component to store download content
|
497 |
-
download_content = gr.Textbox(visible=False)
|
498 |
-
|
499 |
-
# Information sections
|
500 |
-
with gr.Accordion("🔧 Troubleshooting Common Issues", open=False):
|
501 |
-
gr.Markdown("""
|
502 |
-
### 🚨 **Error 429 - Quota Exceeded:**
|
503 |
-
- **Check billing:** Ensure your Google Cloud project has billing enabled
|
504 |
-
- **API limits:** You may have hit free tier limits - consider upgrading
|
505 |
-
- **Rate limiting:** Wait a few minutes and try again
|
506 |
-
- **Multiple keys:** Try generating a new API key
|
507 |
-
|
508 |
-
### 🔐 **For HuggingFace Spaces Users:**
|
509 |
-
1. Go to your Space settings
|
510 |
-
2. Add a new secret: `GOOGLE_API_KEY`
|
511 |
-
3. Paste your Google API key as the value
|
512 |
-
4. Restart your Space
|
513 |
-
|
514 |
-
### 📊 **Monitor Usage:**
|
515 |
-
- [Google AI Studio Usage](https://makersuite.google.com/)
|
516 |
-
- [Google Cloud Console](https://console.cloud.google.com/)
|
517 |
-
|
518 |
-
### 🔄 **If Still Having Issues:**
|
519 |
-
- Try a different API key
|
520 |
-
- Check if Gemini API is enabled in your project
|
521 |
-
- Verify your Google Cloud project settings
|
522 |
-
""")
|
523 |
-
|
524 |
-
with gr.Accordion("💡 About Gemini Vision Pro", open=False):
|
525 |
-
gr.Markdown("""
|
526 |
-
### 🚀 **Advantages of Gemini Vision Pro:**
|
527 |
-
- **Fast Processing**: No local model loading - results in seconds
|
528 |
-
- **Low Resource Usage**: Runs via API calls, minimal local computing needed
|
529 |
-
- **High Quality**: Google's latest multimodal AI model
|
530 |
-
- **Always Updated**: Access to the latest model improvements
|
531 |
-
- **Reliable**: Enterprise-grade infrastructure
|
532 |
-
|
533 |
-
### 🔍 **Supported Medical Images:**
|
534 |
-
- **Radiology**: X-rays, CT scans, MRI images, Ultrasound
|
535 |
-
- **Pathology**: Histological slides, Cytology specimens
|
536 |
-
- **Dermatology**: Skin lesions, Rashes, Clinical photos
|
537 |
-
- **Ophthalmology**: Fundus photos, OCT images
|
538 |
-
- **Clinical Photography**: Wound assessment, Physical findings
|
539 |
-
- **Microscopy**: Cellular and tissue analysis
|
540 |
-
|
541 |
-
### 💰 **Cost Information:**
|
542 |
-
- Gemini Vision Pro uses pay-per-use pricing
|
543 |
-
- Typically very affordable for individual analyses
|
544 |
-
- Check [Google AI Pricing](https://ai.google.dev/pricing) for current rates
|
545 |
-
""")
|
546 |
-
|
547 |
-
# Footer
|
548 |
-
gr.HTML("""
|
549 |
-
<div style="text-align: center; margin-top: 20px; padding: 15px; background-color: #fff3cd; border-radius: 8px;">
|
550 |
-
<strong>⚠️ Medical Disclaimer:</strong> This AI tool is for educational and research purposes only.
|
551 |
-
It should never replace professional medical diagnosis or treatment.
|
552 |
-
Always consult qualified healthcare providers for medical decisions.
|
553 |
-
</div>
|
554 |
-
""")
|
555 |
-
|
556 |
-
# Event handlers
|
557 |
-
def create_download_file(content):
|
558 |
-
if content:
|
559 |
-
filename = f"gemini_medical_analysis_{int(time.time())}.json"
|
560 |
-
with open(filename, "w") as f:
|
561 |
-
f.write(content)
|
562 |
-
return gr.File(value=filename, visible=True)
|
563 |
-
return gr.File(visible=False)
|
564 |
-
|
565 |
-
def test_api_connection(api_key_override):
|
566 |
-
# Use override if provided, otherwise use secrets
|
567 |
-
test_key = api_key_override if api_key_override.strip() else None
|
568 |
-
success, status = setup_gemini(test_key)
|
569 |
-
return status
|
570 |
-
|
571 |
-
# Test connection on startup if we have an API key
|
572 |
-
if initial_api_key:
|
573 |
-
def initial_connection_test():
|
574 |
-
return test_api_connection("")
|
575 |
-
|
576 |
-
interface.load(
|
577 |
-
fn=initial_connection_test,
|
578 |
-
outputs=[status_display]
|
579 |
-
)
|
580 |
-
|
581 |
-
# API key testing
|
582 |
-
api_key_input.change(
|
583 |
-
fn=test_api_connection,
|
584 |
-
inputs=[api_key_input],
|
585 |
-
outputs=[status_display]
|
586 |
-
)
|
587 |
-
|
588 |
-
# Main analysis
|
589 |
analyze_btn.click(
|
590 |
-
|
591 |
-
inputs=[
|
592 |
-
|
593 |
-
history_input, medications_input, analysis_type, focus_areas, api_key_input
|
594 |
-
],
|
595 |
-
outputs=[analysis_output, download_content, status_display]
|
596 |
-
).then(
|
597 |
-
fn=create_download_file,
|
598 |
-
inputs=[download_content],
|
599 |
-
outputs=[download_file]
|
600 |
)
|
601 |
-
|
602 |
return interface
|
603 |
|
604 |
if __name__ == "__main__":
|
605 |
-
|
606 |
-
|
607 |
-
|
608 |
-
# Check for API key
|
609 |
-
api_key_found = get_api_key_from_secrets()
|
610 |
-
if api_key_found:
|
611 |
-
print("✅ API key found in environment/secrets")
|
612 |
-
else:
|
613 |
-
print("⚠️ No API key found - user will need to provide one")
|
614 |
-
|
615 |
-
print("🚀 No local model loading required - using Google Gemini Vision Pro API")
|
616 |
-
|
617 |
-
# Create and launch interface
|
618 |
-
interface = create_interface()
|
619 |
-
|
620 |
-
# Launch with optimized settings
|
621 |
-
interface.launch(
|
622 |
-
server_name="0.0.0.0",
|
623 |
-
server_port=7860,
|
624 |
-
share=False,
|
625 |
-
show_error=True,
|
626 |
-
quiet=False
|
627 |
-
)
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
+
Gemini Vision Pro Medical Image Analysis - Secure Gradio Interface
|
4 |
+
API key is loaded only from environment variables or .env file (never exposed to users)
|
5 |
"""
|
6 |
|
7 |
import gradio as gr
|
|
|
10 |
import json
|
11 |
import time
|
12 |
import os
|
13 |
+
from typing import Dict, Tuple
|
14 |
+
from dotenv import load_dotenv # pip install python-dotenv
|
|
|
15 |
import warnings
|
16 |
+
|
17 |
warnings.filterwarnings("ignore")
|
18 |
|
19 |
+
# Load environment variables from .env if present
|
20 |
+
load_dotenv()
|
|
|
21 |
|
22 |
+
# Global model instance
|
23 |
+
GEMINI_MODEL = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
+
def setup_gemini():
|
26 |
+
"""Initialize Gemini Vision Pro using API key from environment (never exposed to users)"""
|
27 |
+
global GEMINI_MODEL
|
28 |
+
api_key = os.getenv('GOOGLE_API_KEY') or os.getenv('GEMINI_API_KEY')
|
29 |
+
if not api_key:
|
30 |
+
return False, "❌ No API key found. Please set GOOGLE_API_KEY or GEMINI_API_KEY in your environment or .env file."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
try:
|
32 |
+
genai.configure(api_key=api_key)
|
33 |
+
GEMINI_MODEL = genai.GenerativeModel('gemini-1.5-pro')
|
34 |
+
# Test connection
|
35 |
+
test_response = GEMINI_MODEL.generate_content("Hello, can you help with medical image analysis?")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
if test_response and test_response.text:
|
37 |
return True, "✅ Gemini Vision Pro connected successfully!"
|
38 |
else:
|
39 |
+
return False, "❌ Failed to connect to Gemini API"
|
|
|
40 |
except Exception as e:
|
41 |
+
return False, f"❌ Gemini setup failed: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
|
43 |
def create_medical_prompt(clinical_data: Dict, analysis_type: str, focus_areas: str) -> str:
|
44 |
"""Create optimized medical analysis prompt for Gemini"""
|
45 |
+
base_prompt = (
|
46 |
+
"You are an expert medical AI assistant specializing in medical image analysis. "
|
47 |
+
"You have extensive training across radiology, pathology, dermatology, ophthalmology, and clinical medicine.\n"
|
48 |
+
"**ANALYSIS INSTRUCTIONS:**\n"
|
49 |
+
"- Use clear medical terminology with explanations for complex terms\n"
|
50 |
+
"- Structure your response with clear sections and headers\n"
|
51 |
+
"- Be thorough but concise\n"
|
52 |
+
"- Always mention limitations and emphasize the need for professional medical consultation\n"
|
53 |
+
"- Focus on observable findings rather than definitive diagnoses\n"
|
54 |
+
)
|
|
|
|
|
|
|
|
|
55 |
clinical_context = ""
|
56 |
if clinical_data and any(v.strip() for v in clinical_data.values() if v):
|
57 |
clinical_context = "\n**CLINICAL CONTEXT:**\n"
|
|
|
61 |
if clinical_data.get("symptoms"): context_items.append(f"Presenting Symptoms: {clinical_data['symptoms']}")
|
62 |
if clinical_data.get("history"): context_items.append(f"Medical History: {clinical_data['history']}")
|
63 |
if clinical_data.get("medications"): context_items.append(f"Current Medications: {clinical_data['medications']}")
|
|
|
64 |
clinical_context += "\n".join(f"• {item}" for item in context_items) + "\n"
|
65 |
+
|
|
|
66 |
analysis_instructions = {
|
67 |
"Comprehensive": """
|
68 |
**PROVIDE COMPREHENSIVE ANALYSIS WITH THESE SECTIONS:**
|
|
|
69 |
## 1. IMAGE ASSESSMENT
|
70 |
- Image type, quality, and technical adequacy
|
71 |
- Anatomical structures and regions visible
|
72 |
- Any artifacts or limitations
|
|
|
73 |
## 2. CLINICAL FINDINGS
|
74 |
- Normal anatomical structures observed
|
75 |
- Abnormal findings or variations from normal
|
76 |
- Specific measurements or quantitative observations if applicable
|
|
|
77 |
## 3. CLINICAL INTERPRETATION
|
78 |
- Significance of the findings
|
79 |
- Differential diagnostic considerations
|
80 |
- Correlation with provided clinical history
|
|
|
81 |
## 4. RECOMMENDATIONS
|
82 |
- Suggested next steps or additional imaging
|
83 |
- Clinical correlation recommendations
|
84 |
- Follow-up suggestions
|
|
|
85 |
## 5. LIMITATIONS & DISCLAIMERS
|
86 |
- What cannot be determined from this image alone
|
87 |
- Need for clinical correlation and professional evaluation
|
88 |
""",
|
89 |
"Quick Assessment": """
|
90 |
**PROVIDE FOCUSED QUICK ASSESSMENT:**
|
|
|
91 |
## KEY FINDINGS
|
92 |
- Most significant observations
|
93 |
- Normal vs abnormal structures
|
|
|
94 |
## CLINICAL IMPRESSION
|
95 |
- Primary considerations based on image
|
96 |
- Any urgent findings that require immediate attention
|
|
|
97 |
## IMMEDIATE RECOMMENDATIONS
|
98 |
- Essential next steps
|
99 |
- Urgency level assessment
|
|
|
100 |
## LIMITATIONS
|
101 |
- Important caveats about this assessment
|
102 |
""",
|
103 |
"Educational": """
|
104 |
**PROVIDE EDUCATIONAL ANALYSIS:**
|
|
|
105 |
## LEARNING OBJECTIVES
|
106 |
- Key educational points from this case
|
107 |
- Important anatomical or pathological concepts
|
|
|
108 |
## NORMAL vs ABNORMAL
|
109 |
- Clear explanation of what's normal in this image
|
110 |
- Detailed description of any abnormal findings
|
|
|
111 |
## CLINICAL CORRELATION
|
112 |
- How image findings relate to symptoms/history
|
113 |
- Real-world clinical significance
|
|
|
114 |
## TEACHING PEARLS
|
115 |
- Important concepts this case demonstrates
|
116 |
- Common pitfalls or considerations
|
117 |
"""
|
118 |
}
|
|
|
119 |
focus_instruction = ""
|
120 |
if focus_areas and focus_areas.strip():
|
121 |
focus_instruction = f"\n**SPECIAL FOCUS AREAS**: Pay particular attention to: {focus_areas}\n"
|
122 |
+
disclaimer = (
|
123 |
+
"**IMPORTANT MEDICAL DISCLAIMER**: This AI-powered analysis is for educational and research purposes only. "
|
124 |
+
"It should never replace professional medical diagnosis, treatment, or consultation with qualified healthcare providers. "
|
125 |
+
"Always seek professional medical advice for any health concerns or medical decisions."
|
126 |
+
)
|
127 |
+
return (
|
128 |
+
base_prompt
|
129 |
+
+ clinical_context
|
130 |
+
+ analysis_instructions.get(analysis_type, analysis_instructions["Comprehensive"])
|
131 |
+
+ focus_instruction
|
132 |
+
+ disclaimer
|
133 |
+
)
|
134 |
|
135 |
def analyze_medical_image_gemini(
|
136 |
image: Image.Image,
|
|
|
141 |
medications: str,
|
142 |
analysis_type: str,
|
143 |
focus_areas: str,
|
|
|
144 |
progress=gr.Progress()
|
145 |
) -> Tuple[str, str, str]:
|
146 |
"""Main analysis function using Gemini Vision Pro"""
|
|
|
147 |
if image is None:
|
148 |
return "❌ Please upload an image first.", "", "❌ No image provided"
|
|
|
|
|
149 |
progress(0.1, desc="Connecting to Gemini...")
|
150 |
+
success, status = setup_gemini()
|
151 |
if not success:
|
152 |
return status, "", status
|
|
|
153 |
try:
|
154 |
progress(0.3, desc="Preparing analysis...")
|
|
|
|
|
155 |
clinical_data = {
|
156 |
+
"age": age.strip(),
|
157 |
+
"gender": gender,
|
158 |
+
"symptoms": symptoms.strip(),
|
159 |
+
"history": history.strip(),
|
160 |
+
"medications": medications.strip()
|
161 |
}
|
|
|
|
|
162 |
prompt = create_medical_prompt(clinical_data, analysis_type, focus_areas)
|
|
|
163 |
progress(0.5, desc="Analyzing image with Gemini...")
|
164 |
+
response = GEMINI_MODEL.generate_content([prompt, image])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
if not response or not response.text:
|
166 |
+
return "❌ No response received from Gemini API", "", "❌ Analysis failed"
|
|
|
167 |
progress(0.9, desc="Preparing results...")
|
|
|
|
|
168 |
report_data = {
|
169 |
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC"),
|
170 |
"model": "Google Gemini-1.5-Pro Vision",
|
171 |
"analysis_type": analysis_type,
|
172 |
"clinical_data": clinical_data,
|
173 |
+
"focus_areas": focus_areas,
|
174 |
"analysis": response.text
|
175 |
}
|
|
|
176 |
download_content = json.dumps(report_data, indent=2)
|
|
|
177 |
progress(1.0, desc="Analysis complete!")
|
|
|
178 |
return response.text, download_content, "✅ Analysis completed successfully"
|
|
|
179 |
except Exception as e:
|
180 |
error_msg = f"❌ Analysis failed: {str(e)}"
|
181 |
+
if "API_KEY" in str(e):
|
182 |
+
error_msg += "\n\n💡 Tip: Make sure your Google API key is valid and has access to Gemini API"
|
183 |
+
elif "quota" in str(e).lower():
|
184 |
+
error_msg += "\n\n💡 Tip: You may have exceeded your API quota. Check your Google Cloud Console"
|
185 |
+
elif "safety" in str(e).lower():
|
186 |
+
error_msg += "\n\n💡 Tip: The image may have been blocked by safety filters. Try a different medical image"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
return error_msg, "", error_msg
|
188 |
|
189 |
def create_interface():
|
190 |
"""Create the Gradio interface for Gemini Vision Pro"""
|
|
|
|
|
191 |
css = """
|
192 |
+
.gradio-container { max-width: 1400px !important; }
|
193 |
+
.medical-header { text-align: center; color: #1a73e8; margin-bottom: 20px; }
|
194 |
+
.status-success { color: #137333; font-weight: 500; }
|
195 |
+
.status-error { color: #d93025; font-weight: 500; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
"""
|
|
|
197 |
with gr.Blocks(css=css, theme=gr.themes.Soft(), title="Gemini Medical AI") as interface:
|
198 |
+
gr.HTML("<h1 class='medical-header'>Gemini Vision Pro Medical Image Analysis</h1><p>*Fast, efficient medical image analysis using Google's latest AI*</p>")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
with gr.Row():
|
200 |
+
with gr.Column():
|
201 |
+
image_input = gr.Image(type="pil", label="Upload a medical image")
|
202 |
+
age = gr.Textbox(label="Patient Age", placeholder="e.g. 47")
|
203 |
+
gender = gr.Dropdown(choices=["Male", "Female", "Other", "Unknown"], label="Gender", value="Unknown")
|
204 |
+
symptoms = gr.Textbox(label="Symptoms", placeholder="Describe presenting symptoms")
|
205 |
+
history = gr.Textbox(label="Medical History", placeholder="Relevant medical history")
|
206 |
+
medications = gr.Textbox(label="Current Medications", placeholder="List current medications")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
analysis_type = gr.Radio(
|
208 |
choices=["Comprehensive", "Quick Assessment", "Educational"],
|
209 |
+
value="Comprehensive", label="Analysis Type"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
)
|
211 |
+
focus_areas = gr.Textbox(label="Special Focus Areas", placeholder="e.g. lung nodules, bone lesions")
|
212 |
+
analyze_btn = gr.Button("Analyze Image", variant="primary")
|
213 |
+
with gr.Column():
|
214 |
+
status = gr.Markdown(value="", elem_classes="status-success")
|
215 |
+
output = gr.Markdown(label="AI Medical Analysis")
|
216 |
+
download = gr.DownloadButton(label="Download Report", file_name="analysis_report.json")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
analyze_btn.click(
|
218 |
+
analyze_medical_image_gemini,
|
219 |
+
inputs=[image_input, age, gender, symptoms, history, medications, analysis_type, focus_areas],
|
220 |
+
outputs=[output, download, status]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
)
|
|
|
222 |
return interface
|
223 |
|
224 |
if __name__ == "__main__":
|
225 |
+
demo = create_interface()
|
226 |
+
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|