diff --git "a/src/ui_components_original.py" "b/src/ui_components_original.py" --- "a/src/ui_components_original.py" +++ "b/src/ui_components_original.py" @@ -1,1264 +1,1215 @@ -import gradio as gr -import os -import re -import logging -import tempfile -import shutil -import base64 -from datetime import datetime -from PIL import Image -import html # Import the html module for escaping - -from .patient_history import PatientHistoryManager, ReportGenerator - -class UIComponents: - def __init__(self, auth_manager, database_manager, wound_analyzer): - self.auth_manager = auth_manager - self.database_manager = database_manager - self.wound_analyzer = wound_analyzer - self.current_user = {} - self.patient_history_manager = PatientHistoryManager(database_manager) - self.report_generator = ReportGenerator() - - # Ensure uploads directory exists - if not os.path.exists("uploads"): - os.makedirs("uploads", exist_ok=True) - - def image_to_base64(self, image_path): - """Convert image to base64 data URL for embedding in HTML""" - if not image_path or not os.path.exists(image_path): - return None - - try: - with open(image_path, "rb") as image_file: - encoded_string = base64.b64encode(image_file.read()).decode() - - # Determine image format - image_ext = os.path.splitext(image_path)[1].lower() - if image_ext in [".jpg", ".jpeg"]: - mime_type = "image/jpeg" - elif image_ext == ".png": - mime_type = "image/png" - elif image_ext == ".gif": - mime_type = "image/gif" - else: - mime_type = "image/png" # Default to PNG - - return f"data:{mime_type};base64,{encoded_string}" - except Exception as e: - logging.error(f"Error converting image to base64: {e}") - return None - - def markdown_to_html(self, markdown_text): - """Convert markdown text to proper HTML format with enhanced support""" - if not markdown_text: - return "" - - # Escape HTML entities first to prevent issues with special characters - html_text = html.escape(markdown_text) - - # Convert headers - html_text = re.sub(r"^### (.*?)$", r"

\1

", html_text, flags=re.MULTILINE) - html_text = re.sub(r"^## (.*?)$", r"

\1

", html_text, flags=re.MULTILINE) - html_text = re.sub(r"^# (.*?)$", r"

\1

", html_text, flags=re.MULTILINE) - - # Convert bold text - html_text = re.sub(r"\*\*(.*?)\*\*", r"\1", html_text) - - # Convert italic text - html_text = re.sub(r"\*(.*?)\*", r"\1", html_text) - - # Convert code blocks (triple backticks) - html_text = re.sub(r"```(.*?)```", r"
\1
", html_text, flags=re.DOTALL) - # Convert inline code (single backticks) - html_text = re.sub(r"`(.*?)`", r"\1", html_text) - - # Convert blockquotes - html_text = re.sub(r"^> (.*?)$", r"
\1
", html_text, flags=re.MULTILINE) - - # Convert links - html_text = re.sub(r"\[(.*?)\]\((.*?)\)", r"\1", html_text) - - # Convert horizontal rules - html_text = re.sub(r"^\s*[-*_]{3,}\s*$", r"
", html_text, flags=re.MULTILINE) - - # Convert bullet points and handle nested lists (simplified for example) - lines = html_text.split("\n") - in_list = False - result_lines = [] - - for line in lines: - stripped = line.strip() - if stripped.startswith("- "): - if not in_list: - result_lines.append("") - in_list = False - if stripped: - result_lines.append(f"

{stripped}

