|
|
import gradio as gr |
|
|
import logging |
|
|
from datetime import datetime |
|
|
import time |
|
|
from typing import Dict, Any, Optional, Tuple |
|
|
import json |
|
|
import os |
|
|
from PIL import Image |
|
|
|
|
|
from .enhanced_ai_processor import EnhancedAIProcessor |
|
|
from .dashboard_database_manager import DashboardDatabaseManager |
|
|
from .dashboard_api import DashboardIntegrationManager |
|
|
from .auth import AuthManager |
|
|
|
|
|
class EnhancedUIComponents: |
|
|
"""Enhanced UI components with dashboard integration and analytics tracking""" |
|
|
|
|
|
def __init__(self, auth_manager: AuthManager, database_manager: DashboardDatabaseManager, |
|
|
ai_processor: EnhancedAIProcessor): |
|
|
"""Initialize enhanced UI components""" |
|
|
self.auth_manager = auth_manager |
|
|
self.database_manager = database_manager |
|
|
self.ai_processor = ai_processor |
|
|
self.dashboard_integration = DashboardIntegrationManager(database_manager) |
|
|
|
|
|
|
|
|
self.dashboard_integration.start_integration() |
|
|
|
|
|
|
|
|
self.theme = gr.themes.Soft() |
|
|
self.custom_css = self._load_custom_css() |
|
|
|
|
|
|
|
|
self.current_session = {} |
|
|
|
|
|
logging.info("β
Enhanced UI Components initialized with dashboard integration") |
|
|
|
|
|
def _load_custom_css(self): |
|
|
"""Load custom CSS for the application""" |
|
|
return """ |
|
|
/* =================== 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; |
|
|
} |
|
|
.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; |
|
|
} |
|
|
/* 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; |
|
|
} |
|
|
/* 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: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; |
|
|
} |
|
|
/* 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; |
|
|
} |
|
|
/* Image gallery styling for better visualization */ |
|
|
.image-gallery { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
|
|
gap: 20px; |
|
|
margin: 20px 0; |
|
|
} |
|
|
.image-item { |
|
|
background: #f8f9fa; |
|
|
border-radius: 12px; |
|
|
padding: 15px; |
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1); |
|
|
text-align: center; |
|
|
} |
|
|
.image-item img { |
|
|
max-width: 100%; |
|
|
height: auto; |
|
|
border-radius: 8px; |
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15); |
|
|
} |
|
|
.image-item h4 { |
|
|
margin: 15px 0 5px 0; |
|
|
color: #2d3748; |
|
|
font-weight: 600; |
|
|
} |
|
|
.image-item p { |
|
|
margin: 0; |
|
|
color: #666; |
|
|
font-size: 0.9em; |
|
|
} |
|
|
/* 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; |
|
|
} |
|
|
/* 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; |
|
|
} |
|
|
|
|
|
.image-gallery { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
} |
|
|
""" |
|
|
|
|
|
def create_interface(self): |
|
|
"""Create the enhanced Gradio interface with dashboard integration""" |
|
|
|
|
|
with gr.Blocks(theme=self.theme, css=self.custom_css, title="SmartHeal AI - Enhanced") as interface: |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div class="main-header"> |
|
|
<h1>π₯ SmartHeal AI - Enhanced Edition</h1> |
|
|
<p>Advanced Wound Care Analysis with Real-time Dashboard Integration</p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
integration_status = gr.HTML(self._get_integration_status_html()) |
|
|
|
|
|
|
|
|
session_info = gr.HTML(self._get_session_info_html()) |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
|
|
|
with gr.Tab("π Authentication"): |
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
gr.HTML(""" |
|
|
<div class="section-header"> |
|
|
<h2>User Authentication</h2> |
|
|
<h3>Login to access SmartHeal AI analysis features</h3> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
username_input = gr.Textbox( |
|
|
label="Username", |
|
|
placeholder="Enter your username", |
|
|
interactive=True |
|
|
) |
|
|
password_input = gr.Textbox( |
|
|
label="Password", |
|
|
type="password", |
|
|
placeholder="Enter your password", |
|
|
interactive=True |
|
|
) |
|
|
login_btn = gr.Button("Login", variant="primary") |
|
|
logout_btn = gr.Button("Logout", variant="secondary") |
|
|
|
|
|
auth_status = gr.HTML(value="<div class='warning-box'>Please login to continue</div>") |
|
|
|
|
|
|
|
|
with gr.Tab("π¬ Wound Analysis"): |
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.HTML(""" |
|
|
<div class="section-header"> |
|
|
<h2>Patient Information</h2> |
|
|
<h3>Complete patient details for comprehensive analysis</h3> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient name") |
|
|
patient_age = gr.Number(label="Patient Age", value=0, minimum=0, maximum=120) |
|
|
patient_gender = gr.Dropdown( |
|
|
label="Gender", |
|
|
choices=["Male", "Female", "Other"], |
|
|
value="Male" |
|
|
) |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div class="section-header"> |
|
|
<h2>Wound Information</h2> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left heel, Right forearm") |
|
|
wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks, 1 month") |
|
|
pain_level = gr.Slider(label="Pain Level (0-10)", minimum=0, maximum=10, value=0, step=1) |
|
|
|
|
|
|
|
|
moisture_level = gr.Dropdown( |
|
|
label="Moisture Level", |
|
|
choices=["Dry", "Moist", "Wet", "Macerated"], |
|
|
value="Moist" |
|
|
) |
|
|
infection_signs = gr.Dropdown( |
|
|
label="Signs of Infection", |
|
|
choices=["None", "Mild", "Moderate", "Severe"], |
|
|
value="None" |
|
|
) |
|
|
diabetic_status = gr.Dropdown( |
|
|
label="Diabetic Status", |
|
|
choices=["No", "Type 1", "Type 2", "Unknown"], |
|
|
value="No" |
|
|
) |
|
|
|
|
|
|
|
|
gr.HTML(""" |
|
|
<div class="section-header"> |
|
|
<h2>Medical History</h2> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
previous_treatment = gr.Textbox( |
|
|
label="Previous Treatment", |
|
|
placeholder="Describe any previous treatments", |
|
|
lines=2 |
|
|
) |
|
|
medical_history = gr.Textbox( |
|
|
label="Medical History", |
|
|
placeholder="Relevant medical conditions", |
|
|
lines=2 |
|
|
) |
|
|
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 relevant information", |
|
|
lines=3 |
|
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
gr.HTML(""" |
|
|
<div class="section-header"> |
|
|
<h2>Wound Image Analysis</h2> |
|
|
<h3>Upload wound image for AI analysis</h3> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
wound_image = gr.Image( |
|
|
label="Wound Image", |
|
|
type="pil", |
|
|
height=400 |
|
|
) |
|
|
|
|
|
|
|
|
analyze_btn = gr.Button("π Analyze Wound", variant="primary", size="lg") |
|
|
|
|
|
|
|
|
processing_status = gr.HTML(visible=False) |
|
|
|
|
|
|
|
|
analysis_metrics = gr.HTML(visible=False) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
gr.HTML(""" |
|
|
<div class="section-header"> |
|
|
<h2>Analysis Results</h2> |
|
|
<h3>Comprehensive AI-powered wound assessment</h3> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
detection_image = gr.Image(label="Wound Detection", visible=False) |
|
|
segmentation_image = gr.Image(label="Wound Segmentation", visible=False) |
|
|
|
|
|
|
|
|
analysis_report = gr.Markdown(visible=False) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
download_report = gr.File(label="Download Report", visible=False) |
|
|
download_images = gr.File(label="Download Analysis Images", visible=False) |
|
|
|
|
|
|
|
|
with gr.Tab("π Dashboard Integration"): |
|
|
gr.HTML(""" |
|
|
<div class="section-header"> |
|
|
<h2>Dashboard Integration Status</h2> |
|
|
<h3>Real-time connection to SmartHeal Dashboard</h3> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
dashboard_status = gr.HTML() |
|
|
|
|
|
with gr.Row(): |
|
|
refresh_status_btn = gr.Button("π Refresh Status", variant="secondary") |
|
|
view_analytics_btn = gr.Button("π View Analytics", variant="primary") |
|
|
|
|
|
|
|
|
analytics_summary = gr.HTML() |
|
|
|
|
|
|
|
|
recent_activity = gr.HTML() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
login_btn.click( |
|
|
fn=self._handle_login, |
|
|
inputs=[username_input, password_input], |
|
|
outputs=[auth_status, session_info] |
|
|
) |
|
|
|
|
|
logout_btn.click( |
|
|
fn=self._handle_logout, |
|
|
outputs=[auth_status, session_info] |
|
|
) |
|
|
|
|
|
|
|
|
analyze_btn.click( |
|
|
fn=self._start_analysis, |
|
|
inputs=[], |
|
|
outputs=[processing_status, analysis_metrics] |
|
|
).then( |
|
|
fn=self._perform_enhanced_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_report, detection_image, segmentation_image, |
|
|
download_report, download_images, processing_status, |
|
|
analysis_metrics, session_info |
|
|
] |
|
|
) |
|
|
|
|
|
|
|
|
refresh_status_btn.click( |
|
|
fn=self._refresh_dashboard_status, |
|
|
outputs=[dashboard_status, analytics_summary] |
|
|
) |
|
|
|
|
|
view_analytics_btn.click( |
|
|
fn=self._get_analytics_summary, |
|
|
outputs=[analytics_summary, recent_activity] |
|
|
) |
|
|
|
|
|
|
|
|
interface.load( |
|
|
fn=self._refresh_dashboard_status, |
|
|
outputs=[dashboard_status, analytics_summary] |
|
|
) |
|
|
|
|
|
return interface |
|
|
|
|
|
def _get_integration_status_html(self) -> str: |
|
|
"""Get HTML for integration status display""" |
|
|
status = self.dashboard_integration.get_integration_status() |
|
|
|
|
|
if status['api_running'] and status['database_connected']: |
|
|
return """ |
|
|
<div class="integration-status"> |
|
|
β
<strong>Dashboard Integration Active</strong><br> |
|
|
API Server: Running | Database: Connected | Real-time Analytics: Enabled |
|
|
</div> |
|
|
""" |
|
|
else: |
|
|
return """ |
|
|
<div class="error-box"> |
|
|
β <strong>Dashboard Integration Issues</strong><br> |
|
|
Please check API server and database connection |
|
|
</div> |
|
|
""" |
|
|
|
|
|
def _get_session_info_html(self) -> str: |
|
|
"""Get HTML for session information display""" |
|
|
if self.current_session: |
|
|
user_info = self.current_session.get('user_info', {}) |
|
|
return f""" |
|
|
<div class="session-info"> |
|
|
π€ <strong>Active Session</strong><br> |
|
|
User: {user_info.get('name', 'Unknown')} | |
|
|
Role: {user_info.get('role', 'Unknown')} | |
|
|
Session Started: {self.current_session.get('start_time', 'Unknown')} |
|
|
</div> |
|
|
""" |
|
|
else: |
|
|
return """ |
|
|
<div class="warning-box"> |
|
|
β οΈ <strong>No Active Session</strong><br> |
|
|
Please login to start tracking your analysis session |
|
|
</div> |
|
|
""" |
|
|
|
|
|
def _handle_login(self, username: str, password: str) -> Tuple[str, str]: |
|
|
"""Handle user login with session tracking""" |
|
|
try: |
|
|
if not username or not password: |
|
|
return ( |
|
|
"<div class='error-box'>β Please enter both username and password</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
|
|
|
user_info = self.auth_manager.authenticate_user(username, password) |
|
|
|
|
|
if user_info: |
|
|
|
|
|
self.current_session = { |
|
|
'user_info': user_info, |
|
|
'start_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), |
|
|
'session_id': f"session_{int(time.time())}", |
|
|
'analyses_count': 0 |
|
|
} |
|
|
|
|
|
return ( |
|
|
f"<div class='success-box'>β
Welcome, {user_info.get('name', username)}! You are now logged in.</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
else: |
|
|
return ( |
|
|
"<div class='error-box'>β Invalid username or password</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Login error: {e}") |
|
|
return ( |
|
|
f"<div class='error-box'>β Login failed: {str(e)}</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
def _handle_logout(self) -> Tuple[str, str]: |
|
|
"""Handle user logout""" |
|
|
try: |
|
|
if self.current_session: |
|
|
|
|
|
session_duration = time.time() - datetime.strptime( |
|
|
self.current_session['start_time'], '%Y-%m-%d %H:%M:%S' |
|
|
).timestamp() |
|
|
|
|
|
session_data = { |
|
|
'user_id': self.current_session['user_info'].get('id'), |
|
|
'session_duration': round(session_duration / 60, 2), |
|
|
'analyses_count': self.current_session.get('analyses_count', 0) |
|
|
} |
|
|
|
|
|
|
|
|
self.current_session = {} |
|
|
|
|
|
return ( |
|
|
"<div class='warning-box'>π You have been logged out successfully</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
else: |
|
|
return ( |
|
|
"<div class='warning-box'>β οΈ No active session to logout</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Logout error: {e}") |
|
|
return ( |
|
|
f"<div class='error-box'>β Logout error: {str(e)}</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
def _start_analysis(self) -> Tuple[str, str]: |
|
|
"""Start analysis process with status indicators""" |
|
|
return ( |
|
|
""" |
|
|
<div class="processing-indicator" style="display: block;"> |
|
|
π <strong>Analysis in Progress...</strong><br> |
|
|
Please wait while we process your wound image and patient data |
|
|
</div> |
|
|
""", |
|
|
""" |
|
|
<div class="metrics-display"> |
|
|
<strong>Analysis Metrics:</strong><br> |
|
|
Status: Initializing...<br> |
|
|
Processing Time: 0.0s<br> |
|
|
Models Loading: β³ |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
def _perform_enhanced_analysis(self, patient_name: str, patient_age: int, patient_gender: str, |
|
|
wound_location: str, wound_duration: str, pain_level: int, |
|
|
moisture_level: str, infection_signs: str, diabetic_status: str, |
|
|
previous_treatment: str, medical_history: str, medications: str, |
|
|
allergies: str, additional_notes: str, wound_image) -> Tuple: |
|
|
"""Perform enhanced analysis with dashboard integration""" |
|
|
|
|
|
start_time = time.time() |
|
|
|
|
|
try: |
|
|
|
|
|
if not self.current_session: |
|
|
return ( |
|
|
"β **Authentication Required**\n\nPlease login before performing analysis.", |
|
|
None, None, None, None, |
|
|
"<div class='error-box'>β Authentication required</div>", |
|
|
"<div class='error-box'>Please login to continue</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
|
|
|
if not wound_image: |
|
|
return ( |
|
|
"β **Image Required**\n\nPlease upload a wound image for analysis.", |
|
|
None, None, None, None, |
|
|
"<div class='error-box'>β Wound image required</div>", |
|
|
"<div class='error-box'>Please upload an image</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
if not patient_name.strip(): |
|
|
return ( |
|
|
"β **Patient Name Required**\n\nPlease enter the patient's name.", |
|
|
None, None, None, None, |
|
|
"<div class='error-box'>β Patient name required</div>", |
|
|
"<div class='error-box'>Please enter patient name</div>", |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
|
|
|
patient_info = { |
|
|
'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 |
|
|
} |
|
|
|
|
|
|
|
|
user_id = self.current_session['user_info'].get('id') |
|
|
questionnaire_id = self.database_manager.save_questionnaire_response(patient_info, user_id) |
|
|
|
|
|
if not questionnaire_id: |
|
|
logging.warning("Failed to save questionnaire response") |
|
|
|
|
|
|
|
|
image_id = None |
|
|
if questionnaire_id: |
|
|
image_id = self.database_manager.save_wound_image(questionnaire_id, wound_image, "wound_analysis.jpg") |
|
|
|
|
|
|
|
|
analysis_results = self.ai_processor.perform_comprehensive_analysis(wound_image, patient_info) |
|
|
|
|
|
processing_time = analysis_results.get('processing_time', 0) |
|
|
|
|
|
|
|
|
analysis_data = { |
|
|
'questionnaire_id': questionnaire_id, |
|
|
'image_id': image_id, |
|
|
'analysis_data': analysis_results, |
|
|
'summary': analysis_results.get('report', '')[:1000], |
|
|
'recommendations': analysis_results.get('report', ''), |
|
|
'risk_score': analysis_results.get('risk_score', 0), |
|
|
'processing_time': processing_time, |
|
|
'model_version': analysis_results.get('model_version', 'v1.0'), |
|
|
'visual_results': analysis_results.get('visual_results', {}) |
|
|
} |
|
|
|
|
|
analysis_id = self.database_manager.save_ai_analysis(analysis_data) |
|
|
|
|
|
|
|
|
session_data = { |
|
|
'user_id': user_id, |
|
|
'questionnaire_id': questionnaire_id, |
|
|
'image_id': image_id, |
|
|
'analysis_id': analysis_id, |
|
|
'session_duration': processing_time |
|
|
} |
|
|
|
|
|
self.dashboard_integration.log_analysis_session(session_data) |
|
|
|
|
|
|
|
|
interaction_data = { |
|
|
'patient_id': None, |
|
|
'practitioner_id': user_id, |
|
|
'input_text': f"Wound analysis for {patient_name}", |
|
|
'output_text': analysis_results.get('report', '')[:500], |
|
|
'wound_image_url': f"uploads/wound_analysis_{int(time.time())}.jpg", |
|
|
'interaction_type': 'wound_analysis' |
|
|
} |
|
|
|
|
|
self.dashboard_integration.log_bot_interaction(interaction_data) |
|
|
|
|
|
|
|
|
self.current_session['analyses_count'] = self.current_session.get('analyses_count', 0) + 1 |
|
|
|
|
|
|
|
|
visual_results = analysis_results.get('visual_results', {}) |
|
|
report = analysis_results.get('report', 'Analysis completed but no report generated.') |
|
|
|
|
|
|
|
|
detection_image = visual_results.get('detection_image_pil') |
|
|
segmentation_image = visual_results.get('segmentation_image_pil') |
|
|
|
|
|
|
|
|
report_file = self._create_report_file(analysis_results, patient_info) |
|
|
|
|
|
|
|
|
metrics_html = f""" |
|
|
<div class="metrics-display"> |
|
|
<strong>Analysis Completed Successfully!</strong><br> |
|
|
Processing Time: {processing_time}s<br> |
|
|
Risk Score: {analysis_results.get('risk_score', 0)}/100<br> |
|
|
Wound Type: {visual_results.get('wound_type', 'Unknown')}<br> |
|
|
Surface Area: {visual_results.get('surface_area_cm2', 0)} cmΒ²<br> |
|
|
Model Version: {analysis_results.get('model_version', 'v1.0')}<br> |
|
|
Dashboard Integration: β
Active |
|
|
</div> |
|
|
""" |
|
|
|
|
|
success_status = f""" |
|
|
<div class="success-box"> |
|
|
β
<strong>Analysis Completed Successfully!</strong><br> |
|
|
Processing Time: {processing_time}s | Risk Score: {analysis_results.get('risk_score', 0)}/100<br> |
|
|
Results saved to dashboard for real-time analytics |
|
|
</div> |
|
|
""" |
|
|
|
|
|
return ( |
|
|
report, |
|
|
detection_image, |
|
|
segmentation_image, |
|
|
report_file, |
|
|
None, |
|
|
success_status, |
|
|
metrics_html, |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
processing_time = time.time() - start_time |
|
|
error_message = str(e) |
|
|
logging.error(f"Analysis error: {error_message}") |
|
|
|
|
|
error_status = f""" |
|
|
<div class="error-box"> |
|
|
β <strong>Analysis Failed</strong><br> |
|
|
Error: {error_message}<br> |
|
|
Processing Time: {processing_time:.2f}s |
|
|
</div> |
|
|
""" |
|
|
|
|
|
error_metrics = f""" |
|
|
<div class="error-box"> |
|
|
<strong>Analysis Error:</strong><br> |
|
|
Status: Failed<br> |
|
|
Processing Time: {processing_time:.2f}s<br> |
|
|
Error: {error_message} |
|
|
</div> |
|
|
""" |
|
|
|
|
|
return ( |
|
|
f"β **Analysis Failed**\n\n**Error:** {error_message}\n\nPlease check your inputs and try again.", |
|
|
None, None, None, None, |
|
|
error_status, |
|
|
error_metrics, |
|
|
self._get_session_info_html() |
|
|
) |
|
|
|
|
|
def _create_report_file(self, analysis_results: Dict[str, Any], patient_info: Dict[str, Any]) -> str: |
|
|
"""Create downloadable report file""" |
|
|
try: |
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') |
|
|
filename = f"wound_analysis_report_{timestamp}.md" |
|
|
filepath = os.path.join("uploads", filename) |
|
|
|
|
|
|
|
|
os.makedirs("uploads", exist_ok=True) |
|
|
|
|
|
|
|
|
report_content = f"""# SmartHeal AI Wound Analysis Report |
|
|
|
|
|
**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} |
|
|
**Patient:** {patient_info.get('patient_name', 'N/A')} |
|
|
**Analysis ID:** {timestamp} |
|
|
|
|
|
## Patient Information |
|
|
- **Name:** {patient_info.get('patient_name', 'N/A')} |
|
|
- **Age:** {patient_info.get('patient_age', 'N/A')} years |
|
|
- **Gender:** {patient_info.get('patient_gender', 'N/A')} |
|
|
- **Wound Location:** {patient_info.get('wound_location', 'N/A')} |
|
|
- **Wound Duration:** {patient_info.get('wound_duration', 'N/A')} |
|
|
- **Pain Level:** {patient_info.get('pain_level', 'N/A')}/10 |
|
|
|
|
|
## Analysis Results |
|
|
{analysis_results.get('report', 'No report generated')} |
|
|
|
|
|
## Technical Details |
|
|
- **Processing Time:** {analysis_results.get('processing_time', 0)}s |
|
|
- **Risk Score:** {analysis_results.get('risk_score', 0)}/100 |
|
|
- **Model Version:** {analysis_results.get('model_version', 'Unknown')} |
|
|
- **Analysis Timestamp:** {analysis_results.get('analysis_timestamp', 'Unknown')} |
|
|
|
|
|
--- |
|
|
*Generated by SmartHeal AI Enhanced Edition with Dashboard Integration* |
|
|
""" |
|
|
|
|
|
with open(filepath, 'w', encoding='utf-8') as f: |
|
|
f.write(report_content) |
|
|
|
|
|
return filepath |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Error creating report file: {e}") |
|
|
return None |
|
|
|
|
|
def _refresh_dashboard_status(self) -> Tuple[str, str]: |
|
|
"""Refresh dashboard integration status""" |
|
|
try: |
|
|
status = self.dashboard_integration.get_integration_status() |
|
|
analytics_data = self.database_manager.get_analytics_data() |
|
|
|
|
|
if status['api_running'] and status['database_connected']: |
|
|
status_html = f""" |
|
|
<div class="integration-status"> |
|
|
β
<strong>Dashboard Integration Active</strong><br> |
|
|
API Server: Running on port 5001<br> |
|
|
Database: Connected<br> |
|
|
Last Updated: {status['timestamp']}<br> |
|
|
<a href="http://localhost:5001/api/health" target="_blank">π Test API Health</a> |
|
|
</div> |
|
|
""" |
|
|
else: |
|
|
status_html = f""" |
|
|
<div class="error-box"> |
|
|
β <strong>Dashboard Integration Issues</strong><br> |
|
|
API Running: {status['api_running']}<br> |
|
|
Database Connected: {status['database_connected']}<br> |
|
|
Last Checked: {status['timestamp']} |
|
|
</div> |
|
|
""" |
|
|
|
|
|
analytics_html = f""" |
|
|
<div class="analytics-info"> |
|
|
π <strong>Analytics Summary</strong><br> |
|
|
Total Analyses: {analytics_data.get('total_analyses', 0)}<br> |
|
|
Average Processing Time: {analytics_data.get('avg_processing_time', 0)}s<br> |
|
|
High Risk Cases: {analytics_data.get('high_risk_count', 0)}<br> |
|
|
Average Risk Score: {analytics_data.get('avg_risk_score', 0)}<br> |
|
|
Analyses Today: {analytics_data.get('analyses_today', 0)} |
|
|
</div> |
|
|
""" |
|
|
|
|
|
return status_html, analytics_html |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Error refreshing dashboard status: {e}") |
|
|
return ( |
|
|
f"<div class='error-box'>β Error refreshing status: {str(e)}</div>", |
|
|
"<div class='error-box'>β Unable to load analytics</div>" |
|
|
) |
|
|
|
|
|
def _get_analytics_summary(self) -> Tuple[str, str]: |
|
|
"""Get comprehensive analytics summary""" |
|
|
try: |
|
|
analytics_data = self.database_manager.get_analytics_data() |
|
|
interaction_history = self.database_manager.get_interaction_history(10) |
|
|
|
|
|
|
|
|
analytics_html = f""" |
|
|
<div class="analytics-info"> |
|
|
<h3>π Comprehensive Analytics</h3> |
|
|
<strong>Analysis Statistics:</strong><br> |
|
|
β’ Total Analyses: {analytics_data.get('total_analyses', 0)}<br> |
|
|
β’ Analyses Today: {analytics_data.get('analyses_today', 0)}<br> |
|
|
β’ Analyses This Week: {analytics_data.get('analyses_this_week', 0)}<br> |
|
|
β’ Average Processing Time: {analytics_data.get('avg_processing_time', 0)}s<br> |
|
|
β’ Average Risk Score: {analytics_data.get('avg_risk_score', 0)}/100<br> |
|
|
<br> |
|
|
<strong>Risk Distribution:</strong><br> |
|
|
β’ High Risk Cases: {analytics_data.get('high_risk_count', 0)}<br> |
|
|
β’ Unique Questionnaires: {analytics_data.get('unique_questionnaires', 0)}<br> |
|
|
β’ Analyses with Images: {analytics_data.get('analyses_with_images', 0)}<br> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
activity_html = "<div class='result-box'><h3>π Recent Activity</h3>" |
|
|
|
|
|
if interaction_history: |
|
|
activity_html += "<ul>" |
|
|
for interaction in interaction_history[:5]: |
|
|
timestamp = interaction.get('interacted_at', 'Unknown') |
|
|
if isinstance(timestamp, str): |
|
|
try: |
|
|
timestamp = datetime.fromisoformat(timestamp.replace('Z', '+00:00')).strftime('%Y-%m-%d %H:%M') |
|
|
except: |
|
|
pass |
|
|
|
|
|
activity_html += f""" |
|
|
<li><strong>{timestamp}</strong> - {interaction.get('interaction_type', 'Unknown')} |
|
|
(Patient: {interaction.get('patient_name', 'Unknown')})</li> |
|
|
""" |
|
|
activity_html += "</ul>" |
|
|
else: |
|
|
activity_html += "<p>No recent activity found.</p>" |
|
|
|
|
|
activity_html += "</div>" |
|
|
|
|
|
return analytics_html, activity_html |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Error getting analytics summary: {e}") |
|
|
return ( |
|
|
f"<div class='error-box'>β Error loading analytics: {str(e)}</div>", |
|
|
"<div class='error-box'>β Unable to load recent activity</div>" |
|
|
) |
|
|
|
|
|
|