|
|
import os |
|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
import plotly.express as px |
|
|
import plotly.graph_objects as go |
|
|
from plotly.subplots import make_subplots |
|
|
import numpy as np |
|
|
import datetime |
|
|
from typing import Dict, List, Optional, Tuple |
|
|
import json |
|
|
from dataclasses import dataclass, asdict |
|
|
import google.generativeai as genai |
|
|
from PIL import Image |
|
|
import pytesseract |
|
|
import keras |
|
|
from huggingface_hub import hf_hub_download |
|
|
import warnings |
|
|
warnings.filterwarnings("ignore") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.set_page_config( |
|
|
page_title="π₯ MedAI Suite - AI-Powered Medical Platform", |
|
|
page_icon="π₯", |
|
|
layout="wide", |
|
|
initial_sidebar_state="expanded" |
|
|
) |
|
|
|
|
|
|
|
|
try: |
|
|
GEMINI_API_KEY = st.secrets["GEMINI_API_KEY"] |
|
|
genai.configure(api_key=GEMINI_API_KEY) |
|
|
except: |
|
|
st.error("π Gemini API key not found in secrets. Please configure it in HF Space settings.") |
|
|
st.stop() |
|
|
|
|
|
|
|
|
|
|
|
def load_custom_css(): |
|
|
st.markdown(""" |
|
|
<style> |
|
|
/* Main theme colors */ |
|
|
:root { |
|
|
--primary-color: #2E8B57; |
|
|
--secondary-color: #4682B4; |
|
|
--accent-color: #32CD32; |
|
|
--success-color: #28a745; |
|
|
--warning-color: #ffc107; |
|
|
--danger-color: #dc3545; |
|
|
--info-color: #17a2b8; |
|
|
--dark-bg: #1e1e1e; |
|
|
--light-bg: #f8f9fa; |
|
|
} |
|
|
|
|
|
/* Header styling */ |
|
|
.main-header { |
|
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); |
|
|
padding: 2rem; |
|
|
border-radius: 12px; |
|
|
color: white; |
|
|
text-align: center; |
|
|
margin-bottom: 2rem; |
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.main-header h1 { |
|
|
margin: 0; |
|
|
font-size: 2.5rem; |
|
|
font-weight: 700; |
|
|
} |
|
|
|
|
|
.main-header p { |
|
|
margin: 0.5rem 0 0 0; |
|
|
font-size: 1.1rem; |
|
|
opacity: 0.9; |
|
|
} |
|
|
|
|
|
/* Feature cards - FIXED VISIBILITY */ |
|
|
.feature-card { |
|
|
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); |
|
|
padding: 2rem; |
|
|
border-radius: 12px; |
|
|
border: 2px solid var(--primary-color); |
|
|
margin: 1rem 0; |
|
|
box-shadow: 0 4px 20px rgba(46, 139, 87, 0.15); |
|
|
transition: transform 0.2s ease; |
|
|
color: #333333 !important; |
|
|
} |
|
|
|
|
|
.feature-card:hover { |
|
|
transform: translateY(-3px); |
|
|
box-shadow: 0 8px 25px rgba(46, 139, 87, 0.25); |
|
|
border-color: var(--secondary-color); |
|
|
} |
|
|
|
|
|
.feature-card h1, .feature-card h2, .feature-card h3, .feature-card h4 { |
|
|
color: var(--primary-color) !important; |
|
|
margin: 0.5rem 0 !important; |
|
|
} |
|
|
|
|
|
.feature-card p, .feature-card li { |
|
|
color: #444444 !important; |
|
|
font-weight: 500; |
|
|
line-height: 1.6; |
|
|
} |
|
|
|
|
|
.feature-card ul { |
|
|
margin: 1rem 0; |
|
|
padding-left: 1.5rem; |
|
|
} |
|
|
|
|
|
/* Metric cards */ |
|
|
.metric-card { |
|
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); |
|
|
padding: 1.5rem; |
|
|
border-radius: 12px; |
|
|
color: white; |
|
|
text-align: center; |
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1); |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
|
|
|
.metric-card h3 { |
|
|
margin: 0 0 0.5rem 0; |
|
|
font-size: 1rem; |
|
|
opacity: 0.9; |
|
|
} |
|
|
|
|
|
.metric-card h2 { |
|
|
margin: 0; |
|
|
font-size: 2rem; |
|
|
font-weight: 700; |
|
|
} |
|
|
|
|
|
.metric-card small { |
|
|
opacity: 0.8; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
|
|
|
/* Sidebar styling - FIXED */ |
|
|
.css-1d391kg { |
|
|
background: linear-gradient(180deg, var(--primary-color) 0%, var(--secondary-color) 100%) !important; |
|
|
} |
|
|
|
|
|
/* Patient info box - FIXED */ |
|
|
.patient-info-card { |
|
|
background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%); |
|
|
border: 2px solid var(--primary-color); |
|
|
padding: 1rem; |
|
|
border-radius: 12px; |
|
|
margin-bottom: 1rem; |
|
|
color: #2d5016 !important; |
|
|
} |
|
|
|
|
|
.patient-info-card h4 { |
|
|
color: var(--primary-color) !important; |
|
|
margin-bottom: 0.5rem !important; |
|
|
} |
|
|
|
|
|
.patient-info-card p { |
|
|
color: #2d5016 !important; |
|
|
font-weight: 600; |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
/* Button styling */ |
|
|
.stButton > button { |
|
|
background: linear-gradient(90deg, var(--primary-color) 0%, var(--secondary-color) 100%); |
|
|
color: white; |
|
|
border: none; |
|
|
border-radius: 8px; |
|
|
padding: 0.7rem 1.5rem; |
|
|
font-weight: 600; |
|
|
transition: all 0.2s ease; |
|
|
} |
|
|
|
|
|
.stButton > button:hover { |
|
|
transform: translateY(-1px); |
|
|
box-shadow: 0 4px 15px rgba(46, 139, 87, 0.3); |
|
|
} |
|
|
|
|
|
/* Alert styling */ |
|
|
.success-alert { |
|
|
background: linear-gradient(90deg, #d4edda, #c3e6cb); |
|
|
border: 2px solid var(--success-color); |
|
|
border-left: 6px solid var(--success-color); |
|
|
padding: 1.5rem; |
|
|
border-radius: 8px; |
|
|
margin: 1rem 0; |
|
|
color: #155724 !important; |
|
|
} |
|
|
|
|
|
.success-alert h3, .success-alert h4 { |
|
|
color: #155724 !important; |
|
|
margin-bottom: 0.5rem !important; |
|
|
} |
|
|
|
|
|
.warning-alert { |
|
|
background: linear-gradient(90deg, #fff3cd, #ffeaa7); |
|
|
border: 2px solid var(--warning-color); |
|
|
border-left: 6px solid var(--warning-color); |
|
|
padding: 1.5rem; |
|
|
border-radius: 8px; |
|
|
margin: 1rem 0; |
|
|
color: #856404 !important; |
|
|
} |
|
|
|
|
|
.warning-alert h3, .warning-alert h4 { |
|
|
color: #856404 !important; |
|
|
margin-bottom: 0.5rem !important; |
|
|
} |
|
|
|
|
|
.error-alert { |
|
|
background: linear-gradient(90deg, #f8d7da, #f5c6cb); |
|
|
border: 2px solid var(--danger-color); |
|
|
border-left: 6px solid var(--danger-color); |
|
|
padding: 1.5rem; |
|
|
border-radius: 8px; |
|
|
margin: 1rem 0; |
|
|
color: #721c24 !important; |
|
|
} |
|
|
|
|
|
.error-alert h3, .error-alert h4 { |
|
|
color: #721c24 !important; |
|
|
margin-bottom: 0.5rem !important; |
|
|
} |
|
|
|
|
|
/* Chat styling */ |
|
|
.chat-message { |
|
|
padding: 1.5rem; |
|
|
border-radius: 12px; |
|
|
margin: 1rem 0; |
|
|
border: 1px solid #ddd; |
|
|
} |
|
|
|
|
|
.user-message { |
|
|
background: linear-gradient(135deg, #e3f2fd, #bbdefb); |
|
|
border-left: 4px solid #2196f3; |
|
|
color: #0d47a1 !important; |
|
|
} |
|
|
|
|
|
.assistant-message { |
|
|
background: linear-gradient(135deg, #e8f5e8, #c8e6c9); |
|
|
border-left: 4px solid var(--accent-color); |
|
|
color: #2d5016 !important; |
|
|
} |
|
|
|
|
|
.chat-message strong { |
|
|
font-weight: 700; |
|
|
} |
|
|
|
|
|
/* Hide Streamlit default elements */ |
|
|
#MainMenu {visibility: hidden;} |
|
|
footer {visibility: hidden;} |
|
|
header {visibility: hidden;} |
|
|
|
|
|
/* Professional medical disclaimer */ |
|
|
.medical-disclaimer { |
|
|
background: linear-gradient(135deg, #fff3e0, #ffe0b2); |
|
|
border: 3px solid #ff9800; |
|
|
border-radius: 12px; |
|
|
padding: 2rem; |
|
|
margin: 2rem 0; |
|
|
text-align: center; |
|
|
color: #e65100 !important; |
|
|
} |
|
|
|
|
|
.medical-disclaimer h4 { |
|
|
color: #e65100 !important; |
|
|
margin-bottom: 1rem !important; |
|
|
font-weight: 700; |
|
|
} |
|
|
|
|
|
.medical-disclaimer p { |
|
|
color: #bf360c !important; |
|
|
font-weight: 500; |
|
|
line-height: 1.6; |
|
|
} |
|
|
|
|
|
/* Sidebar text colors - FIXED */ |
|
|
.css-1d391kg .css-10trblm { |
|
|
color: white !important; |
|
|
} |
|
|
|
|
|
/* Info box styling - FIXED */ |
|
|
.stInfo { |
|
|
background: linear-gradient(135deg, #e1f5fe, #b3e5fc); |
|
|
border: 2px solid var(--info-color); |
|
|
color: #01579b !important; |
|
|
} |
|
|
|
|
|
/* Expander styling */ |
|
|
.streamlit-expanderHeader { |
|
|
background: var(--light-bg); |
|
|
border: 1px solid var(--primary-color); |
|
|
border-radius: 8px; |
|
|
} |
|
|
|
|
|
/* Table styling */ |
|
|
.stDataFrame { |
|
|
border: 2px solid var(--primary-color); |
|
|
border-radius: 8px; |
|
|
} |
|
|
|
|
|
/* Progress bar styling */ |
|
|
.stProgress .st-bo { |
|
|
background: var(--primary-color); |
|
|
} |
|
|
</style> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass |
|
|
class UserProfile: |
|
|
"""Enhanced user medical profile""" |
|
|
user_id: str |
|
|
name: str |
|
|
age: int |
|
|
height: float |
|
|
weight: float |
|
|
sex: str |
|
|
blood_sugar: float |
|
|
blood_pressure: str |
|
|
past_medications: List[str] |
|
|
current_conditions: List[str] |
|
|
allergies: List[str] |
|
|
created_at: str |
|
|
bmi: float = 0.0 |
|
|
|
|
|
def __post_init__(self): |
|
|
if self.height > 0: |
|
|
height_m = self.height / 100 |
|
|
self.bmi = round(self.weight / (height_m ** 2), 2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MedicalAI: |
|
|
"""Complete Medical AI System""" |
|
|
|
|
|
def __init__(self): |
|
|
self.user_profiles = {} |
|
|
self.conversation_history = {} |
|
|
self.gemini_model = None |
|
|
self.dr_model = None |
|
|
self.dr_model_loaded = False |
|
|
|
|
|
|
|
|
self._init_gemini() |
|
|
|
|
|
|
|
|
self._init_dr_model() |
|
|
|
|
|
def _init_gemini(self): |
|
|
"""Initialize Gemini model""" |
|
|
try: |
|
|
self.gemini_model = genai.GenerativeModel("gemini-2.0-flash") |
|
|
|
|
|
except Exception as e: |
|
|
st.error(f"β Gemini connection failed: {str(e)}") |
|
|
|
|
|
def _init_dr_model(self): |
|
|
"""Initialize diabetic retinopathy model""" |
|
|
try: |
|
|
repo_id = "Arko007/diabetic-retinopathy-v1" |
|
|
weights_filename = "best_model.h5" |
|
|
local_weights_path = hf_hub_download(repo_id=repo_id, filename=weights_filename) |
|
|
self.dr_model = keras.saving.load_model(local_weights_path) |
|
|
self.dr_model_loaded = True |
|
|
except Exception as e: |
|
|
|
|
|
self.dr_model_loaded = False |
|
|
|
|
|
def validate_user_input(self, user_data: Dict) -> Tuple[bool, List[str]]: |
|
|
"""Validate user profile data""" |
|
|
errors = [] |
|
|
|
|
|
required_fields = ['name', 'age', 'height', 'weight', 'sex'] |
|
|
for field in required_fields: |
|
|
if not user_data.get(field): |
|
|
errors.append(f"β {field.title()} is required") |
|
|
|
|
|
|
|
|
try: |
|
|
age = int(user_data.get('age', 0)) |
|
|
if not 1 <= age <= 150: |
|
|
errors.append("β Age must be between 1-150 years") |
|
|
except: |
|
|
errors.append("β Age must be a valid number") |
|
|
|
|
|
|
|
|
try: |
|
|
height = float(user_data.get('height', 0)) |
|
|
if not 50 <= height <= 300: |
|
|
errors.append("β Height must be between 50-300 cm") |
|
|
except: |
|
|
errors.append("β Height must be a valid number") |
|
|
|
|
|
|
|
|
try: |
|
|
weight = float(user_data.get('weight', 0)) |
|
|
if not 10 <= weight <= 500: |
|
|
errors.append("β Weight must be between 10-500 kg") |
|
|
except: |
|
|
errors.append("β Weight must be a valid number") |
|
|
|
|
|
return len(errors) == 0, errors |
|
|
|
|
|
def create_user_profile(self, user_data: Dict) -> Tuple[Optional[UserProfile], List[str]]: |
|
|
"""Create new user profile""" |
|
|
is_valid, errors = self.validate_user_input(user_data) |
|
|
if not is_valid: |
|
|
return None, errors |
|
|
|
|
|
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') |
|
|
user_id = f"medai_user_{timestamp}" |
|
|
|
|
|
try: |
|
|
profile = UserProfile( |
|
|
user_id=user_id, |
|
|
name=user_data.get('name', '').strip().title(), |
|
|
age=int(user_data.get('age', 0)), |
|
|
height=float(user_data.get('height', 0)), |
|
|
weight=float(user_data.get('weight', 0)), |
|
|
sex=user_data.get('sex', '').lower().strip(), |
|
|
blood_sugar=float(user_data.get('blood_sugar') or 0), |
|
|
blood_pressure=user_data.get('blood_pressure', '').strip(), |
|
|
past_medications=self._clean_list(user_data.get('past_medications', [])), |
|
|
current_conditions=self._clean_list(user_data.get('current_conditions', [])), |
|
|
allergies=self._clean_list(user_data.get('allergies', [])), |
|
|
created_at=datetime.datetime.now().isoformat() |
|
|
) |
|
|
|
|
|
self.user_profiles[user_id] = profile |
|
|
self.conversation_history[user_id] = [] |
|
|
|
|
|
return profile, [] |
|
|
|
|
|
except Exception as e: |
|
|
return None, [f"β Profile creation error: {str(e)}"] |
|
|
|
|
|
def _clean_list(self, input_list) -> List[str]: |
|
|
"""Clean list inputs""" |
|
|
if isinstance(input_list, str): |
|
|
return [item.strip() for item in input_list.split(',') if item.strip()] |
|
|
elif isinstance(input_list, list): |
|
|
return [str(item).strip() for item in input_list if str(item).strip()] |
|
|
return [] |
|
|
|
|
|
def get_bmi_category(self, bmi: float) -> str: |
|
|
"""Get BMI category""" |
|
|
if bmi < 18.5: |
|
|
return "Underweight" |
|
|
elif bmi < 23: |
|
|
return "Normal weight" |
|
|
elif bmi < 25: |
|
|
return "Overweight" |
|
|
elif bmi < 30: |
|
|
return "Obese Class I" |
|
|
else: |
|
|
return "Obese Class II+" |
|
|
|
|
|
def generate_medical_context(self, user_id: str) -> str: |
|
|
"""Generate medical context for consultation""" |
|
|
if user_id not in self.user_profiles: |
|
|
return "" |
|
|
|
|
|
profile = self.user_profiles[user_id] |
|
|
return f""" |
|
|
Patient: {profile.name}, {profile.age} years old, {profile.sex.title()} |
|
|
Physical: Height {profile.height}cm, Weight {profile.weight}kg, BMI {profile.bmi} |
|
|
Vitals: Blood Sugar {profile.blood_sugar} mg/dL, BP {profile.blood_pressure} |
|
|
Medical History: {', '.join(profile.current_conditions) if profile.current_conditions else 'None'} |
|
|
Medications: {', '.join(profile.past_medications) if profile.past_medications else 'None'} |
|
|
Allergies: {', '.join(profile.allergies) if profile.allergies else 'None'} |
|
|
""" |
|
|
|
|
|
def ask_medical_question(self, user_id: str, question: str) -> Dict: |
|
|
"""Medical consultation using Gemini""" |
|
|
if not self.gemini_model: |
|
|
return {'success': False, 'error': "AI consultation unavailable"} |
|
|
|
|
|
if user_id not in self.user_profiles: |
|
|
return {'success': False, 'error': "Please create a profile first"} |
|
|
|
|
|
medical_context = self.generate_medical_context(user_id) |
|
|
|
|
|
prompt = f"""You are MedAI, a professional medical AI assistant for healthcare in India. |
|
|
|
|
|
PATIENT CONTEXT: |
|
|
{medical_context} |
|
|
|
|
|
PATIENT QUESTION: {question} |
|
|
|
|
|
INSTRUCTIONS: |
|
|
1. Provide helpful, evidence-based medical guidance |
|
|
2. Consider the patient's profile and Indian healthcare context |
|
|
3. Use clear, supportive language |
|
|
4. Always emphasize consulting healthcare professionals |
|
|
5. If urgent symptoms, immediately advise emergency care |
|
|
6. Keep response comprehensive but accessible (300-400 words) |
|
|
|
|
|
Provide a detailed, caring response that helps the patient understand their health.""" |
|
|
|
|
|
try: |
|
|
response = self.gemini_model.generate_content(prompt) |
|
|
answer = response.text |
|
|
|
|
|
|
|
|
conversation = { |
|
|
'question': question, |
|
|
'answer': answer, |
|
|
'timestamp': datetime.datetime.now().isoformat() |
|
|
} |
|
|
self.conversation_history[user_id].append(conversation) |
|
|
|
|
|
return {'success': True, 'answer': answer, 'timestamp': conversation['timestamp']} |
|
|
|
|
|
except Exception as e: |
|
|
return {'success': False, 'error': f"Consultation error: {str(e)}"} |
|
|
|
|
|
def analyze_medical_report(self, image: Image.Image) -> Dict: |
|
|
"""Analyze medical reports using Gemini Vision (no OCR needed)""" |
|
|
if not self.gemini_model: |
|
|
return {'success': False, 'error': "Report analysis unavailable"} |
|
|
|
|
|
try: |
|
|
prompt = f"""You are a medical AI analyzing a healthcare report image for Indian patients. |
|
|
|
|
|
ANALYSIS INSTRUCTIONS: |
|
|
1. Identify the type of medical report from the image |
|
|
2. Extract and read all visible text, numbers, and values |
|
|
3. Highlight values outside normal ranges for Indian population |
|
|
4. Explain abnormal findings in simple terms with analogies |
|
|
5. Provide lifestyle recommendations for concerning values |
|
|
6. Use markdown formatting for clear structure |
|
|
7. Always end with medical disclaimer |
|
|
|
|
|
Provide a comprehensive, patient-friendly analysis of this medical report image.""" |
|
|
|
|
|
|
|
|
response = self.gemini_model.generate_content([prompt, image]) |
|
|
|
|
|
return { |
|
|
'success': True, |
|
|
'analysis': response.text, |
|
|
'extracted_text': "Analysis performed using AI vision - no text extraction needed", |
|
|
'timestamp': datetime.datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
return {'success': False, 'error': f"Analysis error: {str(e)}"} |
|
|
|
|
|
def analyze_diabetic_retinopathy(self, image: Image.Image) -> Dict: |
|
|
"""Diabetic retinopathy screening using custom model + Gemini explanation""" |
|
|
if not self.dr_model_loaded: |
|
|
return {'success': False, 'error': "Eye screening model unavailable"} |
|
|
|
|
|
try: |
|
|
|
|
|
img_resized = image.resize((384, 384)) |
|
|
image_array = np.array(img_resized).astype(np.float32) / 255.0 |
|
|
image_array = np.expand_dims(image_array, axis=0) |
|
|
|
|
|
|
|
|
predictions = self.dr_model.predict(image_array, verbose=0) |
|
|
predicted_class_index = np.argmax(predictions, axis=1)[0] |
|
|
confidence = float(np.max(predictions)) |
|
|
|
|
|
class_labels = ["No DR", "Mild DR", "Moderate DR", "Severe DR", "Proliferative DR"] |
|
|
predicted_label = class_labels[predicted_class_index] |
|
|
|
|
|
|
|
|
if self.gemini_model: |
|
|
prompt = f"""You are an ophthalmology AI providing educational analysis for Indian healthcare. |
|
|
|
|
|
SCREENING RESULT: |
|
|
- AI Classification: {predicted_label} |
|
|
- Model Confidence: {confidence:.2%} |
|
|
- Custom Model: Arko007/diabetic-retinopathy-v1 |
|
|
|
|
|
ANALYSIS INSTRUCTIONS: |
|
|
1. Explain what this classification means |
|
|
2. Describe typical changes for this stage |
|
|
3. Provide follow-up recommendations |
|
|
4. Include lifestyle guidance |
|
|
5. Use supportive, clear language |
|
|
6. Always emphasize this is screening, not diagnosis |
|
|
|
|
|
Provide comprehensive analysis with clear sections.""" |
|
|
|
|
|
try: |
|
|
gemini_response = self.gemini_model.generate_content(prompt) |
|
|
detailed_analysis = gemini_response.text |
|
|
except: |
|
|
detailed_analysis = f"**Classification:** {predicted_label}\n**Confidence:** {confidence:.2%}\n\nPlease consult an eye specialist for detailed evaluation." |
|
|
else: |
|
|
detailed_analysis = f"**Classification:** {predicted_label}\n**Confidence:** {confidence:.2%}" |
|
|
|
|
|
return { |
|
|
'success': True, |
|
|
'classification': predicted_label, |
|
|
'confidence': confidence, |
|
|
'analysis': detailed_analysis, |
|
|
'timestamp': datetime.datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
return {'success': False, 'error': f"Screening error: {str(e)}"} |
|
|
|
|
|
def get_conversation_history(self, user_id: str, limit: int = 10) -> List[Dict]: |
|
|
"""Get conversation history""" |
|
|
history = self.conversation_history.get(user_id, []) |
|
|
return history[-limit:] if limit else history |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def initialize_session_state(): |
|
|
"""Initialize all session state variables""" |
|
|
if 'medical_ai' not in st.session_state: |
|
|
st.session_state.medical_ai = MedicalAI() |
|
|
|
|
|
if 'current_user' not in st.session_state: |
|
|
st.session_state.current_user = None |
|
|
|
|
|
if 'chat_history' not in st.session_state: |
|
|
st.session_state.chat_history = [] |
|
|
|
|
|
if 'page' not in st.session_state: |
|
|
st.session_state.page = "π₯ Dashboard" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def display_main_header(): |
|
|
"""Display main application header""" |
|
|
st.markdown(""" |
|
|
<div class="main-header"> |
|
|
<h1>π₯ MedAI Suite</h1> |
|
|
<p>AI-Powered Medical Platform | Consultation β’ Report Analysis β’ Eye Screening β’ Health Analytics</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
def create_sidebar(): |
|
|
"""Enhanced sidebar navigation""" |
|
|
with st.sidebar: |
|
|
|
|
|
st.markdown(""" |
|
|
<div style="text-align: center; padding: 1rem; background: linear-gradient(135deg, #2E8B57 0%, #4682B4 100%); border-radius: 12px; color: white; margin-bottom: 1rem;"> |
|
|
<h2>π₯ MedAI Suite</h2> |
|
|
<p>Your AI Health Assistant</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
|
|
|
if st.session_state.current_user: |
|
|
user = st.session_state.current_user |
|
|
st.markdown(f""" |
|
|
<div class="patient-info-card"> |
|
|
<h4>π€ Current Patient</h4> |
|
|
<p><strong>{user.name}</strong><br> |
|
|
Age: {user.age} | BMI: {user.bmi}</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
if st.button("π New Patient", use_container_width=True): |
|
|
st.session_state.current_user = None |
|
|
st.session_state.chat_history = [] |
|
|
st.rerun() |
|
|
else: |
|
|
st.markdown(""" |
|
|
<div class="patient-info-card"> |
|
|
<h4>π€ No Patient Selected</h4> |
|
|
<p>Create a profile to get started</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.markdown("### π§ Navigation") |
|
|
|
|
|
page_options = [ |
|
|
"π₯ Dashboard", |
|
|
"π€ Patient Profile", |
|
|
"π€ Medical Consultation", |
|
|
"π Report Analysis", |
|
|
"ποΈ Eye Screening", |
|
|
"π Health Analytics" |
|
|
] |
|
|
|
|
|
selected_page = st.selectbox( |
|
|
"Go to:", |
|
|
page_options, |
|
|
index=page_options.index(st.session_state.page) if st.session_state.page in page_options else 0, |
|
|
key="navigation_selectbox" |
|
|
) |
|
|
|
|
|
if selected_page != st.session_state.page: |
|
|
st.session_state.page = selected_page |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
st.markdown("### β‘ Quick Actions") |
|
|
|
|
|
if st.session_state.current_user: |
|
|
if st.button("π¨ Emergency Consultation", use_container_width=True, type="primary"): |
|
|
st.session_state.page = "π€ Medical Consultation" |
|
|
st.rerun() |
|
|
|
|
|
if st.button("π Health Dashboard", use_container_width=True): |
|
|
st.session_state.page = "π Health Analytics" |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
st.markdown(""" |
|
|
<div style="text-align: center; font-size: 0.8em; color: #666;"> |
|
|
<p>π₯ MedAI Suite<br> |
|
|
AI-Powered Healthcare Assistant<br> |
|
|
Made with β€οΈ for Better Health</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
def create_patient_profile_form(): |
|
|
"""Patient profile creation with FIXED numeric types""" |
|
|
st.markdown("## π€ Create Patient Profile") |
|
|
|
|
|
with st.form("patient_profile_form", clear_on_submit=False): |
|
|
st.markdown("### Basic Information") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
name = st.text_input("π€ Full Name*", placeholder="Enter patient name") |
|
|
age = st.number_input("π Age*", min_value=1, max_value=150, value=30, step=1) |
|
|
height = st.number_input("π Height (cm)*", min_value=50.0, max_value=300.0, value=170.0, step=1.0) |
|
|
weight = st.number_input("βοΈ Weight (kg)*", min_value=10.0, max_value=300.0, value=70.0, step=0.1) |
|
|
|
|
|
with col2: |
|
|
sex = st.selectbox("β§ Sex*", ["Male", "Female", "Other"], key="sex_selectbox") |
|
|
blood_sugar = st.number_input("π¬ Blood Sugar (mg/dL)", min_value=0.0, value=95.0, step=1.0) |
|
|
|
|
|
|
|
|
st.markdown("π©Ί **Blood Pressure**") |
|
|
bp_col1, bp_col2 = st.columns(2) |
|
|
with bp_col1: |
|
|
bp_sys = st.number_input("Systolic", min_value=60, max_value=250, value=120, step=1, key="bp_sys") |
|
|
with bp_col2: |
|
|
bp_dia = st.number_input("Diastolic", min_value=40, max_value=150, value=80, step=1, key="bp_dia") |
|
|
|
|
|
st.markdown("### Medical History (Optional)") |
|
|
|
|
|
col3, col4 = st.columns(2) |
|
|
|
|
|
with col3: |
|
|
conditions = st.text_area("π₯ Current Conditions", |
|
|
placeholder="e.g., Diabetes, Hypertension (comma-separated)") |
|
|
medications = st.text_area("π Past Medications", |
|
|
placeholder="e.g., Metformin, Aspirin (comma-separated)") |
|
|
|
|
|
with col4: |
|
|
allergies = st.text_area("β οΈ Known Allergies", |
|
|
placeholder="e.g., Penicillin, Shellfish (comma-separated)") |
|
|
notes = st.text_area("π Additional Notes", |
|
|
placeholder="Any other relevant information") |
|
|
|
|
|
|
|
|
submitted = st.form_submit_button("β
Create Profile", type="primary", use_container_width=True) |
|
|
|
|
|
if submitted and name and age and height and weight and sex: |
|
|
user_data = { |
|
|
'name': name, |
|
|
'age': age, |
|
|
'height': height, |
|
|
'weight': weight, |
|
|
'sex': sex.lower(), |
|
|
'blood_sugar': blood_sugar, |
|
|
'blood_pressure': f"{bp_sys}/{bp_dia}", |
|
|
'current_conditions': conditions, |
|
|
'past_medications': medications, |
|
|
'allergies': allergies |
|
|
} |
|
|
|
|
|
profile, errors = st.session_state.medical_ai.create_user_profile(user_data) |
|
|
|
|
|
if profile: |
|
|
st.session_state.current_user = profile |
|
|
st.markdown(f""" |
|
|
<div class="success-alert"> |
|
|
<h4>β
Profile Created Successfully!</h4> |
|
|
<p>Welcome <strong>{name}</strong>! Your medical profile is ready.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
st.balloons() |
|
|
st.rerun() |
|
|
else: |
|
|
for error in errors: |
|
|
st.error(error) |
|
|
|
|
|
def display_patient_profile(): |
|
|
"""Display patient profile dashboard""" |
|
|
if not st.session_state.current_user: |
|
|
return |
|
|
|
|
|
user = st.session_state.current_user |
|
|
|
|
|
|
|
|
st.markdown(f""" |
|
|
<div class="main-header"> |
|
|
<h2>π€ {user.name}</h2> |
|
|
<p>Patient ID: {user.user_id} | Created: {user.created_at[:10]}</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
col1, col2, col3, col4, col5, col6 = st.columns(6) |
|
|
|
|
|
metrics = [ |
|
|
(col1, "Age", f"{user.age}", "years"), |
|
|
(col2, "Height", f"{user.height}", "cm"), |
|
|
(col3, "Weight", f"{user.weight}", "kg"), |
|
|
(col4, "BMI", f"{user.bmi}", st.session_state.medical_ai.get_bmi_category(user.bmi)), |
|
|
(col5, "Blood Sugar", f"{user.blood_sugar}", "mg/dL"), |
|
|
(col6, "Blood Pressure", user.blood_pressure, "mmHg") |
|
|
] |
|
|
|
|
|
for col, title, value, unit in metrics: |
|
|
with col: |
|
|
st.markdown(f""" |
|
|
<div class="metric-card"> |
|
|
<h3>{title}</h3> |
|
|
<h2>{value}</h2> |
|
|
<small>{unit}</small> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if any([user.current_conditions, user.past_medications, user.allergies]): |
|
|
st.markdown("### π Medical Summary") |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
|
|
with col1: |
|
|
st.markdown("**π₯ Current Conditions**") |
|
|
if user.current_conditions: |
|
|
for condition in user.current_conditions: |
|
|
st.markdown(f"β’ {condition}") |
|
|
else: |
|
|
st.markdown("β’ None reported") |
|
|
|
|
|
with col2: |
|
|
st.markdown("**π Past Medications**") |
|
|
if user.past_medications: |
|
|
for medication in user.past_medications: |
|
|
st.markdown(f"β’ {medication}") |
|
|
else: |
|
|
st.markdown("β’ None reported") |
|
|
|
|
|
with col3: |
|
|
st.markdown("**β οΈ Known Allergies**") |
|
|
if user.allergies: |
|
|
for allergy in user.allergies: |
|
|
st.markdown(f"β’ {allergy}") |
|
|
else: |
|
|
st.markdown("β’ None reported") |
|
|
|
|
|
def create_medical_consultation(): |
|
|
"""AI Medical Consultation Interface""" |
|
|
st.markdown("## π€ AI Medical Consultation") |
|
|
|
|
|
if not st.session_state.current_user: |
|
|
st.markdown(""" |
|
|
<div class="warning-alert"> |
|
|
<h4>β οΈ Patient Profile Required</h4> |
|
|
<p>Please create a patient profile first to receive personalized medical consultation.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
return |
|
|
|
|
|
user = st.session_state.current_user |
|
|
|
|
|
|
|
|
with st.expander("π€ Current Patient Context", expanded=False): |
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
st.write(f"**Name:** {user.name}") |
|
|
st.write(f"**Age:** {user.age} years") |
|
|
st.write(f"**BMI:** {user.bmi} ({st.session_state.medical_ai.get_bmi_category(user.bmi)})") |
|
|
with col2: |
|
|
st.write(f"**Blood Sugar:** {user.blood_sugar} mg/dL") |
|
|
st.write(f"**Blood Pressure:** {user.blood_pressure}") |
|
|
if user.current_conditions: |
|
|
st.write(f"**Conditions:** {', '.join(user.current_conditions)}") |
|
|
|
|
|
|
|
|
st.markdown("#### π Quick Consultation Topics") |
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
quick_topics = [ |
|
|
("π Health Review", "Please review my current health metrics and provide insights."), |
|
|
("π©Ί Blood Pressure", "What should I know about managing my blood pressure?"), |
|
|
("π Lifestyle Tips", "What lifestyle changes would you recommend for me?"), |
|
|
("β οΈ Risk Assessment", "Are there any health risks I should be aware of?") |
|
|
] |
|
|
|
|
|
selected_topic = None |
|
|
for i, (col, (title, question)) in enumerate(zip([col1, col2, col3, col4], quick_topics)): |
|
|
with col: |
|
|
if st.button(title, use_container_width=True, key=f"topic_{i}"): |
|
|
selected_topic = question |
|
|
|
|
|
|
|
|
st.markdown("### π¬ Ask Your Medical Questions") |
|
|
|
|
|
|
|
|
for msg in st.session_state.chat_history[-5:]: |
|
|
if msg["role"] == "user": |
|
|
st.markdown(f""" |
|
|
<div class="chat-message user-message"> |
|
|
<strong>π€ You:</strong> {msg["content"]} |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
else: |
|
|
st.markdown(f""" |
|
|
<div class="chat-message assistant-message"> |
|
|
<strong>π€ MedAI:</strong> {msg["content"]} |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
with st.form("medical_consultation_form", clear_on_submit=True): |
|
|
question = st.text_area( |
|
|
"βοΈ Your Medical Question:", |
|
|
value=selected_topic if selected_topic else "", |
|
|
placeholder="Describe your symptoms or ask about your health...", |
|
|
height=100 |
|
|
) |
|
|
|
|
|
col1, col2 = st.columns([3, 1]) |
|
|
with col1: |
|
|
priority = st.selectbox("π¨ Priority Level", ["Normal", "Urgent", "Emergency"], key="priority_selectbox") |
|
|
|
|
|
submitted = st.form_submit_button("π€ Get AI Consultation", type="primary", use_container_width=True) |
|
|
|
|
|
if submitted and question.strip(): |
|
|
|
|
|
st.session_state.chat_history.append({"role": "user", "content": question}) |
|
|
|
|
|
|
|
|
if priority == "Emergency" or any(word in question.lower() for word in ['chest pain', "can't breathe", 'severe pain', 'bleeding']): |
|
|
st.markdown(""" |
|
|
<div class="error-alert"> |
|
|
<h4>π¨ MEDICAL EMERGENCY ALERT</h4> |
|
|
<p><strong>If this is a medical emergency, please call emergency services immediately (102/108) or visit the nearest hospital!</strong></p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
with st.spinner("π§ MedAI is analyzing your question..."): |
|
|
response = st.session_state.medical_ai.ask_medical_question(user.user_id, question) |
|
|
|
|
|
if response['success']: |
|
|
|
|
|
st.session_state.chat_history.append({"role": "assistant", "content": response['answer']}) |
|
|
st.rerun() |
|
|
else: |
|
|
st.error(f"β Consultation error: {response['error']}") |
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<div class="medical-disclaimer"> |
|
|
<h4>β οΈ IMPORTANT MEDICAL DISCLAIMER</h4> |
|
|
<p>This AI assistant provides general health information for educational purposes only. |
|
|
It is not a substitute for professional medical advice, diagnosis, or treatment. |
|
|
Always consult qualified healthcare professionals for medical concerns.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
def create_report_analysis(): |
|
|
"""Medical Report Analysis Interface""" |
|
|
st.markdown("## π Medical Report Analysis") |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="feature-card"> |
|
|
<h4>π¬ AI-Powered Report Analysis</h4> |
|
|
<p>Upload images of your medical reports for AI analysis. Supports blood tests, urine tests, |
|
|
lipid profiles, liver function tests, kidney function tests, and more.</p> |
|
|
<p><strong>Supported formats:</strong> PNG, JPG, JPEG</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
uploaded_file = st.file_uploader( |
|
|
"π Choose Medical Report Image", |
|
|
type=['png', 'jpg', 'jpeg'], |
|
|
help="Upload a clear image of your medical report" |
|
|
) |
|
|
|
|
|
if uploaded_file: |
|
|
image = Image.open(uploaded_file) |
|
|
|
|
|
col1, col2 = st.columns([1, 1]) |
|
|
|
|
|
with col1: |
|
|
st.markdown("### π· Uploaded Report") |
|
|
st.image(image, caption="Your medical report", use_column_width=True) |
|
|
|
|
|
|
|
|
width, height = image.size |
|
|
if width < 800 or height < 600: |
|
|
st.warning("β οΈ Low resolution detected. Higher resolution images provide better analysis.") |
|
|
|
|
|
with col2: |
|
|
if st.button("π Analyze Report", type="primary", use_container_width=True): |
|
|
with st.spinner("π§ Analyzing your medical report..."): |
|
|
result = st.session_state.medical_ai.analyze_medical_report(image) |
|
|
|
|
|
if result['success']: |
|
|
st.markdown("### π AI Analysis Results") |
|
|
st.markdown(result['analysis']) |
|
|
|
|
|
|
|
|
with st.expander("π Extracted Text Preview", expanded=False): |
|
|
st.text(result.get('extracted_text', 'Text extraction unavailable')) |
|
|
|
|
|
if st.session_state.current_user: |
|
|
st.success("β
Analysis complete! Results ready for consultation.") |
|
|
|
|
|
else: |
|
|
st.error(f"β Analysis failed: {result['error']}") |
|
|
|
|
|
|
|
|
with st.expander("π‘ Tips for Better Analysis", expanded=False): |
|
|
st.markdown(""" |
|
|
**For optimal results:** |
|
|
|
|
|
1. **πΈ Clear Image:** Ensure good lighting and sharp text |
|
|
2. **π Straight Orientation:** Keep the report upright |
|
|
3. **π High Resolution:** Use at least 800x600 pixels |
|
|
4. **π Complete Report:** Include all sections and reference ranges |
|
|
5. **π₯ Official Reports:** Use certified laboratory reports |
|
|
|
|
|
**Supported Report Types:** |
|
|
- Complete Blood Count (CBC) |
|
|
- Blood Chemistry Panels |
|
|
- Lipid Profiles |
|
|
- Liver & Kidney Function Tests |
|
|
- Thyroid Function Tests |
|
|
- Diabetes Monitoring (HbA1c, Glucose) |
|
|
""") |
|
|
|
|
|
def create_diabetic_retinopathy_screening(): |
|
|
"""Diabetic Retinopathy Screening Interface""" |
|
|
st.markdown("## ποΈ AI Eye Screening") |
|
|
|
|
|
if not st.session_state.medical_ai.dr_model_loaded: |
|
|
st.markdown(""" |
|
|
<div class="error-alert"> |
|
|
<h4>β Eye Screening Unavailable</h4> |
|
|
<p>The diabetic retinopathy screening model could not be loaded. Please try refreshing the page.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
return |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="feature-card"> |
|
|
<h4>π¬ AI-Powered Diabetic Retinopathy Screening</h4> |
|
|
<p>Upload retinal fundus images for AI screening of diabetic retinopathy. |
|
|
Our custom deep learning model can detect signs of eye complications in diabetic patients.</p> |
|
|
<p><strong>Model:</strong> Arko007/diabetic-retinopathy-v1 (Custom trained for Indian population)</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
uploaded_image = st.file_uploader( |
|
|
"π Upload Retinal Fundus Image", |
|
|
type=['png', 'jpg', 'jpeg'], |
|
|
help="Upload a clear retinal photograph", |
|
|
key="dr_image_upload" |
|
|
) |
|
|
|
|
|
if uploaded_image: |
|
|
image = Image.open(uploaded_image) |
|
|
|
|
|
col1, col2 = st.columns([1, 1]) |
|
|
|
|
|
with col1: |
|
|
st.markdown("### ποΈ Retinal Image") |
|
|
st.image(image, caption="Retinal fundus image", use_column_width=True) |
|
|
|
|
|
|
|
|
width, height = image.size |
|
|
st.info(f"π Image dimensions: {width}Γ{height} pixels") |
|
|
|
|
|
if width < 384 or height < 384: |
|
|
st.warning("β οΈ Minimum recommended resolution: 384Γ384 pixels") |
|
|
|
|
|
with col2: |
|
|
if st.button("π Screen for Diabetic Retinopathy", type="primary", use_container_width=True): |
|
|
with st.spinner("π§ AI is analyzing the retinal image..."): |
|
|
result = st.session_state.medical_ai.analyze_diabetic_retinopathy(image) |
|
|
|
|
|
if result['success']: |
|
|
classification = result['classification'] |
|
|
confidence = result['confidence'] |
|
|
|
|
|
|
|
|
severity_colors = { |
|
|
"No DR": "π’", |
|
|
"Mild DR": "π‘", |
|
|
"Moderate DR": "π ", |
|
|
"Severe DR": "π΄", |
|
|
"Proliferative DR": "π΄" |
|
|
} |
|
|
|
|
|
color = severity_colors.get(classification, "βͺ") |
|
|
|
|
|
st.markdown(f""" |
|
|
<div class="success-alert"> |
|
|
<h3>{color} AI Screening Result: {classification}</h3> |
|
|
<p><strong>Model Confidence:</strong> {confidence:.1%}</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.progress(confidence) |
|
|
|
|
|
|
|
|
st.markdown("### π Detailed Analysis") |
|
|
st.markdown(result['analysis']) |
|
|
|
|
|
|
|
|
st.markdown("### π‘ Recommendations") |
|
|
if classification == "No DR": |
|
|
st.success("β
No diabetic retinopathy detected. Continue regular eye checkups.") |
|
|
elif classification == "Mild DR": |
|
|
st.warning("β οΈ Mild diabetic retinopathy detected. Schedule follow-up in 6-12 months.") |
|
|
elif classification in ["Moderate DR", "Severe DR"]: |
|
|
st.error("π΄ Significant diabetic retinopathy detected. Consult an ophthalmologist promptly.") |
|
|
elif classification == "Proliferative DR": |
|
|
st.error("π¨ Advanced diabetic retinopathy detected. URGENT ophthalmologist consultation required!") |
|
|
|
|
|
if st.session_state.current_user: |
|
|
st.info("πΎ Screening results saved to your profile.") |
|
|
|
|
|
else: |
|
|
st.error(f"β Screening failed: {result['error']}") |
|
|
|
|
|
|
|
|
st.markdown("### π Understanding Diabetic Retinopathy") |
|
|
|
|
|
with st.expander("βΉοΈ About Diabetic Retinopathy", expanded=False): |
|
|
st.markdown(""" |
|
|
**What is Diabetic Retinopathy?** |
|
|
|
|
|
A diabetes complication affecting the eyes, caused by damage to retinal blood vessels. |
|
|
|
|
|
**Screening Stages:** |
|
|
|
|
|
1. **π’ No DR:** No signs detected |
|
|
2. **π‘ Mild DR:** Early changes (microaneurysms) |
|
|
3. **π Moderate DR:** Blood vessels become blocked |
|
|
4. **π΄ Severe DR:** Significant blood vessel blockage |
|
|
5. **π΄ Proliferative DR:** Advanced stage with new vessel growth |
|
|
|
|
|
**Risk Factors:** |
|
|
- Duration of diabetes |
|
|
- Poor blood sugar control |
|
|
- High blood pressure |
|
|
- Pregnancy in diabetic women |
|
|
|
|
|
**Prevention:** |
|
|
- Maintain good blood sugar control |
|
|
- Regular eye examinations |
|
|
- Control blood pressure and cholesterol |
|
|
- Healthy lifestyle choices |
|
|
""") |
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<div class="medical-disclaimer"> |
|
|
<h4>β οΈ SCREENING DISCLAIMER</h4> |
|
|
<p>This AI screening tool is for educational purposes only. It is not a medical device |
|
|
and cannot replace professional eye examination. Always consult qualified ophthalmologists |
|
|
for proper diagnosis and treatment of eye conditions.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
def generate_health_metrics(profile_data=None, days=30): |
|
|
"""Generate sample health data for analytics""" |
|
|
dates = pd.date_range(start=datetime.date.today() - datetime.timedelta(days=days), |
|
|
periods=days, freq='D') |
|
|
|
|
|
|
|
|
if profile_data: |
|
|
base_weight = profile_data.weight |
|
|
base_sugar = profile_data.blood_sugar if profile_data.blood_sugar > 0 else 95 |
|
|
bp_parts = profile_data.blood_pressure.split('/') |
|
|
base_bp_sys = float(bp_parts[0]) if len(bp_parts) == 2 else 120 |
|
|
base_bp_dia = float(bp_parts[1]) if len(bp_parts) == 2 else 80 |
|
|
else: |
|
|
base_weight, base_sugar, base_bp_sys, base_bp_dia = 70, 95, 120, 80 |
|
|
|
|
|
|
|
|
np.random.seed(42) |
|
|
weight = np.random.normal(base_weight, 0.5, days) |
|
|
blood_sugar = np.clip(np.random.normal(base_sugar, 10, days), 70, 300) |
|
|
bp_systolic = np.clip(np.random.normal(base_bp_sys, 8, days), 90, 180) |
|
|
bp_diastolic = np.clip(np.random.normal(base_bp_dia, 5, days), 60, 120) |
|
|
steps = np.maximum(2000, np.random.normal(8000, 1500, days)).astype(int) |
|
|
sleep_hours = np.clip(np.random.normal(7.5, 1, days), 5, 10) |
|
|
|
|
|
return pd.DataFrame({ |
|
|
'date': dates, |
|
|
'weight': weight.round(1), |
|
|
'blood_sugar': blood_sugar.round(0), |
|
|
'bp_systolic': bp_systolic.round(0), |
|
|
'bp_diastolic': bp_diastolic.round(0), |
|
|
'steps': steps, |
|
|
'sleep_hours': sleep_hours.round(1) |
|
|
}) |
|
|
|
|
|
def create_health_analytics(): |
|
|
"""Health Analytics Dashboard""" |
|
|
st.markdown("## π Health Analytics Dashboard") |
|
|
|
|
|
if not st.session_state.current_user: |
|
|
st.markdown(""" |
|
|
<div class="warning-alert"> |
|
|
<h4>β οΈ Patient Profile Required</h4> |
|
|
<p>Create a patient profile to view personalized health analytics.</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
return |
|
|
|
|
|
user = st.session_state.current_user |
|
|
health_data = generate_health_metrics(user) |
|
|
|
|
|
|
|
|
bmi_score = 25 if 18.5 <= user.bmi <= 25 else 15 |
|
|
sugar_score = 25 if user.blood_sugar <= 100 else 15 |
|
|
bp_parts = user.blood_pressure.split('/') |
|
|
bp_score = 25 if len(bp_parts) == 2 and int(bp_parts[0]) <= 120 and int(bp_parts[1]) <= 80 else 15 |
|
|
activity_score = 25 |
|
|
|
|
|
total_score = bmi_score + sugar_score + bp_score + activity_score |
|
|
|
|
|
|
|
|
tab1, tab2, tab3 = st.tabs(["π Overview", "π Detailed Metrics", "π― Health Score"]) |
|
|
|
|
|
with tab1: |
|
|
st.subheader("π Health Overview") |
|
|
|
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
latest = health_data.iloc[-1] |
|
|
|
|
|
with col1: |
|
|
st.metric("Current Weight", f"{latest['weight']:.1f} kg", |
|
|
delta=f"{latest['weight'] - health_data['weight'].mean():+.1f}") |
|
|
with col2: |
|
|
st.metric("Blood Sugar", f"{latest['blood_sugar']:.0f} mg/dL", |
|
|
delta=f"{latest['blood_sugar'] - health_data['blood_sugar'].mean():+.0f}") |
|
|
with col3: |
|
|
st.metric("Blood Pressure", f"{latest['bp_systolic']:.0f}/{latest['bp_diastolic']:.0f}", |
|
|
help="Systolic/Diastolic pressure in mmHg") |
|
|
with col4: |
|
|
st.metric("Daily Steps", f"{latest['steps']:,}", |
|
|
delta=f"{latest['steps'] - health_data['steps'].mean():+,.0f}") |
|
|
|
|
|
|
|
|
fig = make_subplots( |
|
|
rows=2, cols=2, |
|
|
subplot_titles=('Weight Trend', 'Blood Sugar', 'Blood Pressure', 'Activity'), |
|
|
specs=[[{"secondary_y": False}, {"secondary_y": False}], |
|
|
[{"secondary_y": False}, {"secondary_y": False}]] |
|
|
) |
|
|
|
|
|
fig.add_trace(go.Scatter(x=health_data['date'], y=health_data['weight'], |
|
|
name='Weight', line=dict(color='#2E8B57')), row=1, col=1) |
|
|
fig.add_trace(go.Scatter(x=health_data['date'], y=health_data['blood_sugar'], |
|
|
name='Blood Sugar', line=dict(color='#DC143C')), row=1, col=2) |
|
|
fig.add_trace(go.Scatter(x=health_data['date'], y=health_data['bp_systolic'], |
|
|
name='Systolic', line=dict(color='#FF8C00')), row=2, col=1) |
|
|
fig.add_trace(go.Scatter(x=health_data['date'], y=health_data['steps'], |
|
|
name='Steps', line=dict(color='#32CD32')), row=2, col=2) |
|
|
|
|
|
fig.update_layout(height=600, showlegend=False, title_text="30-Day Health Trends") |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with tab2: |
|
|
st.subheader("π Detailed Health Metrics") |
|
|
|
|
|
|
|
|
st.markdown("#### Recent Health Records") |
|
|
display_data = health_data.tail(7).copy() |
|
|
display_data['date'] = display_data['date'].dt.strftime('%Y-%m-%d') |
|
|
st.dataframe(display_data, use_container_width=True) |
|
|
|
|
|
|
|
|
st.markdown("#### Health Metrics Correlation") |
|
|
corr_data = health_data[['weight', 'blood_sugar', 'bp_systolic', 'steps', 'sleep_hours']].corr() |
|
|
fig_corr = px.imshow(corr_data, title="Health Metrics Correlation Matrix", |
|
|
color_continuous_scale='RdBu_r', aspect="auto") |
|
|
st.plotly_chart(fig_corr, use_container_width=True) |
|
|
|
|
|
with tab3: |
|
|
st.subheader("π― Personal Health Score") |
|
|
|
|
|
|
|
|
fig_gauge = go.Figure(go.Indicator( |
|
|
mode="gauge+number", |
|
|
value=total_score, |
|
|
domain={'x': [0, 1], 'y': [0, 1]}, |
|
|
title={'text': "Overall Health Score"}, |
|
|
gauge={ |
|
|
'axis': {'range': [None, 100]}, |
|
|
'bar': {'color': "#2E8B57"}, |
|
|
'steps': [ |
|
|
{'range': [0, 60], 'color': "lightgray"}, |
|
|
{'range': [60, 80], 'color': "yellow"}, |
|
|
{'range': [80, 100], 'color': "#32CD32"} |
|
|
], |
|
|
'threshold': { |
|
|
'line': {'color': "red", 'width': 4}, |
|
|
'thickness': 0.75, |
|
|
'value': 90 |
|
|
} |
|
|
} |
|
|
)) |
|
|
|
|
|
st.plotly_chart(fig_gauge, use_container_width=True) |
|
|
|
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
with col1: |
|
|
st.metric("BMI Score", f"{bmi_score}/25") |
|
|
with col2: |
|
|
st.metric("Blood Sugar Score", f"{sugar_score}/25") |
|
|
with col3: |
|
|
st.metric("Blood Pressure Score", f"{bp_score}/25") |
|
|
with col4: |
|
|
st.metric("Activity Score", f"{activity_score}/25") |
|
|
|
|
|
|
|
|
st.markdown("### π‘ Personalized Health Recommendations") |
|
|
|
|
|
recommendations = [] |
|
|
if bmi_score < 25: |
|
|
recommendations.append("ποΈ Focus on achieving a healthy BMI through balanced nutrition and exercise") |
|
|
if sugar_score < 25: |
|
|
recommendations.append("π Monitor blood sugar levels and consider dietary adjustments") |
|
|
if bp_score < 25: |
|
|
recommendations.append("π©Ί Keep track of blood pressure and practice stress management") |
|
|
|
|
|
if not recommendations: |
|
|
recommendations.append("π Excellent health metrics! Continue your healthy lifestyle.") |
|
|
|
|
|
for rec in recommendations: |
|
|
st.success(rec) |
|
|
|
|
|
def create_dashboard_home(): |
|
|
"""Main dashboard home page""" |
|
|
display_main_header() |
|
|
|
|
|
if not st.session_state.current_user: |
|
|
|
|
|
st.markdown("## π Welcome to MedAI Suite") |
|
|
|
|
|
col1, col2 = st.columns([2, 1]) |
|
|
|
|
|
with col1: |
|
|
st.markdown(""" |
|
|
<div class="feature-card"> |
|
|
<h3>π Your AI-Powered Health Platform</h3> |
|
|
<p>Get started with comprehensive healthcare AI tools:</p> |
|
|
<ul> |
|
|
<li><strong>π€ Medical Consultation:</strong> Personalized health guidance from AI</li> |
|
|
<li><strong>π Report Analysis:</strong> AI analysis of blood tests and medical reports</li> |
|
|
<li><strong>ποΈ Eye Screening:</strong> Diabetic retinopathy detection using custom AI</li> |
|
|
<li><strong>π Health Analytics:</strong> Track and visualize your health metrics</li> |
|
|
</ul> |
|
|
<h4>π€ Create your patient profile to begin!</h4> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with col2: |
|
|
st.markdown("### π― Quick Start") |
|
|
if st.button("π€ Create Patient Profile", use_container_width=True, type="primary"): |
|
|
st.session_state.page = "π€ Patient Profile" |
|
|
st.rerun() |
|
|
|
|
|
st.markdown("### π§ System Status") |
|
|
status_items = [ |
|
|
("π€ AI Consultation", "Ready"), |
|
|
("π Report Analysis", "Ready"), |
|
|
("ποΈ Eye Screening", "Ready" if st.session_state.medical_ai.dr_model_loaded else "Loading"), |
|
|
("π Health Analytics", "Ready") |
|
|
] |
|
|
|
|
|
for item, status in status_items: |
|
|
color = "π’" if status == "Ready" else "π‘" |
|
|
st.markdown(f"{color} {item}: {status}") |
|
|
|
|
|
|
|
|
st.markdown("## π₯ Platform Features") |
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
features = [ |
|
|
("π€", "AI Consultation", "Get personalized medical advice"), |
|
|
("π", "Report Analysis", "Understand your medical reports"), |
|
|
("ποΈ", "Eye Screening", "Detect diabetic retinopathy"), |
|
|
("π", "Health Analytics", "Track your health progress") |
|
|
] |
|
|
|
|
|
for col, (icon, title, desc) in zip([col1, col2, col3, col4], features): |
|
|
with col: |
|
|
st.markdown(f""" |
|
|
<div class="feature-card" style="text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;"> |
|
|
<h1 style="font-size: 3rem; margin: 0;">{icon}</h1> |
|
|
<h4 style="margin: 0.5rem 0;">{title}</h4> |
|
|
<p style="margin: 0; font-size: 0.9rem;">{desc}</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
else: |
|
|
|
|
|
user = st.session_state.current_user |
|
|
|
|
|
st.markdown(f"## π Welcome back, {user.name}!") |
|
|
|
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
consultations = len(st.session_state.medical_ai.get_conversation_history(user.user_id)) |
|
|
days_active = (datetime.datetime.now() - datetime.datetime.fromisoformat(user.created_at)).days |
|
|
|
|
|
with col1: |
|
|
st.metric("π¬ Consultations", consultations) |
|
|
with col2: |
|
|
st.metric("π
Days Active", days_active) |
|
|
with col3: |
|
|
st.metric("π― Health Score", "85/100") |
|
|
with col4: |
|
|
st.metric("βοΈ BMI Status", st.session_state.medical_ai.get_bmi_category(user.bmi)) |
|
|
|
|
|
|
|
|
st.markdown("### β‘ Quick Actions") |
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
action_buttons = [ |
|
|
("π€ AI Consultation", "π€ Medical Consultation"), |
|
|
("π Analyze Report", "π Report Analysis"), |
|
|
("ποΈ Eye Screening", "ποΈ Eye Screening"), |
|
|
("π Health Dashboard", "π Health Analytics") |
|
|
] |
|
|
|
|
|
for col, (title, page) in zip([col1, col2, col3, col4], action_buttons): |
|
|
with col: |
|
|
if st.button(title, use_container_width=True): |
|
|
st.session_state.page = page |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
st.markdown("### π Recent Activity") |
|
|
|
|
|
recent_history = st.session_state.medical_ai.get_conversation_history(user.user_id, limit=3) |
|
|
|
|
|
if recent_history: |
|
|
for i, conv in enumerate(recent_history[::-1], 1): |
|
|
with st.expander(f"π¬ Consultation {i} - {conv['timestamp'][:16]}"): |
|
|
st.markdown(f"**Question:** {conv['question'][:150]}...") |
|
|
st.markdown(f"**Response:** {conv['answer'][:200]}...") |
|
|
else: |
|
|
st.info("π No consultations yet. Start your first AI consultation!") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(): |
|
|
"""Main application entry point""" |
|
|
|
|
|
load_custom_css() |
|
|
|
|
|
|
|
|
initialize_session_state() |
|
|
|
|
|
|
|
|
create_sidebar() |
|
|
|
|
|
|
|
|
page = st.session_state.page |
|
|
|
|
|
if page == "π₯ Dashboard": |
|
|
create_dashboard_home() |
|
|
|
|
|
elif page == "π€ Patient Profile": |
|
|
if st.session_state.current_user: |
|
|
display_patient_profile() |
|
|
st.markdown("---") |
|
|
if st.button("βοΈ Create New Profile", type="secondary"): |
|
|
st.session_state.current_user = None |
|
|
st.session_state.chat_history = [] |
|
|
st.rerun() |
|
|
else: |
|
|
create_patient_profile_form() |
|
|
|
|
|
elif page == "π€ Medical Consultation": |
|
|
create_medical_consultation() |
|
|
|
|
|
elif page == "π Report Analysis": |
|
|
create_report_analysis() |
|
|
|
|
|
elif page == "ποΈ Eye Screening": |
|
|
create_diabetic_retinopathy_screening() |
|
|
|
|
|
elif page == "π Health Analytics": |
|
|
create_health_analytics() |
|
|
|
|
|
else: |
|
|
create_dashboard_home() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |