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"""
{status}
""" # 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('

📸 Sample Skin Lesion

') 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()