") - else: - result_lines.append("
") - - if in_list: - result_lines.append("") - - return "\n".join(result_lines) - - def get_organizations_dropdown(self): - """Get list of organizations for dropdown""" - try: - organizations = self.database_manager.get_organizations() - return [f"{org["org_name"]} - {org["location"]}" for org in organizations] - except Exception as e: - logging.error(f"Error getting organizations: {e}") - return ["Default Hospital - Location"] - - - def get_custom_css(self): - return """ -/* =================== ORIGINAL SMARTHEAL CSS =================== */ -/* Global Styling */ -body, html { - margin: 0 !important; - padding: 0 !important; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif !important; - background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important; - color: #1A202C !important; - line-height: 1.6 !important; -} -/* Professional Header with Logo */ -.medical-header { - background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%) !important; - color: white !important; - padding: 32px 40px !important; - border-radius: 20px 20px 0 0 !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - margin-bottom: 0 !important; - box-shadow: 0 10px 40px rgba(49, 130, 206, 0.3) !important; - border: none !important; - position: relative !important; - overflow: hidden !important; -} -.medical-header::before { - content: '' !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - bottom: 0 !important; - background: url('data:image/svg+xml,') !important; - opacity: 0.1 !important; - z-index: 1 !important; -} -.medical-header > * { - position: relative !important; - z-index: 2 !important; -} -.logo { - width: 80px !important; - height: 80px !important; - border-radius: 50% !important; - margin-right: 24px !important; - border: 4px solid rgba(255, 255, 255, 0.3) !important; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2) !important; - background: white !important; - padding: 4px !important; -} -.medical-header h1 { - font-size: 3.5rem !important; - font-weight: 800 !important; - margin: 0 !important; - text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3) !important; - background: linear-gradient(45deg, #ffffff, #f8f9fa) !important; - -webkit-background-clip: text !important; - -webkit-text-fill-color: transparent !important; - background-clip: text !important; - filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3)) !important; -} -.medical-header p { - font-size: 1.3rem !important; - margin: 8px 0 0 0 !important; - opacity: 0.95 !important; - font-weight: 500 !important; - text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2) !important; -} -/* Enhanced Form Styling */ -.gr-form { - background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%) !important; - border-radius: 20px !important; - padding: 32px !important; - margin: 24px 0 !important; - box-shadow: 0 16px 48px rgba(0, 0, 0, 0.1) !important; - border: 1px solid rgba(229, 62, 62, 0.1) !important; - backdrop-filter: blur(10px) !important; - position: relative !important; - overflow: hidden !important; -} -.gr-form::before { - content: '' !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - height: 4px !important; - background: linear-gradient(90deg, #e53e3e 0%, #f56565 50%, #e53e3e 100%) !important; - z-index: 1 !important; -} -/* Professional Input Fields */ -.gr-textbox, .gr-number { - border-radius: 12px !important; - border: 2px solid #E2E8F0 !important; - background: #FFFFFF !important; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important; - font-size: 1rem !important; - color: #1A202C !important; - padding: 16px 20px !important; -} -.gr-textbox:focus, -.gr-number:focus, -.gr-textbox input:focus, -.gr-number input:focus { - border-color: #E53E3E !important; - box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important; - background: #FFFFFF !important; - outline: none !important; - transform: translateY(-1px) !important; -} -.gr-textbox input, -.gr-number input { - background: transparent !important; - border: none !important; - outline: none !important; - color: #1A202C !important; - font-size: 1rem !important; - width: 100% !important; - padding: 0 !important; -} -.gr-textbox label, -.gr-number label, -.gr-dropdown label, -.gr-radio label, -.gr-checkbox label { - font-weight: 600 !important; - color: #2D3748 !important; - font-size: 1rem !important; - margin-bottom: 8px !important; - display: block !important; -} -/* Enhanced Button Styling */ -button.gr-button, -button.gr-button-primary { - background: linear-gradient(135deg, #E53E3E 0%, #C53030 100%) !important; - color: #FFFFFF !important; - border: none !important; - border-radius: 12px !important; - font-weight: 700 !important; - padding: 16px 32px !important; - font-size: 1.1rem !important; - letter-spacing: 0.5px !important; - text-align: center !important; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; - box-shadow: 0 4px 16px rgba(229, 62, 62, 0.3) !important; - position: relative !important; - overflow: hidden !important; - text-transform: uppercase !important; - cursor: pointer !important; -} -button.gr-button::before { - content: '' !important; - position: absolute !important; - top: 0 !important; - left: -100% !important; - width: 100% !important; - height: 100% !important; - background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent) !important; - transition: left 0.5s !important; -} -button.gr-button:hover::before { - left: 100% !important; -} -button.gr-button:hover, -button.gr-button-primary:hover { - background: linear-gradient(135deg, #C53030 0%, #9C2A2A 100%) !important; - box-shadow: 0 8px 32px rgba(229, 62, 62, 0.4) !important; - transform: translateY(-3px) !important; -} -button.gr-button:active, -button.gr-button-primary:active { - transform: translateY(-1px) !important; - box-shadow: 0 4px 16px rgba(229, 62, 62, 0.5) !important; -} -button.gr-button:disabled { - background: #A0AEC0 !important; - color: #718096 !important; - cursor: not-allowed !important; - box-shadow: none !important; - transform: none !important; -} -/* Professional Status Messages */ -.status-success { - background: linear-gradient(135deg, #F0FFF4 0%, #E6FFFA 100%) !important; - border: 2px solid #38A169 !important; - color: #22543D !important; - padding: 20px 24px !important; - border-radius: 16px !important; - font-weight: 600 !important; - margin: 16px 0 !important; - box-shadow: 0 8px 24px rgba(56, 161, 105, 0.2) !important; - backdrop-filter: blur(10px) !important; -} -.status-error { - background: linear-gradient(135deg, #FFF5F5 0%, #FED7D7 100%) !important; - border: 2px solid #E53E3E !important; - color: #742A2A !important; - padding: 20px 24px !important; - border-radius: 16px !important; - font-weight: 600 !important; - margin: 16px 0 !important; - box-shadow: 0 8px 24px rgba(229, 62, 62, 0.2) !important; - backdrop-filter: blur(10px) !important; -} -.status-warning { - background: linear-gradient(135deg, #FFFAF0 0%, #FEEBC8 100%) !important; - border: 2px solid #DD6B20 !important; - color: #9C4221 !important; - padding: 20px 24px !important; - border-radius: 16px !important; - font-weight: 600 !important; - margin: 16px 0 !important; - box-shadow: 0 8px 24px rgba(221, 107, 32, 0.2) !important; - backdrop-filter: blur(10px) !important; -} -/* Professional Card Layout */ -.medical-card { - background: linear-gradient(145deg, #FFFFFF 0%, #F7FAFC 100%) !important; - border-radius: 20px !important; - padding: 32px !important; - margin: 24px 0 !important; - box-shadow: 0 16px 48px rgba(0, 0, 0, 0.08) !important; - border: 1px solid rgba(229, 62, 62, 0.1) !important; - backdrop-filter: blur(10px) !important; - position: relative !important; - overflow: hidden !important; -} -.medical-card::before { - content: '' !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - height: 4px !important; - background: linear-gradient(90deg, #E53E3E 0%, #F56565 50%, #E53E3E 100%) !important; -} -.medical-card-title { - font-size: 1.75rem !important; - font-weight: 700 !important; - color: #1A202C !important; - margin-bottom: 24px !important; - padding-bottom: 16px !important; - border-bottom: 2px solid #E53E3E !important; - text-align: center !important; - position: relative !important; -} -.medical-card-title::after { - content: '' !important; - position: absolute !important; - bottom: -2px !important; - left: 50% !important; - transform: translateX(-50%) !important; - width: 60px !important; - height: 4px !important; - background: linear-gradient(90deg, transparent, #E53E3E, transparent) !important; - border-radius: 2px !important; -} -/* Professional Dropdown Styling */ -.gr-dropdown { - border-radius: 12px !important; - border: 2px solid #E2E8F0 !important; - background: #FFFFFF !important; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important; -} -.gr-dropdown:focus, -.gr-dropdown select:focus { - border-color: #E53E3E !important; - box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important; - outline: none !important; -} -.gr-dropdown select { - background: transparent !important; - border: none !important; - color: #1A202C !important; - font-size: 1rem !important; - padding: 16px 20px !important; - border-radius: 12px !important; -} -/* Radio button styling */ -.gr-radio input[type="radio"] { - margin-right: 8px !important; - transform: scale(1.2) !important; -} -.gr-radio label { - display: flex !important; - align-items: center !important; - padding: 8px 0 !important; - font-size: 1rem !important; - line-height: 1.5 !important; - cursor: pointer !important; - color: #1A202C !important; -} -/* Tab styling */ -.gr-tab { - color: #1A202C !important; - font-weight: 500 !important; - font-size: 1rem !important; - padding: 12px 20px !important; - background-color: #F7FAFC !important; -} -.gr-tab.selected { - color: #E53E3E !important; - font-weight: 600 !important; - border-bottom: 2px solid #E53E3E !important; - background-color: #FFFFFF !important; -} -/* Image upload styling */ -.gr-image { - border: 3px dashed #CBD5E0 !important; - border-radius: 16px !important; - background-color: #F7FAFC !important; - transition: all 0.2s ease !important; -} -.gr-image:hover { - border-color: #E53E3E !important; - background-color: #FFF5F5 !important; -} -/* Analyze button special styling */ -#analyze-btn { - background: linear-gradient(135deg, #1B5CF3 0%, #1E3A8A 100%) !important; - color: #FFFFFF !important; - border: none !important; - border-radius: 8px !important; - font-weight: 700 !important; - padding: 14px 28px !important; - font-size: 1.1rem !important; - letter-spacing: 0.5px !important; - text-align: center !important; - transition: all 0.2s ease-in-out !important; -} -#analyze-btn:hover { - background: linear-gradient(135deg, #174ea6 0%, #123b82 100%) !important; - box-shadow: 0 4px 14px rgba(27, 95, 193, 0.4) !important; - transform: translateY(-2px) !important; -} -#analyze-btn:disabled { - background: #A0AEC0 !important; - color: #1A202C !important; - cursor: not-allowed !important; - box-shadow: none !important; - transform: none !important; -} -/* Responsive design */ -@media (max-width: 768px) { - .medical-header { - padding: 16px !important; - text-align: center !important; - } - - .medical-header h1 { - font-size: 2rem !important; - } - - .logo { - width: 48px !important; - height: 48px !important; - margin-right: 16px !important; - } - - .gr-form { - padding: 16px !important; - margin: 8px 0 !important; - } - - button.gr-button, - button.gr-button-primary { - padding: 14px 20px !important; - font-size: 14px !important; - } -} -""" - - def create_interface(self): - """Create the main Gradio interface with original styling and base64 image embedding""" - with gr.Blocks(css=self.get_custom_css(), title="SmartHeal - AI Wound Care Assistant") as app: - - # Header with SmartHeal logo (from original) - logo_url = "https://scontent.fccu31-2.fna.fbcdn.net/v/t39.30808-6/275933824_102121829111657_3325198727201325354_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=6ee11a&_nc_ohc=45krrEUpcSUQ7kNvwGVdiMW&_nc_oc=AdkTdxEC_TkYGiyDkEtTJZ_DFZELW17XKFmWpswmFqGB7JSdvTyWtnrQyLS0USngEiY&_nc_zt=23&_nc_ht=scontent.fccu31-2.fna&_nc_gid=ufAA4Hj5gTRwON5POYzz0Q&oh=00_AfW1-jLEN5RGeggqOvGgEaK_gdg0EDgxf_VhKbZwFLUO0Q&oe=6897A98B" - - gr.HTML(f""" -
- -
-

SmartHeal AI

-

Advanced Wound Care Analysis & Clinical Support System

-
-
- """) - - # Professional disclaimer (from original) - gr.HTML(""" -
-

⚠️ IMPORTANT DISCLAIMER

-

This model is for testing and educational purposes only and is NOT a replacement for professional medical advice.

-

Information generated may be inaccurate. Always consult a qualified healthcare provider for medical concerns. This AI system uses chain-of-thought reasoning to show its decision-making process, but should never be used as the sole basis for clinical decisions.

-

Uploaded images may be stored and used for testing and model improvement purposes.

-
- """) - - # Main interface with conditional visibility (ORIGINAL STRUCTURE) - with gr.Row(): - # Professional Authentication Panel (visible when not logged in) - with gr.Column(visible=True) as auth_panel: - gr.HTML(""" -
-
-

🏥 SmartHeal Access

-

Secure Healthcare Professional Portal

-
-
- """) - - with gr.Tabs(): - with gr.Tab("🔐 Professional Login") as login_tab: - gr.HTML(""" -
-
-

Welcome Back

-

Access your professional dashboard

-
-
- """) - - login_username = gr.Textbox( - label="👤 Username", - placeholder="Enter your username" - ) - login_password = gr.Textbox( - label="🔒 Password", - type="password", - placeholder="Enter your secure password" - ) - - login_btn = gr.Button( - "🚀 Sign In to Dashboard", - variant="primary", - size="lg" - ) - - login_status = gr.HTML( - value="
Enter your credentials to access the system
" - ) - - with gr.Tab("📝 New Registration") as signup_tab: - gr.HTML(""" -
-
-

Create Account

-

Join the SmartHeal healthcare network

-
-
- """) - - signup_username = gr.Textbox( - label="👤 Username", - placeholder="Choose a unique username" - ) - signup_email = gr.Textbox( - label="📧 Email Address", - placeholder="Enter your professional email" - ) - signup_password = gr.Textbox( - label="🔒 Password", - type="password", - placeholder="Create a strong password" - ) - signup_name = gr.Textbox( - label="👨‍⚕️ Full Name", - placeholder="Enter your full professional name" - ) - signup_role = gr.Radio( - ["practitioner", "organization"], - label="🏥 Account Type", - value="practitioner" - ) - - # Organization-specific fields - with gr.Group(visible=False) as org_fields: - gr.HTML("

🏢 Organization Details

") - org_name = gr.Textbox(label="Organization Name", placeholder="Enter organization name") - phone = gr.Textbox(label="Phone Number", placeholder="Enter contact number") - country_code = gr.Textbox(label="Country Code", placeholder="e.g., +1, +44") - department = gr.Textbox(label="Department", placeholder="e.g., Emergency, Surgery") - location = gr.Textbox(label="Location", placeholder="City, State/Province, Country") - - # Practitioner-specific fields - with gr.Group(visible=True) as prac_fields: - gr.HTML("

🏥 Affiliation

") - organization_dropdown = gr.Dropdown( - choices=self.get_organizations_dropdown(), - label="Select Your Organization" - ) - - signup_btn = gr.Button( - "✨ Create Professional Account", - variant="primary", - size="lg" - ) - - signup_status = gr.HTML( - value="
Fill in your details to create an account
" - ) - - # Practitioner Interface (hidden initially) - with gr.Column(visible=False) as practitioner_panel: - gr.HTML('
👩‍⚕️ Practitioner Dashboard
') - - user_info = gr.HTML("") - logout_btn_prac = gr.Button("🚪 Logout", variant="secondary", elem_classes=["logout-btn"]) - - # Main tabs for different functions - with gr.Tabs(): - # WOUND ANALYSIS TAB - with gr.Tab("🔬 Wound Analysis"): - with gr.Row(): - with gr.Column(scale=1): - gr.HTML("

📋 Patient Information

") - patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient's full name") - patient_age = gr.Number(label="Age", value=30, minimum=0, maximum=120) - patient_gender = gr.Dropdown( - choices=["Male", "Female", "Other"], - label="Gender", - value="Male" - ) - - gr.HTML("

🩹 Wound Information

") - wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left ankle, Right arm") - wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks, 1 month") - pain_level = gr.Slider( - minimum=0, maximum=10, value=5, step=1, - label="Pain Level (0-10)" - ) - - gr.HTML("

⚕️ Clinical Assessment

") - moisture_level = gr.Dropdown( - choices=["Dry", "Moist", "Wet", "Saturated"], - label="Moisture Level", - value="Moist" - ) - infection_signs = gr.Dropdown( - choices=["None", "Mild", "Moderate", "Severe"], - label="Signs of Infection", - value="None" - ) - diabetic_status = gr.Dropdown( - choices=["Non-diabetic", "Type 1", "Type 2", "Gestational"], - label="Diabetic Status", - value="Non-diabetic" - ) - - with gr.Column(scale=1): - gr.HTML("

📸 Wound Image Upload

") - wound_image = gr.Image( - label="Upload Wound Image", - type="filepath", - elem_classes=["image-upload"] - ) - - gr.HTML("

📝 Medical History

") - previous_treatment = gr.Textbox( - label="Previous Treatment", - placeholder="Describe any previous treatments...", - lines=3 - ) - medical_history = gr.Textbox( - label="Medical History", - placeholder="Relevant medical conditions, surgeries, etc...", - lines=3 - ) - medications = gr.Textbox( - label="Current Medications", - placeholder="List current medications...", - lines=2 - ) - allergies = gr.Textbox( - label="Known Allergies", - placeholder="List any known allergies...", - lines=2 - ) - additional_notes = gr.Textbox( - label="Additional Notes", - placeholder="Any additional clinical observations...", - lines=3 - ) - - analyze_btn = gr.Button("🔬 Analyze Wound", variant="primary", size="lg") - analysis_output = gr.HTML("") - - # PATIENT HISTORY TAB - with gr.Tab("📋 Patient History"): - with gr.Row(): - with gr.Column(scale=2): - gr.HTML("

📊 Patient History Dashboard

") - history_btn = gr.Button("📋 Load Patient History", variant="primary") - patient_history_output = gr.HTML("") - - with gr.Column(scale=1): - gr.HTML("

🔍 Search Specific Patient

") - search_patient_name = gr.Textbox( - label="Patient Name", - placeholder="Enter patient name to search..." - ) - search_patient_btn = gr.Button("🔍 Search Patient History", variant="secondary") - specific_patient_output = gr.HTML("") - - # Interface already complete above - no additional tabs needed - - # Event handlers - def handle_login(username, password): - user_data = self.auth_manager.authenticate_user(username, password) - if user_data: - self.current_user = user_data - return { - auth_panel: gr.update(visible=False), - practitioner_panel: gr.update(visible=True), - login_status: "
✅ Login successful! Welcome to SmartHeal
" - } - else: - return { - login_status: "
❌ Invalid credentials. Please try again.
" - } - - def handle_signup(username, email, password, name, role, org_name, phone, country_code, department, location, organization_dropdown): - try: - if role == "organization": - org_data = { - 'org_name': org_name, - 'phone': phone, - 'country_code': country_code, - 'department': department, - 'location': location - } - org_id = self.database_manager.create_organization(org_data) - - user_data = { - 'username': username, - 'email': email, - 'password': password, - 'name': name, - 'role': role, - 'org_id': org_id - } - else: - # Extract org_id from dropdown selection - org_id = 1 # Default organization for now - user_data = { - 'username': username, - 'email': email, - 'password': password, - 'name': name, - 'role': role, - 'org_id': org_id - } - - if self.auth_manager.create_user(user_data): - return { - signup_status: "
✅ Account created successfully! Please login.
" - } - else: - return { - signup_status: "
❌ Failed to create account. Username or email may already exist.
" - } - except Exception as e: - return { - signup_status: f"
❌ Error: {str(e)}
" - } - - def handle_analysis(patient_name, patient_age, patient_gender, wound_location, wound_duration, - pain_level, moisture_level, infection_signs, diabetic_status, previous_treatment, - medical_history, medications, allergies, additional_notes, wound_image): - try: - if not wound_image: - return "
❌ Please upload a wound image for analysis.
" - - # Show loading state - loading_html = """ -
-
-

Processing wound analysis...

- -
- """ - - # 1. Construct questionnaire dictionary - questionnaire_data = { - 'user_id': self.current_user.get('id'), - 'patient_name': patient_name, - 'patient_age': patient_age, - 'patient_gender': patient_gender, - 'wound_location': wound_location, - 'wound_duration': wound_duration, - 'pain_level': pain_level, - 'moisture_level': moisture_level, - 'infection_signs': infection_signs, - 'diabetic_status': diabetic_status, - 'previous_treatment': previous_treatment, - 'medical_history': medical_history, - 'medications': medications, - 'allergies': allergies, - 'additional_notes': additional_notes - } - - # 2. Save questionnaire in DB - questionnaire_id = self.database_manager.save_questionnaire(questionnaire_data) - - # 3. Run AI analysis with uploaded image - try: - # Log information about the wound image - if hasattr(wound_image, 'name'): - logging.info(f"Processing image: {wound_image.name}") - - # First try direct analysis with the file-like object - analysis_result = self.wound_analyzer.analyze_wound(wound_image, questionnaire_data) - except Exception as e: - logging.error(f"AI analysis error (first attempt): {e}") - try: - # If that fails, try with PIL image - from PIL import Image - import io - - # Reset file pointer if possible - if hasattr(wound_image, 'seek'): - wound_image.seek(0) - - # Convert to PIL Image - pil_image = Image.open(wound_image) - analysis_result = self.wound_analyzer.analyze_wound(pil_image, questionnaire_data) - except Exception as e2: - logging.error(f"AI analysis error (second attempt): {e2}") - # Return error information for display - return f""" -
-

❌ Analysis Error

-

There was an error analyzing the wound image:

-
{str(e)}\n{str(e2) if 'e2' in locals() else ''}
-

Please try again with a different image or contact support.

-
- """ - - # 4. Save AI analysis result - self.database_manager.save_analysis_result(questionnaire_id, analysis_result) - - # 5. Save wound image metadata - if isinstance(wound_image, str): - image_url = wound_image - elif hasattr(wound_image, 'name'): - image_url = wound_image.name - else: - image_url = 'unknown' - - image_data = { - 'image_url': image_url, - 'filename': os.path.basename(image_url), - 'file_size': None, - 'width': None, - 'height': None - } - - # 6. Format analysis results with visualization - formatted_analysis = self._format_analysis_results(analysis_result, image_url) - - # 7. Generate HTML professional report for complete analysis - professional_report = self.report_generator.generate_analysis_report( - questionnaire_data, - analysis_result, - image_data.get('image_url') - ) - - return formatted_analysis + professional_report - - except Exception as e: - logging.error(f"Analysis error: {e}") - return f"
❌ Analysis failed: {str(e)}
" - - - - def handle_logout(): - self.current_user = {} - return { - auth_panel: gr.update(visible=True), - practitioner_panel: gr.update(visible=False) - } - - def toggle_role_fields(role): - if role == "organization": - return { - org_fields: gr.update(visible=True), - prac_fields: gr.update(visible=False) - } - else: - return { - org_fields: gr.update(visible=False), - prac_fields: gr.update(visible=True) - } - - def load_patient_history(): - try: - user_id = self.current_user.get('id') - if not user_id: - return "
❌ Please login first.
" - - history_data = self.patient_history_manager.get_user_patient_history(user_id) - formatted_history = self.patient_history_manager.format_history_for_display(history_data) - return formatted_history - except Exception as e: - logging.error(f"Error loading patient history: {e}") - return f"
❌ Error loading history: {str(e)}
" - - def search_specific_patient(patient_name): - try: - user_id = self.current_user.get('id') - if not user_id: - return "
❌ Please login first.
" - - if not patient_name.strip(): - return "
⚠️ Please enter a patient name to search.
" - - patient_data = self.patient_history_manager.search_patient_by_name(user_id, patient_name.strip()) - if patient_data: - formatted_data = self.patient_history_manager.format_patient_data_for_display(patient_data) - return formatted_data - else: - return f"
⚠️ No records found for patient: {patient_name}
" - - except Exception as e: - logging.error(f"Error searching patient: {e}") - return f"
❌ Error searching patient: {str(e)}
" - - # Bind event handlers - login_btn.click( - handle_login, - inputs=[login_username, login_password], - outputs=[auth_panel, practitioner_panel, login_status] - ) - - signup_btn.click( - handle_signup, - inputs=[signup_username, signup_email, signup_password, signup_name, signup_role, - org_name, phone, country_code, department, location, organization_dropdown], - outputs=[signup_status] - ) - - signup_role.change( - toggle_role_fields, - inputs=[signup_role], - outputs=[org_fields, prac_fields] - ) - - analyze_btn.click( - handle_analysis, - inputs=[patient_name, patient_age, patient_gender, wound_location, wound_duration, - pain_level, moisture_level, infection_signs, diabetic_status, previous_treatment, - medical_history, medications, allergies, additional_notes, wound_image], - outputs=[analysis_output] - ) - - logout_btn_prac.click( - handle_logout, - outputs=[auth_panel, practitioner_panel] - ) - - history_btn.click( - load_patient_history, - outputs=[patient_history_output] - ) - - search_patient_btn.click( - search_specific_patient, - inputs=[search_patient_name], - outputs=[specific_patient_output] - ) - def _format_analysis_results(self, analysis_result, image_url=None): - """Format analysis results for HTML display with base64 encoded images""" - try: - # Extract key results - summary = analysis_result.get('summary', 'Analysis completed') - - # Get detection and segmentation results - wound_detection = analysis_result.get('wound_detection', {}) - segmentation_result = analysis_result.get('segmentation_result', {}) - risk_assessment = analysis_result.get('risk_assessment', {}) - recommendations = analysis_result.get('recommendations', '') - comprehensive_report = analysis_result.get('comprehensive_report', '') - - # Get detection confidence - detection_confidence = 0.0 - if wound_detection.get('status') == 'success' and wound_detection.get('detections'): - detections = wound_detection.get('detections', []) - if detections and len(detections) > 0: - detection_confidence = detections[0].get('detection_confidence', 0.0) - - # Get wound type and measurements - wound_type = "Unknown" - length_cm = breadth_cm = area_cm2 = 0 - - if wound_detection.get('status') == 'success' and wound_detection.get('detections'): - detections = wound_detection.get('detections', []) - if detections and len(detections) > 0: - wound_type = detections[0].get('wound_type', 'Unknown') - length_cm = detections[0].get('length_cm', 0) - breadth_cm = detections[0].get('breadth_cm', 0) - area_cm2 = detections[0].get('surface_area_cm2', 0) - - # Get risk assessment - risk_level = risk_assessment.get('risk_level', 'Unknown') - risk_score = risk_assessment.get('risk_score', 0) - risk_factors = risk_assessment.get('risk_factors', []) - - # Set risk class for styling - risk_class = "low" - if risk_level.lower() == "moderate": - risk_class = "moderate" - elif risk_level.lower() == "high": - risk_class = "high" - - # Format risk factors - risk_factors_html = "" - if risk_factors: - risk_factors_html = "" - else: - risk_factors_html = "

No specific risk factors identified.

" - - # Format guideline recommendations - guideline_recommendations = analysis_result.get('guideline_recommendations', []) - recommendations_html = "" - - if guideline_recommendations: - recommendations_html = "" - else: - recommendations_html = "

No specific recommendations available.

" - - # Extract detection image path from report if available and convert to base64 - detection_image_path = None - detection_image_base64 = None - if comprehensive_report: - detection_match = re.search(r"Detection Image: (.+?)(?:\n|$)", comprehensive_report) - if detection_match and detection_match.group(1) != "Not available" and os.path.exists(detection_match.group(1).strip()): - detection_image_path = detection_match.group(1).strip() - detection_image_base64 = self.image_to_base64(detection_image_path) - - # Format detection visualization section - detection_html = "" - if wound_detection.get('status') == 'success': - # Include detection image if available - detection_image_html = "" - if detection_image_base64: - detection_image_html = f""" -
-

Wound Detection Visualization

- Wound Detection -
- """ - - detection_html = f""" -
-

🔍 Wound Detection & Classification

- {detection_image_html} -
-
-

Wound Type

-

{wound_type}

-
-
-

Detection Confidence

-

{detection_confidence:.1%}

-
-
-

Total Wounds Detected

-

{wound_detection.get('total_wounds', 0)}

-
-
-
- """ - else: - detection_html = f""" -
- Detection Status: Failed
- Reason: {wound_detection.get('message', 'Unknown error')} -
- """ - - # Extract segmentation image path from report if available and convert to base64 - segmentation_image_path = None - segmentation_image_base64 = None - if comprehensive_report: - segmentation_match = re.search(r"Segmentation Image: (.+?)(?:\n|$)", comprehensive_report) - if segmentation_match and segmentation_match.group(1) != "Not available" and os.path.exists(segmentation_match.group(1).strip()): - segmentation_image_path = segmentation_match.group(1).strip() - segmentation_image_base64 = self.image_to_base64(segmentation_image_path) - - # Format segmentation visualization section - segmentation_html = "" - if segmentation_result.get('status') == 'success': - # Include segmentation image if available - segmentation_image_html = "" - if segmentation_image_base64: - segmentation_image_html = f""" -
-

Wound Segmentation Visualization

- Wound Segmentation -
- """ - - segmentation_html = f""" -
-

📏 Wound Measurements

- {segmentation_image_html} -
-
-

Length

-

{length_cm:.2f} cm

-
-
-

Width

-

{breadth_cm:.2f} cm

-
-
-

Surface Area

-

{area_cm2:.2f} cm²

-
-
-
- """ - else: - segmentation_html = f""" -
- Segmentation Status: Failed
- Reason: {segmentation_result.get('message', 'Unknown error')} -
- """ - - # Format image with detected wounds using base64 - image_visualization = "" - if image_url: - image_base64 = self.image_to_base64(image_url) - if image_base64: - image_visualization = f""" -
-

🖼️ Wound Image

-
- Wound Image -

- Analysis completed successfully -

-
-
- """ - - # Convert comprehensive report from markdown to HTML - comprehensive_report_html = "" - if comprehensive_report: - # Remove the image paths section from the report to avoid duplication - report_without_images = re.sub(r'## Analysis Images.*?(?=##|$)', '', comprehensive_report, flags=re.DOTALL) - comprehensive_report_html = self.markdown_to_html(report_without_images) - - # Create the complete HTML output - html_output = f""" -
-
-

🔬 SmartHeal AI Analysis Results

-

Advanced Computer Vision & Medical AI Assessment

-
- -
-
- Analysis Summary: {summary} -
- - {image_visualization} - - {detection_html} - - {segmentation_html} - -
-

⚠️ Risk Assessment

-
-
- {risk_level} RISK -
-
- Risk Score: {risk_score}/10 -
-
- -
-

Identified Risk Factors:

- {risk_factors_html} -
-
- -
-

💡 Clinical Recommendations

-
- {recommendations_html} -
- -
-

- ⚠️ Note: These recommendations are generated by AI and should be verified by healthcare professionals. -

-
-
- - {f'

📋 Comprehensive Report

{comprehensive_report_html}
' if comprehensive_report_html else ''} - -
- -
-

- Analysis completed by SmartHeal AI - Advanced Wound Care Assistant -

-
-
-
- """ - - return html_output - except Exception as e: - logging.error(f"Error formatting results: {e}") - return f"
❌ Error displaying results: {str(e)}
" - return app - - - \ No newline at end of file +import gradio as gr +import os +import re +import logging +import tempfile +import shutil +import base64 +from datetime import datetime +from PIL import Image +import html # Import the html module for escaping + +from .patient_history import PatientHistoryManager, ReportGenerator + +class UIComponents: + def __init__(self, auth_manager, database_manager, wound_analyzer): + self.auth_manager = auth_manager + self.database_manager = database_manager + self.wound_analyzer = wound_analyzer + self.current_user = {} + self.patient_history_manager = PatientHistoryManager(database_manager) + self.report_generator = ReportGenerator() + + # Ensure uploads directory exists + if not os.path.exists("uploads"): + os.makedirs("uploads", exist_ok=True) + + def image_to_base64(self, image_path): + """Convert image to base64 data URL for embedding in HTML""" + if not image_path or not os.path.exists(image_path): + return None + + try: + with open(image_path, "rb") as image_file: + encoded_string = base64.b64encode(image_file.read()).decode() + + # Determine image format + image_ext = os.path.splitext(image_path)[1].lower() + if image_ext in [".jpg", ".jpeg"]: + mime_type = "image/jpeg" + elif image_ext == ".png": + mime_type = "image/png" + elif image_ext == ".gif": + mime_type = "image/gif" + else: + mime_type = "image/png" # Default to PNG + + return f"data:{mime_type};base64,{encoded_string}" + except Exception as e: + logging.error(f"Error converting image to base64: {e}") + return None + + def markdown_to_html(self, markdown_text): + """Convert markdown text to proper HTML format with enhanced support""" + if not markdown_text: + return "" + + # Escape HTML entities first to prevent issues with special characters + html_text = html.escape(markdown_text) + + # Convert headers + html_text = re.sub(r"^### (.*?)$", r"

\1

", html_text, flags=re.MULTILINE) + html_text = re.sub(r"^## (.*?)$", r"

\1

", html_text, flags=re.MULTILINE) + html_text = re.sub(r"^# (.*?)$", r"

\1

", html_text, flags=re.MULTILINE) + + # Convert bold text + html_text = re.sub(r"\*\*(.*?)\*\*", r"\1", html_text) + + # Convert italic text + html_text = re.sub(r"\*(.*?)\*", r"\1", html_text) + + # Convert code blocks (triple backticks) + html_text = re.sub(r"```(.*?)```", r"
\1
", html_text, flags=re.DOTALL) + # Convert inline code (single backticks) + html_text = re.sub(r"`(.*?)`", r"\1", html_text) + + # Convert blockquotes + html_text = re.sub(r"^> (.*?)$", r"
\1
", html_text, flags=re.MULTILINE) + + # Convert links + html_text = re.sub(r"\[(.*?)\]\((.*?)\)", r"\1", html_text) + + # Convert horizontal rules + html_text = re.sub(r"^\s*[-*_]{3,}\s*$", r"
", html_text, flags=re.MULTILINE) + + # Convert bullet points and handle nested lists (simplified for example) + lines = html_text.split("\n") + in_list = False + result_lines = [] + + for line in lines: + stripped = line.strip() + if stripped.startswith("- "): + if not in_list: + result_lines.append("") + in_list = False + if stripped: + result_lines.append(f"

{stripped}

") + else: + result_lines.append("
") + + if in_list: + result_lines.append("") + + return "\n".join(result_lines) + + def get_organizations_dropdown(self): + """Get list of organizations for dropdown""" + try: + organizations = self.database_manager.get_organizations() + return [f"{org["org_name"]} - {org["location"]}" for org in organizations] + except Exception as e: + logging.error(f"Error getting organizations: {e}") + return ["Default Hospital - Location"] + + + def get_custom_css(self): + return """ +/* =================== ORIGINAL SMARTHEAL CSS =================== */ +/* Global Styling */ +body, html { + margin: 0 !important; + padding: 0 !important; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif !important; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important; + color: #1A202C !important; + line-height: 1.6 !important; +} +/* Professional Header with Logo */ +.medical-header { + background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%) !important; + color: white !important; + padding: 32px 40px !important; + border-radius: 20px 20px 0 0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + margin-bottom: 0 !important; + box-shadow: 0 10px 40px rgba(49, 130, 206, 0.3) !important; + border: none !important; + position: relative !important; + overflow: hidden !important; +} +.medical-header::before { + content: '' !important; + position: absolute !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + background: url('data:image/svg+xml,') !important; + opacity: 0.1 !important; + z-index: 1 !important; +} +.medical-header > * { + position: relative !important; + z-index: 2 !important; +} +.logo { + width: 80px !important; + height: 80px !important; + border-radius: 50% !important; + margin-right: 24px !important; + border: 4px solid rgba(255, 255, 255, 0.3) !important; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2) !important; + background: white !important; + padding: 4px !important; +} +.medical-header h1 { + font-size: 3.5rem !important; + font-weight: 800 !important; + margin: 0 !important; + text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3) !important; + background: linear-gradient(45deg, #ffffff, #f8f9fa) !important; + -webkit-background-clip: text !important; + -webkit-text-fill-color: transparent !important; + background-clip: text !important; + filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3)) !important; +} +.medical-header p { + font-size: 1.3rem !important; + margin: 8px 0 0 0 !important; + opacity: 0.95 !important; + font-weight: 500 !important; + text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2) !important; +} +/* Enhanced Form Styling */ +.gr-form { + background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%) !important; + border-radius: 20px !important; + padding: 32px !important; + margin: 24px 0 !important; + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.1) !important; + border: 1px solid rgba(229, 62, 62, 0.1) !important; + backdrop-filter: blur(10px) !important; + position: relative !important; + overflow: hidden !important; +} +.gr-form::before { + content: '' !important; + position: absolute !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + height: 4px !important; + background: linear-gradient(90deg, #e53e3e 0%, #f56565 50%, #e53e3e 100%) !important; + z-index: 1 !important; +} +/* Professional Input Fields */ +.gr-textbox, .gr-number { + border-radius: 12px !important; + border: 2px solid #E2E8F0 !important; + background: #FFFFFF !important; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important; + font-size: 1rem !important; + color: #1A202C !important; + padding: 16px 20px !important; +} +.gr-textbox:focus, +.gr-number:focus, +.gr-textbox input:focus, +.gr-number input:focus { + border-color: #E53E3E !important; + box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important; + background: #FFFFFF !important; + outline: none !important; + transform: translateY(-1px) !important; +} +.gr-textbox input, +.gr-number input { + background: transparent !important; + border: none !important; + outline: none !important; + color: #1A202C !important; + font-size: 1rem !important; + width: 100% !important; + padding: 0 !important; +} +.gr-textbox label, +.gr-number label, +.gr-dropdown label, +.gr-radio label, +.gr-checkbox label { + font-weight: 600 !important; + color: #2D3748 !important; + font-size: 1rem !important; + margin-bottom: 8px !important; + display: block !important; +} +/* Enhanced Button Styling */ +button.gr-button, +button.gr-button-primary { + background: linear-gradient(135deg, #E53E3E 0%, #C53030 100%) !important; + color: #FFFFFF !important; + border: none !important; + border-radius: 12px !important; + font-weight: 700 !important; + padding: 16px 32px !important; + font-size: 1.1rem !important; + letter-spacing: 0.5px !important; + text-align: center !important; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; + box-shadow: 0 4px 16px rgba(229, 62, 62, 0.3) !important; + position: relative !important; + overflow: hidden !important; + text-transform: uppercase !important; + cursor: pointer !important; +} +button.gr-button::before { + content: '' !important; + position: absolute !important; + top: 0 !important; + left: -100% !important; + width: 100% !important; + height: 100% !important; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent) !important; + transition: left 0.5s !important; +} +button.gr-button:hover::before { + left: 100% !important; +} +button.gr-button:hover, +button.gr-button-primary:hover { + background: linear-gradient(135deg, #C53030 0%, #9C2A2A 100%) !important; + box-shadow: 0 8px 32px rgba(229, 62, 62, 0.4) !important; + transform: translateY(-3px) !important; +} +button.gr-button:active, +button.gr-button-primary:active { + transform: translateY(-1px) !important; + box-shadow: 0 4px 16px rgba(229, 62, 62, 0.5) !important; +} +button.gr-button:disabled { + background: #A0AEC0 !important; + color: #718096 !important; + cursor: not-allowed !important; + box-shadow: none !important; + transform: none !important; +} +/* Professional Status Messages */ +.status-success { + background: linear-gradient(135deg, #F0FFF4 0%, #E6FFFA 100%) !important; + border: 2px solid #38A169 !important; + color: #22543D !important; + padding: 20px 24px !important; + border-radius: 16px !important; + font-weight: 600 !important; + margin: 16px 0 !important; + box-shadow: 0 8px 24px rgba(56, 161, 105, 0.2) !important; + backdrop-filter: blur(10px) !important; +} +.status-error { + background: linear-gradient(135deg, #FFF5F5 0%, #FED7D7 100%) !important; + border: 2px solid #E53E3E !important; + color: #742A2A !important; + padding: 20px 24px !important; + border-radius: 16px !important; + font-weight: 600 !important; + margin: 16px 0 !important; + box-shadow: 0 8px 24px rgba(229, 62, 62, 0.2) !important; + backdrop-filter: blur(10px) !important; +} +.status-warning { + background: linear-gradient(135deg, #FFFAF0 0%, #FEEBC8 100%) !important; + border: 2px solid #DD6B20 !important; + color: #9C4221 !important; + padding: 20px 24px !important; + border-radius: 16px !important; + font-weight: 600 !important; + margin: 16px 0 !important; + box-shadow: 0 8px 24px rgba(221, 107, 32, 0.2) !important; + backdrop-filter: blur(10px) !important; +} +/* Professional Card Layout */ +.medical-card { + background: linear-gradient(145deg, #FFFFFF 0%, #F7FAFC 100%) !important; + border-radius: 20px !important; + padding: 32px !important; + margin: 24px 0 !important; + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.08) !important; + border: 1px solid rgba(229, 62, 62, 0.1) !important; + backdrop-filter: blur(10px) !important; + position: relative !important; + overflow: hidden !important; +} +.medical-card::before { + content: '' !important; + position: absolute !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + height: 4px !important; + background: linear-gradient(90deg, #E53E3E 0%, #F56565 50%, #E53E3E 100%) !important; +} +.medical-card-title { + font-size: 1.75rem !important; + font-weight: 700 !important; + color: #1A202C !important; + margin-bottom: 24px !important; + padding-bottom: 16px !important; + border-bottom: 2px solid #E53E3E !important; + text-align: center !important; + position: relative !important; +} +.medical-card-title::after { + content: '' !important; + position: absolute !important; + bottom: -2px !important; + left: 50% !important; + transform: translateX(-50%) !important; + width: 60px !important; + height: 4px !important; + background: linear-gradient(90deg, transparent, #E53E3E, transparent) !important; + border-radius: 2px !important; +} +/* Professional Dropdown Styling */ +.gr-dropdown { + border-radius: 12px !important; + border: 2px solid #E2E8F0 !important; + background: #FFFFFF !important; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important; +} +.gr-dropdown:focus, +.gr-dropdown select:focus { + border-color: #E53E3E !important; + box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important; + outline: none !important; +} +.gr-dropdown select { + background: transparent !important; + border: none !important; + color: #1A202C !important; + font-size: 1rem !important; + padding: 16px 20px !important; + border-radius: 12px !important; +} +/* Radio button styling */ +.gr-radio input[type="radio"] { + margin-right: 8px !important; + transform: scale(1.2) !important; +} +.gr-radio label { + display: flex !important; + align-items: center !important; + padding: 8px 0 !important; + font-size: 1rem !important; + line-height: 1.5 !important; + cursor: pointer !important; + color: #1A202C !important; +} +/* Tab styling */ +.gr-tab { + color: #1A202C !important; + font-weight: 500 !important; + font-size: 1rem !important; + padding: 12px 20px !important; + background-color: #F7FAFC !important; +} +.gr-tab.selected { + color: #E53E3E !important; + font-weight: 600 !important; + border-bottom: 2px solid #E53E3E !important; + background-color: #FFFFFF !important; +} +/* Image upload styling */ +.gr-image { + border: 3px dashed #CBD5E0 !important; + border-radius: 16px !important; + background-color: #F7FAFC !important; + transition: all 0.2s ease !important; +} +.gr-image:hover { + border-color: #E53E3E !important; + background-color: #FFF5F5 !important; +} +/* Analyze button special styling */ +#analyze-btn { + background: linear-gradient(135deg, #1B5CF3 0%, #1E3A8A 100%) !important; + color: #FFFFFF !important; + border: none !important; + border-radius: 8px !important; + font-weight: 700 !important; + padding: 14px 28px !important; + font-size: 1.1rem !important; + letter-spacing: 0.5px !important; + text-align: center !important; + transition: all 0.2s ease-in-out !important; +} +#analyze-btn:hover { + background: linear-gradient(135deg, #174ea6 0%, #123b82 100%) !important; + box-shadow: 0 4px 14px rgba(27, 95, 193, 0.4) !important; + transform: translateY(-2px) !important; +} +#analyze-btn:disabled { + background: #A0AEC0 !important; + color: #1A202C !important; + cursor: not-allowed !important; + box-shadow: none !important; + transform: none !important; +} +/* Responsive design */ +@media (max-width: 768px) { + .medical-header { + padding: 16px !important; + text-align: center !important; + } + + .medical-header h1 { + font-size: 2rem !important; + } + + .logo { + width: 48px !important; + height: 48px !important; + margin-right: 16px !important; + } + + .gr-form { + padding: 16px !important; + margin: 8px 0 !important; + } + + button.gr-button, + button.gr-button-primary { + padding: 14px 20px !important; + font-size: 14px !important; + } +} +""" + + def create_interface(self): + """Create the main Gradio interface with original styling and base64 image embedding""" + with gr.Blocks(css=self.get_custom_css(), title="SmartHeal - AI Wound Care Assistant") as app: + + # Header with SmartHeal logo (from original) + logo_url = "https://scontent.fccu31-2.fna.fbcdn.net/v/t39.30808-6/275933824_102121829111657_3325198727201325354_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=6ee11a&_nc_ohc=45krrEUpcSUQ7kNvwGVdiMW&_nc_oc=AdkTdxEC_TkYGiyDkEtTJZ_DFZELW17XKFmWpswmFqGB7JSdvTyWtnrQyLS0USngEiY&_nc_zt=23&_nc_ht=scontent.fccu31-2.fna&_nc_gid=ufAA4Hj5gTRwON5POYzz0Q&oh=00_AfW1-jLEN5RGeggqOvGgEaK_gdg0EDgxf_VhKbZwFLUO0Q&oe=6897A98B" + + gr.HTML(f""" +
+ +
+

SmartHeal AI

+

Advanced Wound Care Analysis & Clinical Support System

+
+
+ """) + + # Professional disclaimer (from original) + gr.HTML(""" +
+

⚠️ IMPORTANT DISCLAIMER

+

This model is for testing and educational purposes only and is NOT a replacement for professional medical advice.

+

Information generated may be inaccurate. Always consult a qualified healthcare provider for medical concerns. This AI system uses chain-of-thought reasoning to show its decision-making process, but should never be used as the sole basis for clinical decisions.

+

Uploaded images may be stored and used for testing and model improvement purposes.

+
+ """) + + # Main interface with conditional visibility (ORIGINAL STRUCTURE) + with gr.Row(): + # Professional Authentication Panel (visible when not logged in) + with gr.Column(visible=True) as auth_panel: + gr.HTML(""" +
+
+

🏥 SmartHeal Access

+

Secure Healthcare Professional Portal

+
+
+ """) + + with gr.Tabs(): + with gr.Tab("🔐 Professional Login") as login_tab: + gr.HTML(""" +
+
+

Welcome Back

+

Access your professional dashboard

+
+
+ """) + + login_username = gr.Textbox( + label="👤 Username", + placeholder="Enter your username" + ) + login_password = gr.Textbox( + label="🔒 Password", + type="password", + placeholder="Enter your secure password" + ) + + login_btn = gr.Button( + "🚀 Sign In to Dashboard", + variant="primary", + size="lg" + ) + + login_status = gr.HTML( + value="
Enter your credentials to access the system
" + ) + + with gr.Tab("📝 New Registration") as signup_tab: + gr.HTML(""" +
+
+

Create Account

+

Join the SmartHeal healthcare network

+
+
+ """) + + signup_username = gr.Textbox( + label="👤 Username", + placeholder="Choose a unique username" + ) + signup_email = gr.Textbox( + label="📧 Email Address", + placeholder="Enter your professional email" + ) + signup_password = gr.Textbox( + label="🔒 Password", + type="password", + placeholder="Create a strong password" + ) + signup_name = gr.Textbox( + label="👨‍⚕️ Full Name", + placeholder="Enter your full professional name" + ) + signup_role = gr.Radio( + ["practitioner", "organization"], + label="🏥 Account Type", + value="practitioner" + ) + + # Organization-specific fields + with gr.Group(visible=False) as org_fields: + gr.HTML("

🏢 Organization Details

") + org_name = gr.Textbox(label="Organization Name", placeholder="Enter organization name") + phone = gr.Textbox(label="Phone Number", placeholder="Enter contact number") + country_code = gr.Textbox(label="Country Code", placeholder="e.g., +1, +44") + department = gr.Textbox(label="Department", placeholder="e.g., Emergency, Surgery") + location = gr.Textbox(label="Location", placeholder="City, State/Province, Country") + + # Practitioner-specific fields + with gr.Group(visible=True) as prac_fields: + gr.HTML("

🏥 Affiliation

") + organization_dropdown = gr.Dropdown( + choices=self.get_organizations_dropdown(), + label="Select Your Organization" + ) + + signup_btn = gr.Button( + "✨ Create Professional Account", + variant="primary", + size="lg" + ) + + signup_status = gr.HTML( + value="
Fill in your details to create an account
" + ) + + # Practitioner Interface (hidden initially) + with gr.Column(visible=False) as practitioner_panel: + gr.HTML('
👩‍⚕️ Practitioner Dashboard
') + + user_info = gr.HTML("") + logout_btn_prac = gr.Button("🚪 Logout", variant="secondary", elem_classes=["logout-btn"]) + + # Main tabs for different functions + with gr.Tabs(): + # WOUND ANALYSIS TAB + with gr.Tab("🔬 Wound Analysis"): + with gr.Row(): + with gr.Column(scale=1): + gr.HTML("

📋 Patient Information

") + patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient's full name") + patient_age = gr.Number(label="Age", value=30, minimum=0, maximum=120) + patient_gender = gr.Dropdown( + choices=["Male", "Female", "Other"], + label="Gender", + value="Male" + ) + + gr.HTML("

🩹 Wound Information

") + wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left ankle, Right arm") + wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks, 1 month") + pain_level = gr.Slider( + minimum=0, maximum=10, value=5, step=1, + label="Pain Level (0-10)" + ) + + gr.HTML("

⚕️ Clinical Assessment

") + moisture_level = gr.Dropdown( + choices=["Dry", "Moist", "Wet", "Saturated"], + label="Moisture Level", + value="Moist" + ) + infection_signs = gr.Dropdown( + choices=["None", "Mild", "Moderate", "Severe"], + label="Signs of Infection", + value="None" + ) + diabetic_status = gr.Dropdown( + choices=["Non-diabetic", "Type 1", "Type 2", "Gestational"], + label="Diabetic Status", + value="Non-diabetic" + ) + + with gr.Column(scale=1): + gr.HTML("

📸 Wound Image Upload

") + wound_image = gr.Image( + label="Upload Wound Image", + type="filepath", + elem_classes=["image-upload"] + ) + + gr.HTML("

📝 Medical History

") + previous_treatment = gr.Textbox( + label="Previous Treatment", + placeholder="Describe any previous treatments...", + lines=3 + ) + medical_history = gr.Textbox( + label="Medical History", + placeholder="Relevant medical conditions, surgeries, etc...", + lines=3 + ) + medications = gr.Textbox( + label="Current Medications", + placeholder="List current medications...", + lines=2 + ) + allergies = gr.Textbox( + label="Known Allergies", + placeholder="List any known allergies...", + lines=2 + ) + additional_notes = gr.Textbox( + label="Additional Notes", + placeholder="Any additional clinical observations...", + lines=3 + ) + + analyze_btn = gr.Button("🔬 Analyze Wound", variant="primary", size="lg") + analysis_output = gr.HTML("") + + # PATIENT HISTORY TAB + with gr.Tab("📋 Patient History"): + with gr.Row(): + with gr.Column(scale=2): + gr.HTML("

📊 Patient History Dashboard

") + history_btn = gr.Button("📋 Load Patient History", variant="primary") + patient_history_output = gr.HTML("") + + with gr.Column(scale=1): + gr.HTML("

🔍 Search Specific Patient

") + search_patient_name = gr.Textbox( + label="Patient Name", + placeholder="Enter patient name to search..." + ) + search_patient_btn = gr.Button("🔍 Search Patient History", variant="secondary") + specific_patient_output = gr.HTML("") + + # Interface already complete above - no additional tabs needed + + # Event handlers + def handle_login(username, password): + user_data = self.auth_manager.authenticate_user(username, password) + if user_data: + self.current_user = user_data + return { + auth_panel: gr.update(visible=False), + practitioner_panel: gr.update(visible=True), + login_status: "
✅ Login successful! Welcome to SmartHeal
" + } + else: + return { + login_status: "
❌ Invalid credentials. Please try again.
" + } + + def handle_signup(username, email, password, name, role, org_name, phone, country_code, department, location, organization_dropdown): + try: + if role == "organization": + org_data = { + 'org_name': org_name, + 'phone': phone, + 'country_code': country_code, + 'department': department, + 'location': location + } + org_id = self.database_manager.create_organization(org_data) + + user_data = { + 'username': username, + 'email': email, + 'password': password, + 'name': name, + 'role': role, + 'org_id': org_id + } + else: + # Extract org_id from dropdown selection + org_id = 1 # Default organization for now + user_data = { + 'username': username, + 'email': email, + 'password': password, + 'name': name, + 'role': role, + 'org_id': org_id + } + + if self.auth_manager.create_user(user_data): + return { + signup_status: "
✅ Account created successfully! Please login.
" + } + else: + return { + signup_status: "
❌ Failed to create account. Username or email may already exist.
" + } + except Exception as e: + return { + signup_status: f"
❌ Error: {str(e)}
" + } + + def handle_analysis(patient_name, patient_age, patient_gender, wound_location, wound_duration, + pain_level, moisture_level, infection_signs, diabetic_status, previous_treatment, + medical_history, medications, allergies, additional_notes, wound_image): + try: + if not wound_image: + return "
❌ Please upload a wound image for analysis.
" + + # Show loading state + loading_html = """ +
+
+

Processing wound analysis...

+ +
+ """ + + # 1. Construct questionnaire dictionary + questionnaire_data = { + 'user_id': self.current_user.get('id'), + 'patient_name': patient_name, + 'patient_age': patient_age, + 'patient_gender': patient_gender, + 'wound_location': wound_location, + 'wound_duration': wound_duration, + 'pain_level': pain_level, + 'moisture_level': moisture_level, + 'infection_signs': infection_signs, + 'diabetic_status': diabetic_status, + 'previous_treatment': previous_treatment, + 'medical_history': medical_history, + 'medications': medications, + 'allergies': allergies, + 'additional_notes': additional_notes + } + + # 2. Save questionnaire in DB + questionnaire_id = self.database_manager.save_questionnaire(questionnaire_data) + + # 3. Run AI analysis with uploaded image + try: + # Log information about the wound image + if hasattr(wound_image, 'name'): + logging.info(f"Processing image: {wound_image.name}") + + # First try direct analysis with the file-like object + analysis_result = self.wound_analyzer.analyze_wound(wound_image, questionnaire_data) + except Exception as e: + logging.error(f"AI analysis error (first attempt): {e}") + try: + # If that fails, try with PIL image + from PIL import Image + import io + + # Reset file pointer if possible + if hasattr(wound_image, 'seek'): + wound_image.seek(0) + + # Convert to PIL Image + pil_image = Image.open(wound_image) + analysis_result = self.wound_analyzer.analyze_wound(pil_image, questionnaire_data) + except Exception as e2: + logging.error(f"AI analysis error (second attempt): {e2}") + # Return error information for display + return f""" +
+

❌ Analysis Error

+

There was an error analyzing the wound image:

+
{str(e)}\n{str(e2) if 'e2' in locals() else ''}
+

Please try again with a different image or contact support.

+
+ """ + + # 4. Save AI analysis result + self.database_manager.save_analysis_result(questionnaire_id, analysis_result) + + # 5. Save wound image metadata + if isinstance(wound_image, str): + image_url = wound_image + elif hasattr(wound_image, 'name'): + image_url = wound_image.name + else: + image_url = 'unknown' + + image_data = { + 'image_url': image_url, + 'filename': os.path.basename(image_url), + 'file_size': None, + 'width': None, + 'height': None + } + + # 6. Format analysis results with visualization + formatted_analysis = self._format_analysis_results(analysis_result, image_url) + + # 7. Generate HTML professional report for complete analysis + professional_report = self.report_generator.generate_analysis_report( + questionnaire_data, + analysis_result, + image_data.get('image_url') + ) + + return formatted_analysis + professional_report + + except Exception as e: + logging.error(f"Analysis error: {e}") + return f"
❌ Analysis failed: {str(e)}
" + + + + def handle_logout(): + self.current_user = {} + return { + auth_panel: gr.update(visible=True), + practitioner_panel: gr.update(visible=False) + } + + def toggle_role_fields(role): + if role == "organization": + return { + org_fields: gr.update(visible=True), + prac_fields: gr.update(visible=False) + } + else: + return { + org_fields: gr.update(visible=False), + prac_fields: gr.update(visible=True) + } + + def load_patient_history(): + try: + user_id = self.current_user.get('id') + if not user_id: + return "
❌ Please login first.
" + + history_data = self.patient_history_manager.get_user_patient_history(user_id) + formatted_history = self.patient_history_manager.format_history_for_display(history_data) + return formatted_history + except Exception as e: + logging.error(f"Error loading patient history: {e}") + return f"
❌ Error loading history: {str(e)}
" + + def search_specific_patient(patient_name): + try: + user_id = self.current_user.get('id') + if not user_id: + return "
❌ Please login first.
" + + if not patient_name.strip(): + return "
⚠️ Please enter a patient name to search.
" + + patient_data = self.patient_history_manager.search_patient_by_name(user_id, patient_name.strip()) + if patient_data: + formatted_data = self.patient_history_manager.format_patient_data_for_display(patient_data) + return formatted_data + else: + return f"
⚠️ No records found for patient: {patient_name}
" + + except Exception as e: + logging.error(f"Error searching patient: {e}") + return f"
❌ Error searching patient: {str(e)}
" + + # Bind event handlers + login_btn.click( + handle_login, + inputs=[login_username, login_password], + outputs=[auth_panel, practitioner_panel, login_status] + ) + + signup_btn.click( + handle_signup, + inputs=[signup_username, signup_email, signup_password, signup_name, signup_role, + org_name, phone, country_code, department, location, organization_dropdown], + outputs=[signup_status] + ) + + signup_role.change( + toggle_role_fields, + inputs=[signup_role], + outputs=[org_fields, prac_fields] + ) + + analyze_btn.click( + handle_analysis, + inputs=[patient_name, patient_age, patient_gender, wound_location, wound_duration, + pain_level, moisture_level, infection_signs, diabetic_status, previous_treatment, + medical_history, medications, allergies, additional_notes, wound_image], + outputs=[analysis_output] + ) + + logout_btn_prac.click( + handle_logout, + outputs=[auth_panel, practitioner_panel] + ) + + history_btn.click( + load_patient_history, + outputs=[patient_history_output] + ) + + search_patient_btn.click( + search_specific_patient, + inputs=[search_patient_name], + outputs=[specific_patient_output] + ) + return app + def _format_analysis_results(self, analysis_result, image_url=None): + """Format analysis results for HTML display with base64 encoded images, always showing segmentation overlay.""" + try: + # Extract key results + summary = analysis_result.get('summary', 'Analysis completed') + wound_detection = analysis_result.get('wound_detection', {}) + segmentation_result = analysis_result.get('segmentation_result', {}) + risk_assessment = analysis_result.get('risk_assessment', {}) + recommendations = analysis_result.get('recommendations', '') + comprehensive_report = analysis_result.get('comprehensive_report', '') + + # Detection metrics + detection_confidence = 0.0 + wound_type = "Unknown" + length_cm = breadth_cm = area_cm2 = 0 + + if wound_detection.get('status') == 'success' and wound_detection.get('detections'): + detections = wound_detection.get('detections', []) + if detections: + detection_confidence = detections[0].get('detection_confidence', 0.0) + wound_type = detections[0].get('wound_type', 'Unknown') + length_cm = detections[0].get('length_cm', 0) + breadth_cm = detections[0].get('breadth_cm', 0) + area_cm2 = detections[0].get('surface_area_cm2', 0) + + risk_level = risk_assessment.get('risk_level', 'Unknown') + risk_score = risk_assessment.get('risk_score', 0) + risk_factors = risk_assessment.get('risk_factors', []) + + # Set risk class for styling + risk_class = "low" + if risk_level.lower() == "moderate": + risk_class = "moderate" + elif risk_level.lower() == "high": + risk_class = "high" + + # Format risk factors + risk_factors_html = "" if risk_factors else "

No specific risk factors identified.

" + + # Format guideline recommendations + guideline_recommendations = analysis_result.get('guideline_recommendations', []) + recommendations_html = "" if guideline_recommendations else "

No specific recommendations available.

" + + # --- Detection image --- + detection_image_base64 = None + if "overlay_path" in wound_detection and os.path.exists(wound_detection["overlay_path"]): + detection_image_base64 = self.image_to_base64(wound_detection["overlay_path"]) + elif comprehensive_report: + detection_match = re.search(r"Detection Image: (.+?)(?:\n|$)", comprehensive_report) + if detection_match and detection_match.group(1) != "Not available" and os.path.exists(detection_match.group(1).strip()): + detection_image_base64 = self.image_to_base64(detection_match.group(1).strip()) + + detection_image_html = "" + if detection_image_base64: + detection_image_html = f""" +
+

Wound Detection Visualization

+ Wound Detection +
+ """ + + detection_html = f""" +
+

🔍 Wound Detection & Classification

+ {detection_image_html} +
+
+

Wound Type

+

{wound_type}

+
+
+

Detection Confidence

+

{detection_confidence:.1%}

+
+
+

Total Wounds Detected

+

{wound_detection.get('total_wounds', 0)}

+
+
+
+ """ if wound_detection.get('status') == 'success' else f""" +
+ Detection Status: Failed
+ Reason: {wound_detection.get('message', 'Unknown error')} +
+ """ + + # --- Segmentation overlay: prefer direct result! --- + segmentation_image_base64 = None + if "overlay_pil" in segmentation_result and isinstance(segmentation_result["overlay_pil"], Image.Image): + segmentation_image_base64 = pil_to_base64(segmentation_result["overlay_pil"]) + elif "overlay_path" in segmentation_result and os.path.exists(segmentation_result["overlay_path"]): + segmentation_image_base64 = self.image_to_base64(segmentation_result["overlay_path"]) + elif comprehensive_report: + segmentation_match = re.search(r"Segmentation Image: (.+?)(?:\n|$)", comprehensive_report) + if segmentation_match and segmentation_match.group(1) != "Not available" and os.path.exists(segmentation_match.group(1).strip()): + segmentation_image_base64 = self.image_to_base64(segmentation_match.group(1).strip()) + + segmentation_image_html = "" + if segmentation_image_base64: + segmentation_image_html = f""" +
+

Wound Segmentation Visualization

+ Wound Segmentation +
+ """ + + segmentation_html = f""" +
+

📏 Wound Measurements

+ {segmentation_image_html} +
+
+

Length

+

{length_cm:.2f} cm

+
+
+

Width

+

{breadth_cm:.2f} cm

+
+
+

Surface Area

+

{area_cm2:.2f} cm²

+
+
+
+ """ if segmentation_result.get('status') == 'success' else f""" +
+ Segmentation Status: Failed
+ Reason: {segmentation_result.get('message', 'Unknown error')} +
+ """ + + # --- Main input image preview --- + image_visualization = "" + if image_url and os.path.exists(image_url): + image_base64 = self.image_to_base64(image_url) + if image_base64: + image_visualization = f""" +
+

🖼️ Wound Image

+
+ Wound Image +

+ Analysis completed successfully +

+
+
+ """ + + # --- Comprehensive report as HTML --- + comprehensive_report_html = "" + if comprehensive_report: + report_without_images = re.sub(r'## Analysis Images.*?(?=##|$)', '', comprehensive_report, flags=re.DOTALL) + comprehensive_report_html = self.markdown_to_html(report_without_images) + + # --- Final Output --- + html_output = f""" +
+
+

🔬 SmartHeal AI Analysis Results

+

Advanced Computer Vision & Medical AI Assessment

+
+
+
+ Analysis Summary: {summary} +
+ {image_visualization} + {detection_html} + {segmentation_html} +
+

⚠️ Risk Assessment

+
+
+ {risk_level} RISK +
+
+ Risk Score: {risk_score}/10 +
+
+
+

Identified Risk Factors:

+ {risk_factors_html} +
+
+
+

💡 Clinical Recommendations

+
+ {recommendations_html} +
+
+

+ ⚠️ Note: These recommendations are generated by AI and should be verified by healthcare professionals. +

+
+
+ {f'

📋 Comprehensive Report

{comprehensive_report_html}
' if comprehensive_report_html else ''} +
+
+

+ Analysis completed by SmartHeal AI - Advanced Wound Care Assistant +

+
+
+
+ """ + return html_output + + except Exception as e: + logging.error(f"Error formatting results: {e}") + return f"
❌ Error displaying results: {str(e)}
" \ No newline at end of file