|
import streamlit as st |
|
import google.generativeai as genai |
|
import os |
|
from PIL import Image |
|
import io |
|
from typing import Optional, Tuple, Any |
|
|
|
|
|
|
|
|
|
|
|
GEMINI_API_KEY = st.secrets.get("GEMINI_API_KEY", os.environ.get("GEMINI_API_KEY")) |
|
|
|
|
|
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"Fatal Error: Failed to configure Google Generative AI. Check API Key. Details: {e}") |
|
st.stop() |
|
else: |
|
st.error("β οΈ Gemini API Key not found. Please configure `GEMINI_API_KEY` in Streamlit secrets or environment variables.") |
|
st.stop() |
|
|
|
|
|
|
|
TEXT_MODEL_NAME = 'gemini-1.5-pro-latest' |
|
VISION_MODEL_NAME = 'gemini-1.5-flash' |
|
|
|
if genai_client_configured: |
|
try: |
|
text_model = genai.GenerativeModel(TEXT_MODEL_NAME) |
|
vision_model = genai.GenerativeModel(VISION_MODEL_NAME) |
|
st.success(f"Successfully initialized models: Text ({TEXT_MODEL_NAME}), Vision ({VISION_MODEL_NAME})", icon="β
") |
|
except Exception as e: |
|
st.error(f"Fatal Error: Failed to initialize Gemini models. Text: {TEXT_MODEL_NAME}, Vision: {VISION_MODEL_NAME}. Details: {e}") |
|
st.stop() |
|
else: |
|
st.error("AI Models could not be initialized due to configuration issues.") |
|
st.stop() |
|
|
|
|
|
|
|
|
|
|
|
AGENTIC_TEXT_ANALYSIS_PROMPT_TEMPLATE = """ |
|
**Simulated Clinical Reasoning Agent Task:** |
|
|
|
**Role:** AI assistant simulating an agentic clinical reasoning process to support a healthcare professional by structuring information, generating possibilities, and suggesting investigation pathways based *strictly* on the provided text. **This is NOT a diagnosis.** |
|
|
|
**Input Data:** Unstructured clinical information (e.g., symptoms, history, basic findings). |
|
|
|
**Simulated Agentic Steps (Perform sequentially):** |
|
|
|
1. **Information Extraction & Structuring:** |
|
* Key Demographics (Age, Sex if provided). |
|
* Primary Symptoms/Signs. |
|
* Relevant Medical History. |
|
* Pertinent Negatives (if mentioned). |
|
|
|
2. **Differential Considerations Generation:** |
|
* Based *only* on Step 1, list **potential differential considerations** (possible conditions). |
|
* **Use cautious language:** "could be consistent with," "warrants consideration," "less likely but possible." **AVOID definitive statements.** |
|
* Briefly justify each consideration based on findings. |
|
|
|
3. **Information Gap Analysis:** |
|
* Identify critical missing information (e.g., lab results, imaging, exam specifics, duration/onset). |
|
|
|
4. **Suggested Next Steps for Investigation (for Clinician):** |
|
* Propose logical next steps a **healthcare professional might consider**. |
|
* Categorize (e.g., Further History, Exam Points, Labs, Imaging). |
|
* Frame as *suggestions* (e.g., "Consider ordering...", "Assessment of X may be informative"). |
|
|
|
5. **Mandatory Disclaimer:** Conclude with: "This AI-generated analysis is for informational support only. It is **NOT** a diagnosis and cannot replace the judgment of a qualified healthcare professional." |
|
|
|
**Input Clinical Information:** |
|
--- |
|
{text_input} |
|
--- |
|
|
|
**Agentic 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 main anatomical structures. |
|
2. **Identify Potential Anomalies:** Point out areas that *appear* abnormal or deviate from typical presentation (e.g., "potential opacity," "altered signal intensity," "possible asymmetry"). Use cautious, descriptive language. |
|
3. **Correlate with User Prompt (if provided):** Address specific user questions based *only* on visual information. |
|
4. **Limitations:** State that image quality, view, and lack of clinical context limit analysis. |
|
5. **Disclaimer:** Explicitly state this is AI visual analysis, not radiological interpretation or diagnosis, requiring review by a qualified professional with clinical context. |
|
|
|
**User's Additional Context/Question (if any):** |
|
--- |
|
{user_prompt} |
|
--- |
|
|
|
**Image Analysis:** |
|
""" |
|
|
|
def run_agentic_text_analysis(text_input: str) -> Tuple[Optional[str], Optional[str]]: |
|
""" |
|
Sends clinical text to the Gemini text model for simulated agentic analysis. |
|
|
|
Args: |
|
text_input: The clinical text provided by the user. |
|
|
|
Returns: |
|
Tuple: (analysis_text, error_message) |
|
""" |
|
if not text_input or not text_input.strip(): |
|
return None, "Input text cannot be empty." |
|
try: |
|
prompt = AGENTIC_TEXT_ANALYSIS_PROMPT_TEMPLATE.format(text_input=text_input) |
|
|
|
response = text_model.generate_content(prompt) |
|
|
|
if response.parts: |
|
return response.text, None |
|
elif response.prompt_feedback.block_reason: |
|
return None, f"Analysis blocked by safety filters: {response.prompt_feedback.block_reason.name}. Review input." |
|
else: |
|
candidate = response.candidates[0] if response.candidates else None |
|
if candidate and candidate.finish_reason != "STOP": |
|
return None, f"Analysis stopped prematurely. Reason: {candidate.finish_reason.name}." |
|
else: |
|
return None, "Received an empty or unexpected response from the AI model." |
|
|
|
except Exception as e: |
|
st.error(f"Error during agentic text analysis: {e}", icon="π¨") |
|
return None, f"Error communicating with the AI model for text analysis. 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: Uploaded image file object from Streamlit. |
|
user_prompt: Optional text context/questions from the user. |
|
|
|
Returns: |
|
Tuple: (analysis_text, error_message) |
|
""" |
|
if not image_file: |
|
return None, "Image file cannot be empty." |
|
try: |
|
try: |
|
image = Image.open(image_file) |
|
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") |
|
model_input = [prompt_text, image] |
|
|
|
response = vision_model.generate_content(model_input) |
|
|
|
if response.parts: |
|
return response.text, None |
|
elif response.prompt_feedback.block_reason: |
|
return None, f"Image analysis blocked by safety filters: {response.prompt_feedback.block_reason.name}. May relate to sensitive content policies." |
|
else: |
|
candidate = response.candidates[0] if response.candidates else None |
|
if candidate and candidate.finish_reason != "STOP": |
|
return None, f"Image analysis stopped prematurely. Reason: {candidate.finish_reason.name}." |
|
else: |
|
return None, "Received an empty or unexpected response from the AI model for image analysis." |
|
|
|
except Exception as e: |
|
st.error(f"Error during image analysis: {e}", icon="πΌοΈ") |
|
return None, f"Error communicating with the AI model for image analysis. Details: {e}" |
|
|
|
|
|
|
|
|
|
def main(): |
|
st.set_page_config( |
|
page_title="AI Clinical Support Demonstrator", |
|
layout="wide", |
|
initial_sidebar_state="expanded" |
|
) |
|
|
|
st.title("π€ AI Clinical Support Demonstrator") |
|
st.caption(f"Agentic Text Analysis ({TEXT_MODEL_NAME}) & Image Analysis ({VISION_MODEL_NAME})") |
|
st.markdown("---") |
|
|
|
|
|
st.warning( |
|
""" |
|
**π΄ IMPORTANT SAFETY & USE DISCLAIMER π΄** |
|
* This tool **DEMONSTRATES** AI capabilities. It **DOES NOT** provide medical advice or diagnosis. |
|
* **Agentic Text Analysis:** Simulates reasoning on text input. Output is illustrative, not diagnostic. |
|
* **Image Analysis:** Provides observations on images. Output is **NOT** a radiological interpretation. |
|
* AI analysis lacks full clinical context, may be inaccurate, and **CANNOT** replace professional judgment. |
|
* **ALWAYS consult qualified healthcare professionals** for diagnosis and treatment. |
|
* **PRIVACY:** Do **NOT** upload identifiable patient information (PHI) without explicit consent and adherence to all privacy laws (e.g., HIPAA). |
|
""", |
|
icon="β οΈ" |
|
) |
|
st.markdown("---") |
|
|
|
|
|
st.sidebar.header("Analysis Options") |
|
input_method = st.sidebar.radio( |
|
"Select Analysis Type:", |
|
("Agentic Text Analysis", "Medical Image Analysis"), |
|
key="input_method_radio", |
|
help="Choose 'Agentic Text Analysis' for reasoning simulation on clinical text, or 'Medical Image Analysis' for visual observations on images." |
|
) |
|
st.sidebar.markdown("---") |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
st.header("Input Data") |
|
analysis_result = None |
|
error_message = None |
|
output_header = "Analysis Results" |
|
|
|
|
|
if input_method == "Agentic Text Analysis": |
|
st.subheader("Clinical Text for Agentic Analysis") |
|
text_input = st.text_area( |
|
"Paste de-identified clinical information (symptoms, history, findings):", |
|
height=350, |
|
placeholder="Example: 68yo male, sudden SOB & pleuritic chest pain post-flight. HR 110, SpO2 92% RA. No known cardiac hx...", |
|
key="text_input_area" |
|
) |
|
analyze_button = st.button("βΆοΈ Run Agentic Text Analysis", key="analyze_text_button", type="primary") |
|
|
|
if analyze_button: |
|
if text_input: |
|
with st.spinner("π§ Simulating agentic reasoning..."): |
|
analysis_result, error_message = run_agentic_text_analysis(text_input) |
|
output_header = "Simulated Agentic Analysis Output" |
|
else: |
|
st.warning("Please enter clinical text to analyze.", icon="βοΈ") |
|
|
|
|
|
elif input_method == "Medical Image Analysis": |
|
st.subheader("Medical Image for Analysis") |
|
image_file = st.file_uploader( |
|
"Upload a de-identified medical image (e.g., X-ray, CT slice). Supported: PNG, JPG, JPEG.", |
|
type=["png", "jpg", "jpeg"], |
|
key="image_uploader" |
|
) |
|
user_image_prompt = st.text_input( |
|
"Optional: Add context or specific question for image analysis:", |
|
placeholder="Example: 'Describe findings in the lung fields' or 'Any visible fractures?'", |
|
key="image_prompt_input" |
|
) |
|
analyze_button = st.button("πΌοΈ Analyze Medical Image", key="analyze_image_button", type="primary") |
|
|
|
if analyze_button: |
|
if image_file: |
|
st.image(image_file, caption="Uploaded Image Preview", use_column_width=True) |
|
with st.spinner("ποΈ Analyzing image..."): |
|
analysis_result, error_message = analyze_medical_image(image_file, user_image_prompt) |
|
output_header = "Medical Image Analysis Output" |
|
else: |
|
st.warning("Please upload an image file to analyze.", icon="βοΈ") |
|
|
|
|
|
with col2: |
|
st.header(output_header) |
|
|
|
|
|
button_pressed = st.session_state.get('analyze_text_button', False) or st.session_state.get('analyze_image_button', False) |
|
|
|
if button_pressed: |
|
if error_message: |
|
st.error(f"Analysis Failed: {error_message}", icon="β") |
|
elif analysis_result: |
|
st.markdown(analysis_result) |
|
|
|
else: |
|
st.info("Analysis results will appear here after providing input and clicking the corresponding analysis button.") |
|
|
|
|
|
|
|
st.sidebar.markdown("---") |
|
st.sidebar.header("About The Prompts") |
|
with st.sidebar.expander("View Agentic Text Prompt Structure"): |
|
st.markdown(f"```plaintext\n{AGENTIC_TEXT_ANALYSIS_PROMPT_TEMPLATE.split('---')[0]} ... [Input Text] ...\n```") |
|
st.caption("Guides the AI through structured reasoning steps for text.") |
|
with st.sidebar.expander("View Image Analysis Prompt Structure"): |
|
st.markdown(f"```plaintext\n{IMAGE_ANALYSIS_PROMPT_TEMPLATE.split('---')[0]} ... [User Prompt] ...\n```") |
|
st.caption("Guides the AI to describe visual features and potential anomalies in images.") |
|
|
|
st.sidebar.markdown("---") |
|
st.sidebar.error( |
|
"**Ethical Use Reminder:** AI in medicine requires extreme caution. This tool is for demonstration and education, not clinical practice. Verify all information and rely on professional expertise." |
|
) |
|
|
|
if __name__ == "__main__": |
|
main() |