Spaces:
Sleeping
Sleeping
File size: 6,972 Bytes
8c5e3a4 4322841 8c5e3a4 4322841 4e8ce70 8c5e3a4 4322841 3c07b8f 7553f81 3c07b8f 5cad372 7553f81 3c07b8f 4322841 3c07b8f 812891f d5e6c7a 3c07b8f 8c5e3a4 4322841 8c5e3a4 4322841 8c5e3a4 4322841 8c5e3a4 4322841 3c07b8f 4322841 3c07b8f 4322841 3c07b8f 4322841 8c5e3a4 4322841 8c5e3a4 4322841 8c5e3a4 3c07b8f 4322841 8c5e3a4 7553f81 8c5e3a4 4322841 8c5e3a4 4322841 8c5e3a4 4322841 3c07b8f 8c5e3a4 3c07b8f 8c5e3a4 4322841 8c5e3a4 4322841 3c07b8f 4322841 3c07b8f 4322841 3c07b8f d5e6c7a 4322841 8c5e3a4 4322841 3c07b8f 4322841 3c07b8f 4322841 3c07b8f 7553f81 4322841 3c07b8f 4322841 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
import gradio as gr
import requests
import base64
import io
import time
import os
from PIL import Image
# ==========================
# CONFIGURATION
# ==========================
MODEL_ID = "gemini-2.5-pro"
API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL_ID}:generateContent"
MAX_RETRIES = 3
GEMINI_ENV_KEY = "GEMINI_API_KEY"
SYSTEM_INSTRUCTION = (
"""
You are an AI dermatology assistant designed to help patients understand possible skin conditions from images.
Your goal is to offer an informative, empathetic explanation in clear, simple language.
Avoid technical jargon.
Keep your answer brief and concise.
No need to give any disclaimer or introduction.
Generate answers in reportable Bengali language.
"""
)
USER_QUERY = (
"""
Analyze this image of a skin lesion and provide:
1. A brief description of what you observed in the image that supports your conclusion.
2. The diagnosis as Disease Name.
3. A brief explanation of the condition for the patient — including typical symptoms, possible causes, and self-care advice.
"""
)
# ==========================
# HELPER FUNCTIONS
# ==========================
def pil_to_base64(image: Image.Image) -> str:
"""Convert PIL Image to base64-encoded JPEG."""
buffer = io.BytesIO()
if image.mode != "RGB":
image = image.convert("RGB")
image.save(buffer, format="JPEG")
return base64.b64encode(buffer.getvalue()).decode("utf-8")
def generate_gemini_analysis(image: Image.Image, max_output_tokens=2048, temperature=0.0):
"""Send image to Gemini API and return dermatological analysis with a live spinner and status updates."""
def build_spinner_html(status: str = "Working...", hidden: bool = False) -> str:
display = "none" if hidden else "flex"
return f"""
<div style="display:{display};align-items:center;gap:12px;justify-content:center;">
<div style="display:flex;flex-direction:column;align-items:center;gap:8px;">
<div style="
width:48px;height:48px;
border:6px solid rgba(255,255,255,0.12);
border-top-color:#06b6d4;
border-radius:50%;
animation: spin 1s linear infinite;
"></div>
<div style="color:#ddd;font-size:0.95em;min-width:180px;text-align:center;">{status}</div>
</div>
</div>
<style>
@keyframes spin {{ from {{ transform: rotate(0deg); }} to {{ transform: rotate(360deg); }} }}
</style>
"""
# initial spinner
yield build_spinner_html("Initializing..."), ""
api_key = os.environ.get(GEMINI_ENV_KEY)
if not api_key:
# hide spinner when showing error/result
yield build_spinner_html("Missing API key", hidden=True), "⚠️ **Missing API key.** Set GEMINI_API_KEY as an environment variable."
return
if not image:
yield build_spinner_html("No image provided", hidden=True), "⚠️ Please upload an image."
return
yield build_spinner_html("Preparing image..."), ""
image_b64 = pil_to_base64(image)
payload = {
"contents": [
{
"role": "user",
"parts": [
{"text": USER_QUERY},
{"inlineData": {"mimeType": "image/jpeg", "data": image_b64}},
],
}
],
"systemInstruction": {"parts": [{"text": SYSTEM_INSTRUCTION}]},
"generationConfig": {"temperature": temperature, "maxOutputTokens": max_output_tokens},
}
headers = {"Content-Type": "application/json"}
# before sending
yield build_spinner_html("Sending to Gemini..."), ""
# Retry logic
for attempt in range(MAX_RETRIES):
try:
yield build_spinner_html("Working..."), ""
response = requests.post(
f"{API_URL}?key={api_key}",
headers=headers,
json=payload,
timeout=90,
)
response.raise_for_status()
data = response.json()
break
except requests.exceptions.RequestException as e:
if attempt < MAX_RETRIES - 1:
yield build_spinner_html("Network error — retrying..."), ""
time.sleep(2 ** attempt)
continue
yield build_spinner_html("Request failed", hidden=True), f"❌ Request failed: {e}"
return
yield build_spinner_html("Parsing response..."), ""
candidate = data.get("candidates", [{}])[0]
parts = candidate.get("content", {}).get("parts", [])
generated_text = "".join(part.get("text", "") for part in parts)
if not generated_text:
finish_reason = candidate.get("finishReason", "")
yield build_spinner_html("No output", hidden=True), f"⚠️ No text output from model. Finish reason: `{finish_reason}`"
return
final_md = f"### 🩺 Dermatological Result \n\n{generated_text.strip()}"
# hide spinner, show final result
yield build_spinner_html("Completed", hidden=True), final_md
# ==========================
# CUSTOM STYLING
# ==========================
css = """
#container {
margin: 0 auto;
max-width: 600px;
text-align: center;
}
#analyze-button {
display: flex;
justify-content: center;
margin-top: 15px;
}
#analyze-button button {
width: auto !important;
padding: 6px 18px !important;
border-radius: 10px !important;
font-weight: 600;
}
.sample-img {
margin-top: 25px;
text-align: center;
opacity: 0.9;
}
.sample-img img {
border-radius: 10px;
width: 220px;
border: 1px solid #444;
}
"""
# ==========================
# GRADIO UI
# ==========================
with gr.Blocks(css=css, title="Skin Disease Analyzer") as demo:
with gr.Column(elem_id="container"):
gr.Markdown(
"""
# Skin Disease Test
**এআই ডাক্তারকে আক্রান্ত স্কিনের একটি ছবি দিন**
"""
)
image_input = gr.Image(
label="Upload Image",
type="pil",
show_label=False,
height=350,
)
# Centered analyze button (smaller)
with gr.Row(elem_id="analyze-button"):
analyze_btn = gr.Button("Test", variant="primary")
# Progress HTML + Result Markdown
progress_html = gr.HTML()
output_md = gr.Markdown(label="Result")
# Working sample image (local file)
gr.Markdown('<p style="font-size:0.9em;margin-top:50px;">📸 Sample Skin Lesion</p>')
gr.Image(value="assets/bdsd.jpg", show_label=False, interactive=False, height=300)
# Event binding
analyze_btn.click(
fn=generate_gemini_analysis,
inputs=[image_input],
outputs=[progress_html, output_md],
)
demo.launch()
|