Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- app.py +224 -0
- requirements.txt +3 -0
app.py
ADDED
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import requests # To make API calls to Gemini
|
3 |
+
import base64
|
4 |
+
import os
|
5 |
+
from PIL import Image
|
6 |
+
import io
|
7 |
+
|
8 |
+
# --- Configuration ---
|
9 |
+
# IMPORTANT: You must set your GEMINI_API_KEY in your Hugging Face Space secrets.
|
10 |
+
# The key should be named GEMINI_API_KEY.
|
11 |
+
API_KEY = os.getenv("GEMINI_API_KEY", "")
|
12 |
+
GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={API_KEY}"
|
13 |
+
|
14 |
+
# --- Disclaimer (Crucial!) ---
|
15 |
+
MANDATORY_DISCLAIMER_TEXT = """
|
16 |
+
**⚠️ Important Disclaimer:** This AI tool provides general observations or information based on the input provided.
|
17 |
+
It is **NOT** a medical diagnosis and should **NOT** be used to self-diagnose or make treatment decisions.
|
18 |
+
The information provided should **NOT** be considered a substitute for professional medical advice, diagnosis, or treatment from a qualified healthcare provider.
|
19 |
+
**Always seek the advice of a doctor or other qualified healthcare provider** with any questions you may have regarding a medical condition, symptoms, or health concerns.
|
20 |
+
Never disregard professional medical advice or delay in seeking it because of something you have read or seen from this tool.
|
21 |
+
If you believe you may have a medical emergency, call your doctor or emergency services immediately.
|
22 |
+
"""
|
23 |
+
|
24 |
+
# --- AI Prompt for Image Analysis ---
|
25 |
+
def get_gemini_image_prompt(user_comment=None):
|
26 |
+
prompt_core = """You are an AI assistant. Your role is to observe visual signs in an image of a human or animal body part and provide general information.
|
27 |
+
You are not a medical professional and cannot provide a diagnosis. Your response MUST be general and cautious.
|
28 |
+
|
29 |
+
Analyze the following image. Structure your response as follows:
|
30 |
+
|
31 |
+
1. **Observations:** Based *only* on what is visually apparent in the image, describe any notable features, marks, discolorations, or unusual appearances. Use neutral, descriptive language. Do not infer or guess beyond what is visible. If the image is unclear or no specific observations can be made, state that.
|
32 |
+
|
33 |
+
2. **General Information (Not a Diagnosis):** Briefly mention what such visual signs *could generally be associated with* in common, non-emergency situations. Avoid definitive statements or listing specific conditions. For example, instead of saying 'This is X condition,' say 'Sometimes, redness can be associated with irritation.' If no specific observations were made, state that general well-being is important.
|
34 |
+
|
35 |
+
3. **General Prevention/Wellness Tips:** Offer *broad* wellness or preventative tips related to the depicted body part or general health. These tips should be generic and not specific to any presumed condition. For example, 'Maintaining good hygiene is important for skin health,' or 'A balanced diet supports overall well-being.'
|
36 |
+
|
37 |
+
4. **Mandatory Disclaimer:** Your entire response MUST begin with the following disclaimer, exactly as written, with no text preceding it:
|
38 |
+
|
39 |
+
""" + MANDATORY_DISCLAIMER_TEXT
|
40 |
+
|
41 |
+
user_guidance_text = ""
|
42 |
+
if user_comment and user_comment.strip():
|
43 |
+
user_guidance_text += f"\n\nThe user has provided the following comment, possibly related to an area they have highlighted or are concerned about: \"{user_comment.strip()}\". Please take this comment into consideration during your observation. If the user has drawn on the image, pay particular attention to the annotated areas in conjunction with their comment."
|
44 |
+
else:
|
45 |
+
user_guidance_text += "\n\nThe user may have drawn on the image to highlight an area of interest. If so, please pay particular attention to any such visual annotations during your observation."
|
46 |
+
|
47 |
+
prompt_suffix = """
|
48 |
+
|
49 |
+
Ensure your language is cautious, non-prescriptive, and strongly emphasizes the need to consult a real medical professional throughout your response if appropriate (e.g., by reiterating "consult a doctor for any concerns").
|
50 |
+
If the image is not of a body part or is inappropriate, state that you can only analyze images of body parts for general observations and include the disclaimer.
|
51 |
+
"""
|
52 |
+
return prompt_core + user_guidance_text + prompt_suffix
|
53 |
+
|
54 |
+
# --- AI Prompt for Text Queries ---
|
55 |
+
def get_gemini_text_prompt(user_query):
|
56 |
+
return f"""You are an AI assistant designed to provide general health-related information based on user queries.
|
57 |
+
You are NOT a medical professional and CANNOT provide a diagnosis or medical advice. Your response MUST be general, informative, and cautious.
|
58 |
+
|
59 |
+
The user has asked the following question: "{user_query}"
|
60 |
+
|
61 |
+
Please structure your response as follows:
|
62 |
+
|
63 |
+
1. **General Information:** Based on widely accepted health knowledge, provide a general answer to the user's query. Avoid definitive statements or specific medical advice. If the question is too specific, ambiguous, or outside the scope of general health information, state that and recommend consulting a professional.
|
64 |
+
|
65 |
+
2. **General Prevention/Wellness Tips:** If applicable to the query, offer *broad* wellness or preventative tips related to the topic. These tips should be generic. For example, if the query is about headaches, you might mention 'Staying hydrated and managing stress can be helpful for overall well-being.'
|
66 |
+
|
67 |
+
3. **When to See a Doctor:** Briefly mention general circumstances under which one should consult a healthcare professional regarding the topic of the query. For example, 'If symptoms are severe, persistent, or worsening, it's important to see a doctor.'
|
68 |
+
|
69 |
+
4. **Mandatory Disclaimer:** Your entire response MUST begin with the following disclaimer, exactly as written, with no text preceding it:
|
70 |
+
|
71 |
+
""" + MANDATORY_DISCLAIMER_TEXT + """
|
72 |
+
|
73 |
+
Ensure your language is cautious and strongly emphasizes that the information is not a substitute for professional medical advice.
|
74 |
+
If the query is inappropriate, not health-related, or seeks specific medical diagnosis/treatment, politely decline to answer directly and instead reiterate the importance of consulting a qualified healthcare provider, including the full disclaimer.
|
75 |
+
"""
|
76 |
+
|
77 |
+
# --- Image Processing and API Call ---
|
78 |
+
def analyze_image_with_ai(pil_image, user_comment): # Added user_comment
|
79 |
+
if not API_KEY:
|
80 |
+
return MANDATORY_DISCLAIMER_TEXT + "\n\n**Error: `GEMINI_API_KEY` is not set. Please configure it in your Hugging Face Space secrets.**"
|
81 |
+
if pil_image is None:
|
82 |
+
# Gradio's Image component with tool='paint' might return None if no image is drawn or uploaded initially.
|
83 |
+
# Or if the input is cleared.
|
84 |
+
return MANDATORY_DISCLAIMER_TEXT + "\n\n**Error: No image uploaded or drawn. Please provide an image.**"
|
85 |
+
|
86 |
+
try:
|
87 |
+
# The pil_image from Gradio (with tool='paint') will include any drawings.
|
88 |
+
buffered = io.BytesIO()
|
89 |
+
if pil_image.mode == 'RGBA' or pil_image.mode == 'P':
|
90 |
+
pil_image = pil_image.convert('RGB')
|
91 |
+
pil_image.save(buffered, format="JPEG")
|
92 |
+
img_byte = buffered.getvalue()
|
93 |
+
img_base64 = base64.b64encode(img_byte).decode("utf-8")
|
94 |
+
except Exception as e:
|
95 |
+
print(f"Error converting image: {e}")
|
96 |
+
return MANDATORY_DISCLAIMER_TEXT + f"\n\n**Error: Could not process the uploaded image. Details: {e}**"
|
97 |
+
|
98 |
+
prompt_text = get_gemini_image_prompt(user_comment) # Pass user_comment
|
99 |
+
payload = {
|
100 |
+
"contents": [
|
101 |
+
{
|
102 |
+
"role": "user",
|
103 |
+
"parts": [
|
104 |
+
{"text": prompt_text},
|
105 |
+
{"inlineData": {"mimeType": "image/jpeg", "data": img_base64}}
|
106 |
+
]
|
107 |
+
}
|
108 |
+
],
|
109 |
+
"generationConfig": {"temperature": 0.4, "topK": 32, "topP": 1, "maxOutputTokens": 2000}, # Slightly increased tokens
|
110 |
+
"safetySettings": [
|
111 |
+
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
112 |
+
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
113 |
+
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
114 |
+
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
115 |
+
]
|
116 |
+
}
|
117 |
+
return make_gemini_api_call(payload)
|
118 |
+
|
119 |
+
# --- Text Query Processing and API Call ---
|
120 |
+
def answer_text_query(text_query):
|
121 |
+
if not API_KEY:
|
122 |
+
return MANDATORY_DISCLAIMER_TEXT + "\n\n**Error: `GEMINI_API_KEY` is not set. Please configure it in your Hugging Face Space secrets.**"
|
123 |
+
if not text_query or text_query.strip() == "":
|
124 |
+
return MANDATORY_DISCLAIMER_TEXT + "\n\n**Error: No question provided. Please enter your health-related question.**"
|
125 |
+
|
126 |
+
prompt_text = get_gemini_text_prompt(text_query)
|
127 |
+
payload = {
|
128 |
+
"contents": [{"role": "user", "parts": [{"text": prompt_text}]}],
|
129 |
+
"generationConfig": {"temperature": 0.7, "topK": 40, "topP": 1, "maxOutputTokens": 1500},
|
130 |
+
"safetySettings": [
|
131 |
+
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
132 |
+
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
133 |
+
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
134 |
+
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
135 |
+
]
|
136 |
+
}
|
137 |
+
return make_gemini_api_call(payload)
|
138 |
+
|
139 |
+
# --- Common function to make API call and handle response ---
|
140 |
+
def make_gemini_api_call(payload):
|
141 |
+
try:
|
142 |
+
response = requests.post(GEMINI_API_URL, json=payload, headers={'Content-Type': 'application/json'})
|
143 |
+
response.raise_for_status()
|
144 |
+
result = response.json()
|
145 |
+
|
146 |
+
if (result.get("candidates") and
|
147 |
+
len(result["candidates"]) > 0 and
|
148 |
+
result["candidates"][0].get("content") and
|
149 |
+
result["candidates"][0]["content"].get("parts") and
|
150 |
+
len(result["candidates"][0]["content"]["parts"]) > 0 and
|
151 |
+
result["candidates"][0]["content"]["parts"][0].get("text")):
|
152 |
+
generated_text = result["candidates"][0]["content"]["parts"][0]["text"]
|
153 |
+
if not generated_text.strip().startswith("**⚠️ Important Disclaimer:**"):
|
154 |
+
return MANDATORY_DISCLAIMER_TEXT + "\n\n" + generated_text.lstrip()
|
155 |
+
return generated_text
|
156 |
+
elif result.get("promptFeedback") and result["promptFeedback"].get("blockReason"):
|
157 |
+
block_reason = result['promptFeedback']['blockReason']
|
158 |
+
safety_ratings = result['promptFeedback'].get('safetyRatings', 'No specific safety ratings provided.')
|
159 |
+
return MANDATORY_DISCLAIMER_TEXT + f"\n\n**Error from AI: The request was blocked due to: {block_reason}.**\nSafety details: {safety_ratings}"
|
160 |
+
else:
|
161 |
+
print(f"Unexpected API response structure: {result}")
|
162 |
+
return MANDATORY_DISCLAIMER_TEXT + "\n\n**Error: Could not retrieve a valid response from the AI model.**"
|
163 |
+
except requests.exceptions.RequestException as e:
|
164 |
+
print(f"Request failed: {e}")
|
165 |
+
return MANDATORY_DISCLAIMER_TEXT + f"\n\n**Error: Failed to connect to the AI model. Details: {e}**"
|
166 |
+
except Exception as e:
|
167 |
+
print(f"An unexpected error occurred: {e}")
|
168 |
+
return MANDATORY_DISCLAIMER_TEXT + f"\n\n**Error: An unexpected error occurred. Details: {e}**"
|
169 |
+
|
170 |
+
# --- Gradio Interface ---
|
171 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
|
172 |
+
gr.Markdown(
|
173 |
+
"# 🩺 AI Health Information Assistant (Experimental)"
|
174 |
+
"\nUse the tabs below to either analyze an image (with optional drawing and comments) or ask a general health-related question."
|
175 |
+
)
|
176 |
+
gr.Markdown(MANDATORY_DISCLAIMER_TEXT, elem_id="static-disclaimer")
|
177 |
+
|
178 |
+
with gr.Tabs():
|
179 |
+
with gr.TabItem("🖼️ Image Analyzer"):
|
180 |
+
gr.Markdown("Upload an image of a human or animal body part. You can draw on the image to highlight areas and add comments. The AI will attempt to describe observable signs and offer *general wellness information only*.")
|
181 |
+
with gr.Row():
|
182 |
+
with gr.Column(scale=1):
|
183 |
+
# Image input with drawing tool enabled - REMOVED tool="paint"
|
184 |
+
image_input = gr.Image(type="pil", label="Upload Image & Draw (Optional)", sources=["upload", "webcam", "clipboard"], interactive=True)
|
185 |
+
# Textbox for user comments on the image
|
186 |
+
user_comment_image = gr.Textbox(lines=3, placeholder="Add any comments about the image or highlighted areas here (optional)...", label="Your Comments on Image")
|
187 |
+
image_submit_button = gr.Button("Analyze Image", variant="primary")
|
188 |
+
with gr.Column(scale=2):
|
189 |
+
image_output_text = gr.Markdown(label="AI Observations & General Guidance")
|
190 |
+
|
191 |
+
# Update click function to pass both image and comment
|
192 |
+
image_submit_button.click(analyze_image_with_ai, inputs=[image_input, user_comment_image], outputs=image_output_text)
|
193 |
+
|
194 |
+
gr.Examples(
|
195 |
+
examples=[], # Add example image paths if you wish, but drawing needs user interaction
|
196 |
+
inputs=[image_input, user_comment_image], # inputs for examples
|
197 |
+
cache_examples=False,
|
198 |
+
label="Example Scenarios (Load image, then optionally draw & comment)"
|
199 |
+
)
|
200 |
+
|
201 |
+
with gr.TabItem("❓ Text Query"):
|
202 |
+
gr.Markdown("Ask a general health-related question. The AI will provide *general information and wellness tips only*.")
|
203 |
+
with gr.Row():
|
204 |
+
with gr.Column(scale=1):
|
205 |
+
text_input = gr.Textbox(lines=5, placeholder="Type your health-related question here...\ne.g., What are some general tips for better sleep?", label="Your Question")
|
206 |
+
text_submit_button = gr.Button("Get Information", variant="primary")
|
207 |
+
with gr.Column(scale=2):
|
208 |
+
text_output_text = gr.Markdown(label="AI Response & General Guidance")
|
209 |
+
text_submit_button.click(answer_text_query, inputs=text_input, outputs=text_output_text)
|
210 |
+
gr.Examples(
|
211 |
+
examples=[
|
212 |
+
["What are some general tips for managing stress?"],
|
213 |
+
["Can you give some general advice for maintaining a healthy diet?"],
|
214 |
+
["What are common ways to improve sleep hygiene?"],
|
215 |
+
],
|
216 |
+
inputs=text_input,
|
217 |
+
cache_examples=False,
|
218 |
+
label="Example Questions (click to load)"
|
219 |
+
)
|
220 |
+
|
221 |
+
gr.Markdown("--- \n *This tool is for informational purposes only and does not provide medical advice. Always consult a qualified healthcare professional for any health concerns or before making any decisions related to your health.*")
|
222 |
+
|
223 |
+
if __name__ == "__main__":
|
224 |
+
demo.launch()
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
requests
|
3 |
+
Pillow
|