med / app.py
mgbam's picture
Update app.py
b9dbc6c verified
raw
history blame
12.7 kB
import streamlit as st
import google.generativeai as genai
import os
from PIL import Image
import io # Needed for handling image bytes
from typing import Optional, Tuple, Any # For type hinting
# --- Configuration and Initialization ---
# Securely load API key
# Prioritize Streamlit secrets, fall back to environment variable for flexibility
GEMINI_API_KEY = st.secrets.get("GEMINI_API_KEY", os.environ.get("GEMINI_API_KEY"))
# Configure Gemini Client (only if key is found)
genai_client_configured = False
if GEMINI_API_KEY:
try:
genai.configure(api_key=GEMINI_API_KEY)
genai_client_configured = True
except Exception as e:
st.error(f"Failed to configure Google Generative AI: {e}")
st.stop() # Stop execution if configuration fails
else:
st.error("⚠️ Gemini API Key not found. Please configure `GEMINI_API_KEY` in Streamlit secrets or environment variables.")
st.stop() # Stop execution if no API key
# Initialize models (only if client configured)
# Using specific model versions can be good practice for reproducibility
TEXT_MODEL_NAME = 'gemini-1.5-flash' # Or 'gemini-pro' if preferred
VISION_MODEL_NAME = 'gemini-1.5-flash' # Or 'gemini-pro-vision' if preferred
if genai_client_configured:
try:
model = genai.GenerativeModel(TEXT_MODEL_NAME)
vision_model = genai.GenerativeModel(VISION_MODEL_NAME)
except Exception as e:
st.error(f"Failed to initialize Gemini models ({TEXT_MODEL_NAME}, {VISION_MODEL_NAME}): {e}")
st.stop()
else:
# This state should technically not be reached due to earlier st.stop() calls,
# but it's good defensive programming.
st.error("AI Models could not be initialized due to configuration issues.")
st.stop()
# --- Core AI Interaction Functions ---
# Define more sophisticated prompts emphasizing analysis and *potential* findings
# CRITICAL: Avoid definitive "diagnosis" language. Focus on assisting clinical judgment.
TEXT_ANALYSIS_PROMPT_TEMPLATE = """
**Medical Information Analysis Request:**
**Context:** Analyze the provided medical text (symptoms, history, or reports).
**Task:**
1. **Identify Key Findings:** Extract significant symptoms, signs, or data points.
2. **Potential Considerations:** Based *only* on the provided text, list potential underlying conditions or areas of concern that *might* warrant further investigation by a qualified healthcare professional. Use cautious language (e.g., "suggests potential for," "could be consistent with," "warrants investigation into").
3. **Risk Factors (if applicable):** Mention any potential risk factors identifiable from the text.
4. **Information Gaps:** Highlight any missing information that would be crucial for a clinical assessment.
5. **Disclaimer:** Explicitly state that this analysis is AI-generated, not a diagnosis, and cannot replace professional medical evaluation.
**Input Text:**
---
{text_input}
---
**Analysis:**
"""
IMAGE_ANALYSIS_PROMPT_TEMPLATE = """
**Medical Image Analysis Request:**
**Context:** Analyze the provided medical image. User may provide additional context or questions.
**Task:**
1. **Describe Visible Structures:** Briefly describe the main anatomical structures visible.
2. **Identify Potential Anomalies:** Point out any visible areas that *appear* abnormal or deviate from typical presentation (e.g., "potential opacity in the lower left lung field," "area of altered signal intensity," "possible asymmetry"). Use cautious, descriptive language.
3. **Correlate with User Prompt (if provided):** If the user asked a specific question, address it based *only* on the visual information.
4. **Limitations:** State that image quality, view, and lack of clinical context limit the analysis.
5. **Disclaimer:** Explicitly state this is an AI-based visual analysis, not a radiological interpretation or diagnosis, and requires review by a qualified radiologist or physician alongside clinical information.
**User's Additional Context/Question (if any):**
---
{user_prompt}
---
**Image Analysis:**
"""
def analyze_medical_text(text_input: str) -> Tuple[Optional[str], Optional[str]]:
"""
Sends medical text to the Gemini model for analysis using a structured prompt.
Args:
text_input: The medical text provided by the user.
Returns:
A tuple containing:
- The analysis text (str) if successful, None otherwise.
- An error message (str) if an error occurred, None otherwise.
"""
if not text_input:
return None, "Input text cannot be empty."
try:
prompt = TEXT_ANALYSIS_PROMPT_TEMPLATE.format(text_input=text_input)
response = model.generate_content(prompt)
# Add safety check for response structure if needed (e.g., check for blocked content)
if response.parts:
return response.text, None
elif response.prompt_feedback.block_reason:
return None, f"Analysis blocked due to: {response.prompt_feedback.block_reason.name}. Please revise input."
else:
return None, "Received an empty or unexpected response from the AI."
except Exception as e:
st.error(f"An error occurred during text analysis: {e}") # Log for debugging
return None, f"Error communicating with the AI model. Details: {e}"
def analyze_medical_image(image_file: Any, user_prompt: str = "") -> Tuple[Optional[str], Optional[str]]:
"""
Sends a medical image (and optional prompt) to the Gemini Vision model for analysis.
Args:
image_file: The uploaded image file object from Streamlit.
user_prompt: Optional text context or specific questions from the user.
Returns:
A tuple containing:
- The analysis text (str) if successful, None otherwise.
- An error message (str) if an error occurred, None otherwise.
"""
if not image_file:
return None, "Image file cannot be empty."
try:
# Ensure image is opened correctly, handle potential errors
try:
image = Image.open(image_file)
# Optional: Convert image to a supported format if needed, e.g., RGB
if image.mode != 'RGB':
image = image.convert('RGB')
except Exception as img_e:
return None, f"Error opening or processing image file: {img_e}"
prompt_text = IMAGE_ANALYSIS_PROMPT_TEMPLATE.format(user_prompt=user_prompt if user_prompt else "N/A")
# Prepare content for the vision model
model_input = [prompt_text, image]
response = vision_model.generate_content(model_input)
# Add safety check for response structure
if response.parts:
return response.text, None
elif response.prompt_feedback.block_reason:
return None, f"Analysis blocked due to: {response.prompt_feedback.block_reason.name}. This might be due to sensitive content policies."
else:
return None, "Received an empty or unexpected response from the AI."
except Exception as e:
st.error(f"An error occurred during image analysis: {e}") # Log for debugging
return None, f"Error communicating with the AI model. Details: {e}"
# --- Streamlit User Interface ---
def main():
st.set_page_config(page_title="AI Medical Information Assistant", layout="wide")
st.title("🤖 AI Medical Information Assistant")
st.caption(f"Powered by Google Gemini ({TEXT_MODEL_NAME} / {VISION_MODEL_NAME})")
# --- CRITICAL DISCLAIMER ---
st.warning(
"""
**IMPORTANT DISCLAIMER:**
* This tool uses AI to analyze information but **DOES NOT PROVIDE MEDICAL ADVICE OR DIAGNOSIS.**
* The analysis is based solely on the input provided and may be incomplete, inaccurate, or lack clinical context.
* **ALWAYS consult a qualified healthcare professional** for any health concerns, diagnosis, or treatment decisions.
* Do not rely on this tool for medical decisions. It is for informational and educational purposes only, potentially assisting clinicians by highlighting areas for review.
* **Do not upload identifiable patient information** unless you have explicit consent and comply with all privacy regulations (e.g., HIPAA).
"""
)
st.sidebar.header("Input Options")
input_method = st.sidebar.radio(
"Select Input Type:",
("Text Description", "Medical Image"),
help="Choose whether to analyze text-based medical information or a medical image."
)
st.sidebar.markdown("---") # Visual separator
col1, col2 = st.columns(2)
with col1:
st.header("Input")
if input_method == "Text Description":
st.subheader("Enter Medical Text")
text_input = st.text_area(
"Paste symptoms, patient history excerpt, or non-identifiable report sections:",
height=300,
placeholder="Example: 55-year-old male presents with intermittent chest pain, worse on exertion. History of hypertension. Non-smoker...",
key="text_input_area" # Add key for potential state management
)
analyze_button = st.button("Analyze Text", key="analyze_text_button", type="primary")
if analyze_button and text_input:
with st.spinner("🧠 Analyzing Text... Please wait."):
analysis_result, error_message = analyze_medical_text(text_input)
if error_message:
with col2:
st.error(f"Analysis Failed: {error_message}")
elif analysis_result:
with col2:
st.header("Analysis Results")
st.markdown(analysis_result) # Use markdown for better formatting
else:
# Handle unexpected case where both are None
with col2:
st.error("An unknown error occurred during analysis.")
elif analyze_button and not text_input:
st.warning("Please enter some text to analyze.")
elif input_method == "Medical Image":
st.subheader("Upload Medical Image")
image_file = st.file_uploader(
"Upload an image (e.g., X-ray, CT slice, pathology slide). Supported: PNG, JPG, JPEG.",
type=["png", "jpg", "jpeg"],
key="image_uploader"
)
user_image_prompt = st.text_input(
"Optional: Add context or specific questions for the image analysis:",
placeholder="Example: 'Check for abnormalities in the left lung apex' or 'Patient context: Follow-up scan after treatment'",
key="image_prompt_input"
)
analyze_button = st.button("Analyze Image", key="analyze_image_button", type="primary")
if analyze_button and image_file:
# Display uploaded image immediately for confirmation
st.image(image_file, caption="Uploaded Image Preview", use_column_width=True)
with st.spinner("🖼️ Analyzing Image... Please wait."):
analysis_result, error_message = analyze_medical_image(image_file, user_image_prompt)
if error_message:
with col2:
st.error(f"Analysis Failed: {error_message}")
elif analysis_result:
with col2:
st.header("Image Analysis Results")
st.markdown(analysis_result)
else:
with col2:
st.error("An unknown error occurred during analysis.")
elif analyze_button and not image_file:
st.warning("Please upload an image file to analyze.")
# Display placeholder in result column if nothing submitted yet
# Check if 'analysis_result' or 'error_message' exists in session state if needed for persistence,
# but for this simpler flow, checking button presses is often sufficient.
# A simple way is to check if the button was pressed in the current run.
button_pressed = st.session_state.get('analyze_text_button', False) or st.session_state.get('analyze_image_button', False)
if not button_pressed:
with col2:
st.info("Analysis results will appear here once input is submitted.")
st.sidebar.markdown("---")
st.sidebar.info("Remember: This AI tool is an assistant, not a substitute for professional medical expertise.")
if __name__ == "__main__":
main()