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)}
"