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 = True
- result_lines.append(f"{stripped[2:]} ")
- else:
- if 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"""
-
- """)
-
- # 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 = ""
- for factor in risk_factors:
- risk_factors_html += f"{factor} "
- 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 = ""
- for rec in guideline_recommendations:
- if rec and len(rec) > 10: # Avoid very short fragments
- recommendations_html += f"{rec} "
- 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
-
-
- """
-
- 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
-
-
- """
-
- 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
-
-
-
- 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 = True
+ result_lines.append(f"{stripped[2:]} ")
+ else:
+ if 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"""
+
+ """)
+
+ # 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 = "" + "".join(f"{factor} " for factor in risk_factors) + " " if risk_factors else "No specific risk factors identified.
"
+
+ # Format guideline recommendations
+ guideline_recommendations = analysis_result.get('guideline_recommendations', [])
+ recommendations_html = "" + "".join(f"{rec} " for rec in guideline_recommendations if rec and len(rec) > 10) + " " 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
+
+
+ """
+
+ 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
+
+
+ """
+
+ 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
+
+
+
+ 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