Spaces:
Sleeping
Sleeping
Bengali and less jargon
Browse files- app.py +69 -29
- assets/skin.jpg +0 -0
- requirements.txt +0 -1
app.py
CHANGED
|
@@ -15,16 +15,23 @@ MAX_RETRIES = 3
|
|
| 15 |
GEMINI_ENV_KEY = "GEMINI_API_KEY"
|
| 16 |
|
| 17 |
SYSTEM_INSTRUCTION = (
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
)
|
| 23 |
|
| 24 |
USER_QUERY = (
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
| 28 |
)
|
| 29 |
|
| 30 |
|
|
@@ -41,13 +48,41 @@ def pil_to_base64(image: Image.Image) -> str:
|
|
| 41 |
|
| 42 |
|
| 43 |
def generate_gemini_analysis(image: Image.Image, max_output_tokens=2048, temperature=0.0):
|
| 44 |
-
"""Send image to Gemini API and return dermatological analysis."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
api_key = os.environ.get(GEMINI_ENV_KEY)
|
| 46 |
if not api_key:
|
| 47 |
-
|
|
|
|
|
|
|
| 48 |
|
| 49 |
if not image:
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
image_b64 = pil_to_base64(image)
|
| 53 |
|
|
@@ -67,9 +102,13 @@ def generate_gemini_analysis(image: Image.Image, max_output_tokens=2048, tempera
|
|
| 67 |
|
| 68 |
headers = {"Content-Type": "application/json"}
|
| 69 |
|
|
|
|
|
|
|
|
|
|
| 70 |
# Retry logic
|
| 71 |
for attempt in range(MAX_RETRIES):
|
| 72 |
try:
|
|
|
|
| 73 |
response = requests.post(
|
| 74 |
f"{API_URL}?key={api_key}",
|
| 75 |
headers=headers,
|
|
@@ -81,9 +120,13 @@ def generate_gemini_analysis(image: Image.Image, max_output_tokens=2048, tempera
|
|
| 81 |
break
|
| 82 |
except requests.exceptions.RequestException as e:
|
| 83 |
if attempt < MAX_RETRIES - 1:
|
|
|
|
| 84 |
time.sleep(2 ** attempt)
|
| 85 |
continue
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
candidate = data.get("candidates", [{}])[0]
|
| 89 |
parts = candidate.get("content", {}).get("parts", [])
|
|
@@ -91,9 +134,13 @@ def generate_gemini_analysis(image: Image.Image, max_output_tokens=2048, tempera
|
|
| 91 |
|
| 92 |
if not generated_text:
|
| 93 |
finish_reason = candidate.get("finishReason", "")
|
| 94 |
-
|
|
|
|
| 95 |
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
|
| 99 |
# ==========================
|
|
@@ -139,8 +186,8 @@ with gr.Blocks(css=css, title="Skin Disease Analyzer") as demo:
|
|
| 139 |
with gr.Column(elem_id="container"):
|
| 140 |
gr.Markdown(
|
| 141 |
"""
|
| 142 |
-
#
|
| 143 |
-
Upload an image of a skin lesion to receive an **AI-powered dermatological
|
| 144 |
"""
|
| 145 |
)
|
| 146 |
|
|
@@ -153,28 +200,21 @@ Upload an image of a skin lesion to receive an **AI-powered dermatological analy
|
|
| 153 |
|
| 154 |
# Centered analyze button (smaller)
|
| 155 |
with gr.Row(elem_id="analyze-button"):
|
| 156 |
-
analyze_btn = gr.Button("
|
| 157 |
|
|
|
|
|
|
|
| 158 |
output_md = gr.Markdown(label="Result")
|
| 159 |
|
| 160 |
-
# Working sample image (
|
| 161 |
-
gr.
|
| 162 |
-
|
| 163 |
-
<div class="sample-img">
|
| 164 |
-
<p>📸 Sample Image:</p>
|
| 165 |
-
<img src="https://en.wikipedia.org/wiki/Skin_cancer#/media/File:Melanoma.jpg"
|
| 166 |
-
alt="Sample Skin Lesion"
|
| 167 |
-
onerror="this.style.display='none'">
|
| 168 |
-
<p style="font-size: 0.9em; margin-top: 5px;">Sample Skin Lesion</p>
|
| 169 |
-
</div>
|
| 170 |
-
"""
|
| 171 |
-
)
|
| 172 |
|
| 173 |
# Event binding
|
| 174 |
analyze_btn.click(
|
| 175 |
fn=generate_gemini_analysis,
|
| 176 |
inputs=[image_input],
|
| 177 |
-
outputs=output_md,
|
| 178 |
)
|
| 179 |
|
| 180 |
demo.launch()
|
|
|
|
| 15 |
GEMINI_ENV_KEY = "GEMINI_API_KEY"
|
| 16 |
|
| 17 |
SYSTEM_INSTRUCTION = (
|
| 18 |
+
"""
|
| 19 |
+
You are an AI dermatology assistant designed to help patients understand possible skin conditions from images.
|
| 20 |
+
Your goal is to offer an informative, empathetic explanation in clear, simple language.
|
| 21 |
+
Avoid technical jargon.
|
| 22 |
+
keep your answer brief and concise.
|
| 23 |
+
No need to give any disclaimer or introduction.
|
| 24 |
+
Your response should be in Bengali.
|
| 25 |
+
"""
|
| 26 |
)
|
| 27 |
|
| 28 |
USER_QUERY = (
|
| 29 |
+
"""
|
| 30 |
+
Analyze this image of a skin lesion and provide:
|
| 31 |
+
1. The most likely diagnosis as Disease Name.
|
| 32 |
+
2. A brief explanation of the condition for the patient — including typical symptoms, possible causes, and general self-care advice.
|
| 33 |
+
3. A brief description of what you observed in the image that supports your conclusion.
|
| 34 |
+
"""
|
| 35 |
)
|
| 36 |
|
| 37 |
|
|
|
|
| 48 |
|
| 49 |
|
| 50 |
def generate_gemini_analysis(image: Image.Image, max_output_tokens=2048, temperature=0.0):
|
| 51 |
+
"""Send image to Gemini API and return dermatological analysis with a live spinner and status updates."""
|
| 52 |
+
def build_spinner_html(status: str = "Working...", hidden: bool = False) -> str:
|
| 53 |
+
display = "none" if hidden else "flex"
|
| 54 |
+
return f"""
|
| 55 |
+
<div style="display:{display};align-items:center;gap:12px;justify-content:center;">
|
| 56 |
+
<div style="display:flex;flex-direction:column;align-items:center;gap:8px;">
|
| 57 |
+
<div style="
|
| 58 |
+
width:48px;height:48px;
|
| 59 |
+
border:6px solid rgba(255,255,255,0.12);
|
| 60 |
+
border-top-color:#06b6d4;
|
| 61 |
+
border-radius:50%;
|
| 62 |
+
animation: spin 1s linear infinite;
|
| 63 |
+
"></div>
|
| 64 |
+
<div style="color:#ddd;font-size:0.95em;min-width:180px;text-align:center;">{status}</div>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
<style>
|
| 68 |
+
@keyframes spin {{ from {{ transform: rotate(0deg); }} to {{ transform: rotate(360deg); }} }}
|
| 69 |
+
</style>
|
| 70 |
+
"""
|
| 71 |
+
|
| 72 |
+
# initial spinner
|
| 73 |
+
yield build_spinner_html("Initializing..."), ""
|
| 74 |
+
|
| 75 |
api_key = os.environ.get(GEMINI_ENV_KEY)
|
| 76 |
if not api_key:
|
| 77 |
+
# hide spinner when showing error/result
|
| 78 |
+
yield build_spinner_html("Missing API key", hidden=True), "⚠️ **Missing API key.** Set GEMINI_API_KEY as an environment variable."
|
| 79 |
+
return
|
| 80 |
|
| 81 |
if not image:
|
| 82 |
+
yield build_spinner_html("No image provided", hidden=True), "⚠️ Please upload an image."
|
| 83 |
+
return
|
| 84 |
+
|
| 85 |
+
yield build_spinner_html("Preparing image..."), ""
|
| 86 |
|
| 87 |
image_b64 = pil_to_base64(image)
|
| 88 |
|
|
|
|
| 102 |
|
| 103 |
headers = {"Content-Type": "application/json"}
|
| 104 |
|
| 105 |
+
# before sending
|
| 106 |
+
yield build_spinner_html("Sending to Gemini..."), ""
|
| 107 |
+
|
| 108 |
# Retry logic
|
| 109 |
for attempt in range(MAX_RETRIES):
|
| 110 |
try:
|
| 111 |
+
yield build_spinner_html("Processing..."), ""
|
| 112 |
response = requests.post(
|
| 113 |
f"{API_URL}?key={api_key}",
|
| 114 |
headers=headers,
|
|
|
|
| 120 |
break
|
| 121 |
except requests.exceptions.RequestException as e:
|
| 122 |
if attempt < MAX_RETRIES - 1:
|
| 123 |
+
yield build_spinner_html("Network error — retrying..."), ""
|
| 124 |
time.sleep(2 ** attempt)
|
| 125 |
continue
|
| 126 |
+
yield build_spinner_html("Request failed", hidden=True), f"❌ Request failed: {e}"
|
| 127 |
+
return
|
| 128 |
+
|
| 129 |
+
yield build_spinner_html("Parsing response..."), ""
|
| 130 |
|
| 131 |
candidate = data.get("candidates", [{}])[0]
|
| 132 |
parts = candidate.get("content", {}).get("parts", [])
|
|
|
|
| 134 |
|
| 135 |
if not generated_text:
|
| 136 |
finish_reason = candidate.get("finishReason", "")
|
| 137 |
+
yield build_spinner_html("No output", hidden=True), f"⚠️ No text output from model. Finish reason: `{finish_reason}`"
|
| 138 |
+
return
|
| 139 |
|
| 140 |
+
final_md = f"### 🩺 Dermatological Result \n\n{generated_text.strip()}"
|
| 141 |
+
|
| 142 |
+
# hide spinner, show final result
|
| 143 |
+
yield build_spinner_html("Completed", hidden=True), final_md
|
| 144 |
|
| 145 |
|
| 146 |
# ==========================
|
|
|
|
| 186 |
with gr.Column(elem_id="container"):
|
| 187 |
gr.Markdown(
|
| 188 |
"""
|
| 189 |
+
# Skin Disease Test
|
| 190 |
+
Upload an image of a skin lesion to receive an **AI-powered dermatological result**.
|
| 191 |
"""
|
| 192 |
)
|
| 193 |
|
|
|
|
| 200 |
|
| 201 |
# Centered analyze button (smaller)
|
| 202 |
with gr.Row(elem_id="analyze-button"):
|
| 203 |
+
analyze_btn = gr.Button("Test", variant="primary")
|
| 204 |
|
| 205 |
+
# Progress HTML + Result Markdown
|
| 206 |
+
progress_html = gr.HTML()
|
| 207 |
output_md = gr.Markdown(label="Result")
|
| 208 |
|
| 209 |
+
# Working sample image (local file)
|
| 210 |
+
gr.Markdown('<p style="font-size:0.9em;margin-top:50px;">📸 Sample Skin Lesion</p>')
|
| 211 |
+
gr.Image(value="assets/skin.jpg", show_label=False, interactive=False, height=300)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
|
| 213 |
# Event binding
|
| 214 |
analyze_btn.click(
|
| 215 |
fn=generate_gemini_analysis,
|
| 216 |
inputs=[image_input],
|
| 217 |
+
outputs=[progress_html, output_md],
|
| 218 |
)
|
| 219 |
|
| 220 |
demo.launch()
|
assets/skin.jpg
ADDED
|
requirements.txt
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
gradio
|
| 2 |
requests
|
| 3 |
|
| 4 |
-
# add a sample image and analyze button bellow the image and middle position
|
|
|
|
| 1 |
gradio
|
| 2 |
requests
|
| 3 |
|
|
|