SmartHeal commited on
Commit
a2b08bf
·
verified ·
1 Parent(s): 7e7d8ff

Update src/ui_components_original.py

Browse files
Files changed (1) hide show
  1. src/ui_components_original.py +431 -252
src/ui_components_original.py CHANGED
@@ -11,6 +11,28 @@ import html # Import the html module for escaping
11
 
12
  from .patient_history import PatientHistoryManager, ReportGenerator
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  class UIComponents:
15
  def __init__(self, auth_manager, database_manager, wound_analyzer):
16
  self.auth_manager = auth_manager
@@ -117,7 +139,6 @@ class UIComponents:
117
  logging.error(f"Error getting organizations: {e}")
118
  return ["Default Hospital - Location"]
119
 
120
-
121
  def get_custom_css(self):
122
  return """
123
  /* =================== ORIGINAL SMARTHEAL CSS =================== */
@@ -130,6 +151,7 @@ body, html {
130
  color: #1A202C !important;
131
  line-height: 1.6 !important;
132
  }
 
133
  /* Professional Header with Logo */
134
  .medical-header {
135
  background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%) !important;
@@ -145,6 +167,7 @@ body, html {
145
  position: relative !important;
146
  overflow: hidden !important;
147
  }
 
148
  .medical-header::before {
149
  content: '' !important;
150
  position: absolute !important;
@@ -156,10 +179,12 @@ body, html {
156
  opacity: 0.1 !important;
157
  z-index: 1 !important;
158
  }
 
159
  .medical-header > * {
160
  position: relative !important;
161
  z-index: 2 !important;
162
  }
 
163
  .logo {
164
  width: 80px !important;
165
  height: 80px !important;
@@ -170,6 +195,7 @@ body, html {
170
  background: white !important;
171
  padding: 4px !important;
172
  }
 
173
  .medical-header h1 {
174
  font-size: 3.5rem !important;
175
  font-weight: 800 !important;
@@ -181,6 +207,7 @@ body, html {
181
  background-clip: text !important;
182
  filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3)) !important;
183
  }
 
184
  .medical-header p {
185
  font-size: 1.3rem !important;
186
  margin: 8px 0 0 0 !important;
@@ -188,6 +215,7 @@ body, html {
188
  font-weight: 500 !important;
189
  text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2) !important;
190
  }
 
191
  /* Enhanced Form Styling */
192
  .gr-form {
193
  background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%) !important;
@@ -200,6 +228,7 @@ body, html {
200
  position: relative !important;
201
  overflow: hidden !important;
202
  }
 
203
  .gr-form::before {
204
  content: '' !important;
205
  position: absolute !important;
@@ -210,6 +239,7 @@ body, html {
210
  background: linear-gradient(90deg, #e53e3e 0%, #f56565 50%, #e53e3e 100%) !important;
211
  z-index: 1 !important;
212
  }
 
213
  /* Professional Input Fields */
214
  .gr-textbox, .gr-number {
215
  border-radius: 12px !important;
@@ -221,6 +251,7 @@ body, html {
221
  color: #1A202C !important;
222
  padding: 16px 20px !important;
223
  }
 
224
  .gr-textbox:focus,
225
  .gr-number:focus,
226
  .gr-textbox input:focus,
@@ -231,6 +262,7 @@ body, html {
231
  outline: none !important;
232
  transform: translateY(-1px) !important;
233
  }
 
234
  .gr-textbox input,
235
  .gr-number input {
236
  background: transparent !important;
@@ -241,6 +273,7 @@ body, html {
241
  width: 100% !important;
242
  padding: 0 !important;
243
  }
 
244
  .gr-textbox label,
245
  .gr-number label,
246
  .gr-dropdown label,
@@ -252,6 +285,7 @@ body, html {
252
  margin-bottom: 8px !important;
253
  display: block !important;
254
  }
 
255
  /* Enhanced Button Styling */
256
  button.gr-button,
257
  button.gr-button-primary {
@@ -271,6 +305,7 @@ button.gr-button-primary {
271
  text-transform: uppercase !important;
272
  cursor: pointer !important;
273
  }
 
274
  button.gr-button::before {
275
  content: '' !important;
276
  position: absolute !important;
@@ -281,20 +316,24 @@ button.gr-button::before {
281
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent) !important;
282
  transition: left 0.5s !important;
283
  }
 
284
  button.gr-button:hover::before {
285
  left: 100% !important;
286
  }
 
287
  button.gr-button:hover,
288
  button.gr-button-primary:hover {
289
  background: linear-gradient(135deg, #C53030 0%, #9C2A2A 100%) !important;
290
  box-shadow: 0 8px 32px rgba(229, 62, 62, 0.4) !important;
291
  transform: translateY(-3px) !important;
292
  }
 
293
  button.gr-button:active,
294
  button.gr-button-primary:active {
295
  transform: translateY(-1px) !important;
296
  box-shadow: 0 4px 16px rgba(229, 62, 62, 0.5) !important;
297
  }
 
298
  button.gr-button:disabled {
299
  background: #A0AEC0 !important;
300
  color: #718096 !important;
@@ -302,6 +341,7 @@ button.gr-button:disabled {
302
  box-shadow: none !important;
303
  transform: none !important;
304
  }
 
305
  /* Professional Status Messages */
306
  .status-success {
307
  background: linear-gradient(135deg, #F0FFF4 0%, #E6FFFA 100%) !important;
@@ -314,6 +354,7 @@ button.gr-button:disabled {
314
  box-shadow: 0 8px 24px rgba(56, 161, 105, 0.2) !important;
315
  backdrop-filter: blur(10px) !important;
316
  }
 
317
  .status-error {
318
  background: linear-gradient(135deg, #FFF5F5 0%, #FED7D7 100%) !important;
319
  border: 2px solid #E53E3E !important;
@@ -325,6 +366,7 @@ button.gr-button:disabled {
325
  box-shadow: 0 8px 24px rgba(229, 62, 62, 0.2) !important;
326
  backdrop-filter: blur(10px) !important;
327
  }
 
328
  .status-warning {
329
  background: linear-gradient(135deg, #FFFAF0 0%, #FEEBC8 100%) !important;
330
  border: 2px solid #DD6B20 !important;
@@ -336,6 +378,7 @@ button.gr-button:disabled {
336
  box-shadow: 0 8px 24px rgba(221, 107, 32, 0.2) !important;
337
  backdrop-filter: blur(10px) !important;
338
  }
 
339
  /* Professional Card Layout */
340
  .medical-card {
341
  background: linear-gradient(145deg, #FFFFFF 0%, #F7FAFC 100%) !important;
@@ -348,6 +391,7 @@ button.gr-button:disabled {
348
  position: relative !important;
349
  overflow: hidden !important;
350
  }
 
351
  .medical-card::before {
352
  content: '' !important;
353
  position: absolute !important;
@@ -357,6 +401,7 @@ button.gr-button:disabled {
357
  height: 4px !important;
358
  background: linear-gradient(90deg, #E53E3E 0%, #F56565 50%, #E53E3E 100%) !important;
359
  }
 
360
  .medical-card-title {
361
  font-size: 1.75rem !important;
362
  font-weight: 700 !important;
@@ -367,6 +412,7 @@ button.gr-button:disabled {
367
  text-align: center !important;
368
  position: relative !important;
369
  }
 
370
  .medical-card-title::after {
371
  content: '' !important;
372
  position: absolute !important;
@@ -378,6 +424,7 @@ button.gr-button:disabled {
378
  background: linear-gradient(90deg, transparent, #E53E3E, transparent) !important;
379
  border-radius: 2px !important;
380
  }
 
381
  /* Professional Dropdown Styling */
382
  .gr-dropdown {
383
  border-radius: 12px !important;
@@ -386,12 +433,14 @@ button.gr-button:disabled {
386
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
387
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important;
388
  }
 
389
  .gr-dropdown:focus,
390
  .gr-dropdown select:focus {
391
  border-color: #E53E3E !important;
392
  box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important;
393
  outline: none !important;
394
  }
 
395
  .gr-dropdown select {
396
  background: transparent !important;
397
  border: none !important;
@@ -400,11 +449,13 @@ button.gr-button:disabled {
400
  padding: 16px 20px !important;
401
  border-radius: 12px !important;
402
  }
 
403
  /* Radio button styling */
404
  .gr-radio input[type="radio"] {
405
  margin-right: 8px !important;
406
  transform: scale(1.2) !important;
407
  }
 
408
  .gr-radio label {
409
  display: flex !important;
410
  align-items: center !important;
@@ -414,6 +465,7 @@ button.gr-button:disabled {
414
  cursor: pointer !important;
415
  color: #1A202C !important;
416
  }
 
417
  /* Tab styling */
418
  .gr-tab {
419
  color: #1A202C !important;
@@ -422,12 +474,14 @@ button.gr-button:disabled {
422
  padding: 12px 20px !important;
423
  background-color: #F7FAFC !important;
424
  }
 
425
  .gr-tab.selected {
426
  color: #E53E3E !important;
427
  font-weight: 600 !important;
428
  border-bottom: 2px solid #E53E3E !important;
429
  background-color: #FFFFFF !important;
430
  }
 
431
  /* Image upload styling */
432
  .gr-image {
433
  border: 3px dashed #CBD5E0 !important;
@@ -435,10 +489,12 @@ button.gr-button:disabled {
435
  background-color: #F7FAFC !important;
436
  transition: all 0.2s ease !important;
437
  }
 
438
  .gr-image:hover {
439
  border-color: #E53E3E !important;
440
  background-color: #FFF5F5 !important;
441
  }
 
442
  /* Analyze button special styling */
443
  #analyze-btn {
444
  background: linear-gradient(135deg, #1B5CF3 0%, #1E3A8A 100%) !important;
@@ -452,11 +508,13 @@ button.gr-button:disabled {
452
  text-align: center !important;
453
  transition: all 0.2s ease-in-out !important;
454
  }
 
455
  #analyze-btn:hover {
456
  background: linear-gradient(135deg, #174ea6 0%, #123b82 100%) !important;
457
  box-shadow: 0 4px 14px rgba(27, 95, 193, 0.4) !important;
458
  transform: translateY(-2px) !important;
459
  }
 
460
  #analyze-btn:disabled {
461
  background: #A0AEC0 !important;
462
  color: #1A202C !important;
@@ -464,6 +522,42 @@ button.gr-button:disabled {
464
  box-shadow: none !important;
465
  transform: none !important;
466
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  /* Responsive design */
468
  @media (max-width: 768px) {
469
  .medical-header {
@@ -491,6 +585,10 @@ button.gr-button:disabled {
491
  padding: 14px 20px !important;
492
  font-size: 14px !important;
493
  }
 
 
 
 
494
  }
495
  """
496
 
@@ -662,7 +760,7 @@ button.gr-button:disabled {
662
  value="Moist"
663
  )
664
  infection_signs = gr.Dropdown(
665
- choices=["None", "Mild", "Moderate", "Severe"],
666
  label="Signs of Infection",
667
  value="None"
668
  )
@@ -707,7 +805,7 @@ button.gr-button:disabled {
707
  lines=3
708
  )
709
 
710
- analyze_btn = gr.Button("🔬 Analyze Wound", variant="primary", size="lg")
711
  analysis_output = gr.HTML("")
712
 
713
  # PATIENT HISTORY TAB
@@ -726,8 +824,6 @@ button.gr-button:disabled {
726
  )
727
  search_patient_btn = gr.Button("🔍 Search Patient History", variant="secondary")
728
  specific_patient_output = gr.HTML("")
729
-
730
- # Interface already complete above - no additional tabs needed
731
 
732
  # Event handlers
733
  def handle_login(username, password):
@@ -805,99 +901,67 @@ button.gr-button:disabled {
805
  </div>
806
  """
807
 
808
- # 1. Construct questionnaire dictionary
809
  questionnaire_data = {
810
  'user_id': self.current_user.get('id'),
 
 
 
 
 
 
 
 
 
811
  'patient_name': patient_name,
812
- 'patient_age': patient_age,
813
  'patient_gender': patient_gender,
814
  'wound_location': wound_location,
815
  'wound_duration': wound_duration,
816
  'pain_level': pain_level,
817
- 'moisture_level': moisture_level,
818
- 'infection_signs': infection_signs,
819
- 'diabetic_status': diabetic_status,
820
  'previous_treatment': previous_treatment,
821
  'medical_history': medical_history,
822
  'medications': medications,
823
- 'allergies': allergies,
824
  'additional_notes': additional_notes
825
  }
826
 
827
- # 2. Save questionnaire in DB
828
  questionnaire_id = self.database_manager.save_questionnaire(questionnaire_data)
829
 
830
- # 3. Run AI analysis with uploaded image
831
  try:
832
- # Log information about the wound image
833
- if hasattr(wound_image, 'name'):
834
- logging.info(f"Processing image: {wound_image.name}")
835
 
836
- # First try direct analysis with the file-like object
837
  analysis_result = self.wound_analyzer.analyze_wound(wound_image, questionnaire_data)
838
- except Exception as e:
839
- logging.error(f"AI analysis error (first attempt): {e}")
840
- try:
841
- # If that fails, try with PIL image
842
- from PIL import Image
843
- import io
844
-
845
- # Reset file pointer if possible
846
- if hasattr(wound_image, 'seek'):
847
- wound_image.seek(0)
848
-
849
- # Convert to PIL Image
850
- pil_image = Image.open(wound_image)
851
- analysis_result = self.wound_analyzer.analyze_wound(pil_image, questionnaire_data)
852
- except Exception as e2:
853
- logging.error(f"AI analysis error (second attempt): {e2}")
854
- # Return error information for display
855
- return f"""
856
- <div class='status-error' style='padding: 20px; background: #ffeeee; border-left: 5px solid #ff5555; margin: 20px 0;'>
857
- <h3>❌ Analysis Error</h3>
858
- <p>There was an error analyzing the wound image:</p>
859
- <pre style='background: #f5f5f5; padding: 10px; border-radius: 5px;'>{str(e)}\n{str(e2) if 'e2' in locals() else ''}</pre>
860
- <p>Please try again with a different image or contact support.</p>
861
- </div>
862
- """
863
-
864
- # 4. Save AI analysis result
865
- self.database_manager.save_analysis_result(questionnaire_id, analysis_result)
866
-
867
- # 5. Save wound image metadata
868
- if isinstance(wound_image, str):
869
- image_url = wound_image
870
- elif hasattr(wound_image, 'name'):
871
- image_url = wound_image.name
872
- else:
873
- image_url = 'unknown'
874
-
875
- image_data = {
876
- 'image_url': image_url,
877
- 'filename': os.path.basename(image_url),
878
- 'file_size': None,
879
- 'width': None,
880
- 'height': None
881
- }
882
-
883
- # 6. Format analysis results with visualization
884
- formatted_analysis = self._format_analysis_results(analysis_result, image_url)
885
-
886
- # 7. Generate HTML professional report for complete analysis
887
- professional_report = self.report_generator.generate_analysis_report(
888
- questionnaire_data,
889
- analysis_result,
890
- image_data.get('image_url')
891
- )
892
 
893
- return formatted_analysis + professional_report
 
 
 
 
 
 
 
 
 
894
 
895
  except Exception as e:
896
- logging.error(f"Analysis error: {e}")
897
  return f"<div class='status-error'>❌ Analysis failed: {str(e)}</div>"
898
 
899
-
900
-
901
  def handle_logout():
902
  self.current_user = {}
903
  return {
@@ -993,223 +1057,338 @@ button.gr-button:disabled {
993
  inputs=[search_patient_name],
994
  outputs=[specific_patient_output]
995
  )
 
996
  return app
997
- def _format_analysis_results(self, analysis_result, image_url=None):
998
- """Format analysis results for HTML display with base64 encoded images, always showing segmentation overlay."""
 
999
  try:
1000
- # Extract key results
1001
- summary = analysis_result.get('summary', 'Analysis completed')
1002
- wound_detection = analysis_result.get('wound_detection', {})
1003
- segmentation_result = analysis_result.get('segmentation_result', {})
1004
- risk_assessment = analysis_result.get('risk_assessment', {})
1005
- recommendations = analysis_result.get('recommendations', '')
1006
- comprehensive_report = analysis_result.get('comprehensive_report', '')
1007
-
1008
- # Detection metrics
1009
- detection_confidence = 0.0
1010
- wound_type = "Unknown"
1011
- length_cm = breadth_cm = area_cm2 = 0
1012
-
1013
- if wound_detection.get('status') == 'success' and wound_detection.get('detections'):
1014
- detections = wound_detection.get('detections', [])
1015
- if detections:
1016
- detection_confidence = detections[0].get('detection_confidence', 0.0)
1017
- wound_type = detections[0].get('wound_type', 'Unknown')
1018
- length_cm = detections[0].get('length_cm', 0)
1019
- breadth_cm = detections[0].get('breadth_cm', 0)
1020
- area_cm2 = detections[0].get('surface_area_cm2', 0)
1021
-
1022
- risk_level = risk_assessment.get('risk_level', 'Unknown')
1023
- risk_score = risk_assessment.get('risk_score', 0)
1024
- risk_factors = risk_assessment.get('risk_factors', [])
1025
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1026
  # Set risk class for styling
1027
  risk_class = "low"
1028
  if risk_level.lower() == "moderate":
1029
  risk_class = "moderate"
1030
  elif risk_level.lower() == "high":
1031
  risk_class = "high"
1032
-
1033
  # Format risk factors
1034
  risk_factors_html = "<ul>" + "".join(f"<li>{factor}</li>" for factor in risk_factors) + "</ul>" if risk_factors else "<p>No specific risk factors identified.</p>"
1035
-
1036
- # Format guideline recommendations
1037
- guideline_recommendations = analysis_result.get('guideline_recommendations', [])
1038
- recommendations_html = "<ul>" + "".join(f"<li>{rec}</li>" for rec in guideline_recommendations if rec and len(rec) > 10) + "</ul>" if guideline_recommendations else "<p>No specific recommendations available.</p>"
1039
-
1040
- # --- Detection image ---
1041
- detection_image_base64 = None
1042
- if "overlay_path" in wound_detection and os.path.exists(wound_detection["overlay_path"]):
1043
- detection_image_base64 = self.image_to_base64(wound_detection["overlay_path"])
1044
- elif comprehensive_report:
1045
- detection_match = re.search(r"Detection Image: (.+?)(?:\n|$)", comprehensive_report)
1046
- if detection_match and detection_match.group(1) != "Not available" and os.path.exists(detection_match.group(1).strip()):
1047
- detection_image_base64 = self.image_to_base64(detection_match.group(1).strip())
1048
-
1049
- detection_image_html = ""
1050
- if detection_image_base64:
1051
- detection_image_html = f"""
1052
- <div class="image-visualization">
1053
- <h3>Wound Detection Visualization</h3>
1054
- <img src="{detection_image_base64}" alt="Wound Detection" style="max-width: 100%; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin: 10px 0;">
1055
- </div>
1056
- """
1057
-
1058
- detection_html = f"""
1059
- <div class="section">
1060
- <h2>🔍 Wound Detection & Classification</h2>
1061
- {detection_image_html}
1062
- <div class="info-cards" style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin: 20px 0;">
1063
- <div class="info-card">
1064
- <h3>Wound Type</h3>
1065
- <p style="font-weight: 600; color: #3182ce;">{wound_type}</p>
1066
  </div>
1067
- <div class="info-card">
1068
- <h3>Detection Confidence</h3>
1069
- <p>{detection_confidence:.1%}</p>
 
 
 
 
 
1070
  </div>
1071
- <div class="info-card">
1072
- <h3>Total Wounds Detected</h3>
1073
- <p>{wound_detection.get('total_wounds', 0)}</p>
 
 
 
 
 
1074
  </div>
1075
- </div>
1076
- </div>
1077
- """ if wound_detection.get('status') == 'success' else f"""
1078
- <div class="status-error">
1079
- <strong>Detection Status:</strong> Failed<br>
1080
- <strong>Reason:</strong> {wound_detection.get('message', 'Unknown error')}
1081
- </div>
1082
- """
1083
-
1084
- # --- Segmentation overlay: prefer direct result! ---
1085
- segmentation_image_base64 = None
1086
- if "overlay_pil" in segmentation_result and isinstance(segmentation_result["overlay_pil"], Image.Image):
1087
- segmentation_image_base64 = pil_to_base64(segmentation_result["overlay_pil"])
1088
- elif "overlay_path" in segmentation_result and os.path.exists(segmentation_result["overlay_path"]):
1089
- segmentation_image_base64 = self.image_to_base64(segmentation_result["overlay_path"])
1090
- elif comprehensive_report:
1091
- segmentation_match = re.search(r"Segmentation Image: (.+?)(?:\n|$)", comprehensive_report)
1092
- if segmentation_match and segmentation_match.group(1) != "Not available" and os.path.exists(segmentation_match.group(1).strip()):
1093
- segmentation_image_base64 = self.image_to_base64(segmentation_match.group(1).strip())
1094
-
1095
- segmentation_image_html = ""
1096
- if segmentation_image_base64:
1097
- segmentation_image_html = f"""
1098
- <div class="image-visualization">
1099
- <h3>Wound Segmentation Visualization</h3>
1100
- <img src="{segmentation_image_base64}" alt="Wound Segmentation" style="max-width: 100%; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin: 10px 0;">
1101
- </div>
1102
- """
1103
-
1104
- segmentation_html = f"""
1105
- <div class="section">
1106
- <h2>📏 Wound Measurements</h2>
1107
- {segmentation_image_html}
1108
- <div class="info-cards" style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin: 20px 0;">
1109
- <div class="info-card">
1110
- <h3>Length</h3>
1111
- <p style="font-weight: 600; color: #3182ce;">{length_cm:.2f} cm</p>
1112
  </div>
1113
- <div class="info-card">
1114
- <h3>Width</h3>
1115
- <p style="font-weight: 600; color: #3182ce;">{breadth_cm:.2f} cm</p>
 
 
1116
  </div>
1117
- <div class="info-card">
1118
- <h3>Surface Area</h3>
1119
- <p style="font-weight: 600; color: #3182ce;">{area_cm2:.2f} cm²</p>
 
 
1120
  </div>
1121
- </div>
1122
- </div>
1123
- """ if segmentation_result.get('status') == 'success' else f"""
1124
- <div class="status-warning">
1125
- <strong>Segmentation Status:</strong> Failed<br>
1126
- <strong>Reason:</strong> {segmentation_result.get('message', 'Unknown error')}
1127
- </div>
1128
- """
1129
-
1130
- # --- Main input image preview ---
1131
- image_visualization = ""
1132
- if image_url and os.path.exists(image_url):
1133
- image_base64 = self.image_to_base64(image_url)
1134
- if image_base64:
1135
- image_visualization = f"""
1136
- <div class="section">
1137
- <h2>🖼️ Wound Image</h2>
1138
- <div style="text-align: center; margin: 20px 0;">
1139
- <img src="{image_base64}" alt="Wound Image" style="max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); margin-bottom: 10px;">
1140
- <p style="margin-top: 10px; color: #666; font-size: 0.9em;">
1141
- Analysis completed successfully
1142
- </p>
1143
  </div>
1144
  </div>
1145
- """
1146
-
1147
- # --- Comprehensive report as HTML ---
1148
- comprehensive_report_html = ""
1149
- if comprehensive_report:
1150
- report_without_images = re.sub(r'## Analysis Images.*?(?=##|$)', '', comprehensive_report, flags=re.DOTALL)
1151
- comprehensive_report_html = self.markdown_to_html(report_without_images)
1152
-
1153
- # --- Final Output ---
1154
- html_output = f"""
1155
- <div style="max-width: 900px; margin: 0 auto; background: white; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); overflow: hidden;">
1156
- <div style="background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%); color: white; padding: 30px; text-align: center;">
1157
- <h2 style="margin: 0; font-size: 28px; font-weight: 600;">🔬 SmartHeal AI Analysis Results</h2>
1158
- <p style="margin: 10px 0 0 0; opacity: 0.9; font-size: 16px;">Advanced Computer Vision & Medical AI Assessment</p>
1159
- </div>
1160
- <div style="padding: 30px;">
1161
- <div class="status-success" style="margin-bottom: 30px;">
1162
- <strong>Analysis Summary:</strong> {summary}
1163
  </div>
1164
- {image_visualization}
1165
- {detection_html}
1166
- {segmentation_html}
1167
- <div class="section">
1168
- <h2>⚠️ Risk Assessment</h2>
1169
- <div style="display: flex; align-items: center; margin: 20px 0;">
1170
  <div style="background: {'#d4edda' if risk_class == 'low' else '#fff3cd' if risk_class == 'moderate' else '#f8d7da'};
1171
  color: {'#155724' if risk_class == 'low' else '#856404' if risk_class == 'moderate' else '#721c24'};
1172
- padding: 12px 24px;
1173
  border-radius: 30px;
1174
  font-weight: 700;
1175
- font-size: 18px;
1176
  text-transform: uppercase;
1177
  letter-spacing: 1px;
1178
- margin-right: 20px;">
 
1179
  {risk_level} RISK
1180
  </div>
1181
- <div>
1182
  <strong>Risk Score:</strong> {risk_score}/10
1183
  </div>
1184
  </div>
1185
- <div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 15px 0;">
1186
- <h3 style="margin-top: 0;">Identified Risk Factors:</h3>
1187
  {risk_factors_html}
1188
  </div>
1189
  </div>
1190
- <div class="section">
1191
- <h2>💡 Clinical Recommendations</h2>
1192
- <div style="background: #e7f5ff; padding: 20px; border-radius: 8px; border-left: 4px solid #3182ce;">
1193
- {recommendations_html}
1194
- </div>
1195
- <div style="background: #fff4e6; padding: 15px; border-radius: 8px; margin-top: 20px;">
1196
- <p style="margin: 0; color: #e67700;">
1197
- <strong>⚠️ Note:</strong> These recommendations are generated by AI and should be verified by healthcare professionals.
1198
- </p>
 
 
1199
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1200
  </div>
1201
- {f'<div class="section"><h2>📋 Comprehensive Report</h2><div style="background: #f8f9fa; padding: 20px; border-radius: 8px;">{comprehensive_report_html}</div></div>' if comprehensive_report_html else ''}
1202
- <hr style="border: 0; height: 1px; background: #e9ecef; margin: 30px 0;">
1203
- <div style="text-align: center; padding: 20px 0;">
1204
- <p style="color: #6c757d; font-style: italic;">
1205
- Analysis completed by SmartHeal AI - Advanced Wound Care Assistant
 
1206
  </p>
1207
  </div>
1208
  </div>
1209
  </div>
1210
  """
 
1211
  return html_output
1212
 
1213
  except Exception as e:
1214
- logging.error(f"Error formatting results: {e}")
1215
- return f"<div class='status-error'>❌ Error displaying results: {str(e)}</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  from .patient_history import PatientHistoryManager, ReportGenerator
13
 
14
+ def pil_to_base64(pil_image):
15
+ """Convert PIL Image to base64 data URL"""
16
+ import io
17
+ import base64
18
+ from PIL import Image
19
+
20
+ if pil_image is None:
21
+ return None
22
+
23
+ try:
24
+ # Convert image to RGB if it's not already
25
+ if pil_image.mode != 'RGB':
26
+ pil_image = pil_image.convert('RGB')
27
+
28
+ buffer = io.BytesIO()
29
+ pil_image.save(buffer, format='PNG')
30
+ img_str = base64.b64encode(buffer.getvalue()).decode()
31
+ return f"data:image/png;base64,{img_str}"
32
+ except Exception as e:
33
+ logging.error(f"Error converting PIL image to base64: {e}")
34
+ return None
35
+
36
  class UIComponents:
37
  def __init__(self, auth_manager, database_manager, wound_analyzer):
38
  self.auth_manager = auth_manager
 
139
  logging.error(f"Error getting organizations: {e}")
140
  return ["Default Hospital - Location"]
141
 
 
142
  def get_custom_css(self):
143
  return """
144
  /* =================== ORIGINAL SMARTHEAL CSS =================== */
 
151
  color: #1A202C !important;
152
  line-height: 1.6 !important;
153
  }
154
+
155
  /* Professional Header with Logo */
156
  .medical-header {
157
  background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%) !important;
 
167
  position: relative !important;
168
  overflow: hidden !important;
169
  }
170
+
171
  .medical-header::before {
172
  content: '' !important;
173
  position: absolute !important;
 
179
  opacity: 0.1 !important;
180
  z-index: 1 !important;
181
  }
182
+
183
  .medical-header > * {
184
  position: relative !important;
185
  z-index: 2 !important;
186
  }
187
+
188
  .logo {
189
  width: 80px !important;
190
  height: 80px !important;
 
195
  background: white !important;
196
  padding: 4px !important;
197
  }
198
+
199
  .medical-header h1 {
200
  font-size: 3.5rem !important;
201
  font-weight: 800 !important;
 
207
  background-clip: text !important;
208
  filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3)) !important;
209
  }
210
+
211
  .medical-header p {
212
  font-size: 1.3rem !important;
213
  margin: 8px 0 0 0 !important;
 
215
  font-weight: 500 !important;
216
  text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2) !important;
217
  }
218
+
219
  /* Enhanced Form Styling */
220
  .gr-form {
221
  background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%) !important;
 
228
  position: relative !important;
229
  overflow: hidden !important;
230
  }
231
+
232
  .gr-form::before {
233
  content: '' !important;
234
  position: absolute !important;
 
239
  background: linear-gradient(90deg, #e53e3e 0%, #f56565 50%, #e53e3e 100%) !important;
240
  z-index: 1 !important;
241
  }
242
+
243
  /* Professional Input Fields */
244
  .gr-textbox, .gr-number {
245
  border-radius: 12px !important;
 
251
  color: #1A202C !important;
252
  padding: 16px 20px !important;
253
  }
254
+
255
  .gr-textbox:focus,
256
  .gr-number:focus,
257
  .gr-textbox input:focus,
 
262
  outline: none !important;
263
  transform: translateY(-1px) !important;
264
  }
265
+
266
  .gr-textbox input,
267
  .gr-number input {
268
  background: transparent !important;
 
273
  width: 100% !important;
274
  padding: 0 !important;
275
  }
276
+
277
  .gr-textbox label,
278
  .gr-number label,
279
  .gr-dropdown label,
 
285
  margin-bottom: 8px !important;
286
  display: block !important;
287
  }
288
+
289
  /* Enhanced Button Styling */
290
  button.gr-button,
291
  button.gr-button-primary {
 
305
  text-transform: uppercase !important;
306
  cursor: pointer !important;
307
  }
308
+
309
  button.gr-button::before {
310
  content: '' !important;
311
  position: absolute !important;
 
316
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent) !important;
317
  transition: left 0.5s !important;
318
  }
319
+
320
  button.gr-button:hover::before {
321
  left: 100% !important;
322
  }
323
+
324
  button.gr-button:hover,
325
  button.gr-button-primary:hover {
326
  background: linear-gradient(135deg, #C53030 0%, #9C2A2A 100%) !important;
327
  box-shadow: 0 8px 32px rgba(229, 62, 62, 0.4) !important;
328
  transform: translateY(-3px) !important;
329
  }
330
+
331
  button.gr-button:active,
332
  button.gr-button-primary:active {
333
  transform: translateY(-1px) !important;
334
  box-shadow: 0 4px 16px rgba(229, 62, 62, 0.5) !important;
335
  }
336
+
337
  button.gr-button:disabled {
338
  background: #A0AEC0 !important;
339
  color: #718096 !important;
 
341
  box-shadow: none !important;
342
  transform: none !important;
343
  }
344
+
345
  /* Professional Status Messages */
346
  .status-success {
347
  background: linear-gradient(135deg, #F0FFF4 0%, #E6FFFA 100%) !important;
 
354
  box-shadow: 0 8px 24px rgba(56, 161, 105, 0.2) !important;
355
  backdrop-filter: blur(10px) !important;
356
  }
357
+
358
  .status-error {
359
  background: linear-gradient(135deg, #FFF5F5 0%, #FED7D7 100%) !important;
360
  border: 2px solid #E53E3E !important;
 
366
  box-shadow: 0 8px 24px rgba(229, 62, 62, 0.2) !important;
367
  backdrop-filter: blur(10px) !important;
368
  }
369
+
370
  .status-warning {
371
  background: linear-gradient(135deg, #FFFAF0 0%, #FEEBC8 100%) !important;
372
  border: 2px solid #DD6B20 !important;
 
378
  box-shadow: 0 8px 24px rgba(221, 107, 32, 0.2) !important;
379
  backdrop-filter: blur(10px) !important;
380
  }
381
+
382
  /* Professional Card Layout */
383
  .medical-card {
384
  background: linear-gradient(145deg, #FFFFFF 0%, #F7FAFC 100%) !important;
 
391
  position: relative !important;
392
  overflow: hidden !important;
393
  }
394
+
395
  .medical-card::before {
396
  content: '' !important;
397
  position: absolute !important;
 
401
  height: 4px !important;
402
  background: linear-gradient(90deg, #E53E3E 0%, #F56565 50%, #E53E3E 100%) !important;
403
  }
404
+
405
  .medical-card-title {
406
  font-size: 1.75rem !important;
407
  font-weight: 700 !important;
 
412
  text-align: center !important;
413
  position: relative !important;
414
  }
415
+
416
  .medical-card-title::after {
417
  content: '' !important;
418
  position: absolute !important;
 
424
  background: linear-gradient(90deg, transparent, #E53E3E, transparent) !important;
425
  border-radius: 2px !important;
426
  }
427
+
428
  /* Professional Dropdown Styling */
429
  .gr-dropdown {
430
  border-radius: 12px !important;
 
433
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
434
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important;
435
  }
436
+
437
  .gr-dropdown:focus,
438
  .gr-dropdown select:focus {
439
  border-color: #E53E3E !important;
440
  box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important;
441
  outline: none !important;
442
  }
443
+
444
  .gr-dropdown select {
445
  background: transparent !important;
446
  border: none !important;
 
449
  padding: 16px 20px !important;
450
  border-radius: 12px !important;
451
  }
452
+
453
  /* Radio button styling */
454
  .gr-radio input[type="radio"] {
455
  margin-right: 8px !important;
456
  transform: scale(1.2) !important;
457
  }
458
+
459
  .gr-radio label {
460
  display: flex !important;
461
  align-items: center !important;
 
465
  cursor: pointer !important;
466
  color: #1A202C !important;
467
  }
468
+
469
  /* Tab styling */
470
  .gr-tab {
471
  color: #1A202C !important;
 
474
  padding: 12px 20px !important;
475
  background-color: #F7FAFC !important;
476
  }
477
+
478
  .gr-tab.selected {
479
  color: #E53E3E !important;
480
  font-weight: 600 !important;
481
  border-bottom: 2px solid #E53E3E !important;
482
  background-color: #FFFFFF !important;
483
  }
484
+
485
  /* Image upload styling */
486
  .gr-image {
487
  border: 3px dashed #CBD5E0 !important;
 
489
  background-color: #F7FAFC !important;
490
  transition: all 0.2s ease !important;
491
  }
492
+
493
  .gr-image:hover {
494
  border-color: #E53E3E !important;
495
  background-color: #FFF5F5 !important;
496
  }
497
+
498
  /* Analyze button special styling */
499
  #analyze-btn {
500
  background: linear-gradient(135deg, #1B5CF3 0%, #1E3A8A 100%) !important;
 
508
  text-align: center !important;
509
  transition: all 0.2s ease-in-out !important;
510
  }
511
+
512
  #analyze-btn:hover {
513
  background: linear-gradient(135deg, #174ea6 0%, #123b82 100%) !important;
514
  box-shadow: 0 4px 14px rgba(27, 95, 193, 0.4) !important;
515
  transform: translateY(-2px) !important;
516
  }
517
+
518
  #analyze-btn:disabled {
519
  background: #A0AEC0 !important;
520
  color: #1A202C !important;
 
522
  box-shadow: none !important;
523
  transform: none !important;
524
  }
525
+
526
+ /* Image gallery styling for better visualization */
527
+ .image-gallery {
528
+ display: grid;
529
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
530
+ gap: 20px;
531
+ margin: 20px 0;
532
+ }
533
+
534
+ .image-item {
535
+ background: #f8f9fa;
536
+ border-radius: 12px;
537
+ padding: 15px;
538
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
539
+ text-align: center;
540
+ }
541
+
542
+ .image-item img {
543
+ max-width: 100%;
544
+ height: auto;
545
+ border-radius: 8px;
546
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
547
+ }
548
+
549
+ .image-item h4 {
550
+ margin: 15px 0 5px 0;
551
+ color: #2d3748;
552
+ font-weight: 600;
553
+ }
554
+
555
+ .image-item p {
556
+ margin: 0;
557
+ color: #666;
558
+ font-size: 0.9em;
559
+ }
560
+
561
  /* Responsive design */
562
  @media (max-width: 768px) {
563
  .medical-header {
 
585
  padding: 14px 20px !important;
586
  font-size: 14px !important;
587
  }
588
+
589
+ .image-gallery {
590
+ grid-template-columns: 1fr;
591
+ }
592
  }
593
  """
594
 
 
760
  value="Moist"
761
  )
762
  infection_signs = gr.Dropdown(
763
+ choices=["None", "Mild", "Moderate", "Severe"],
764
  label="Signs of Infection",
765
  value="None"
766
  )
 
805
  lines=3
806
  )
807
 
808
+ analyze_btn = gr.Button("🔬 Analyze Wound", variant="primary", size="lg", elem_id="analyze-btn")
809
  analysis_output = gr.HTML("")
810
 
811
  # PATIENT HISTORY TAB
 
824
  )
825
  search_patient_btn = gr.Button("🔍 Search Patient History", variant="secondary")
826
  specific_patient_output = gr.HTML("")
 
 
827
 
828
  # Event handlers
829
  def handle_login(username, password):
 
901
  </div>
902
  """
903
 
904
+ # 1. Construct questionnaire dictionary for AIProcessor
905
  questionnaire_data = {
906
  'user_id': self.current_user.get('id'),
907
+ 'age': patient_age,
908
+ 'diabetic': 'Yes' if diabetic_status != 'Non-diabetic' else 'No',
909
+ 'allergies': allergies,
910
+ 'date_of_injury': 'Unknown', # Not collected in this form
911
+ 'professional_care': 'Yes', # Assumed since using professional interface
912
+ 'oozing_bleeding': 'None', # Could be mapped from infection_signs
913
+ 'infection': 'Yes' if infection_signs != 'None' else 'No',
914
+ 'moisture': moisture_level,
915
+ # Additional fields for comprehensive analysis
916
  'patient_name': patient_name,
 
917
  'patient_gender': patient_gender,
918
  'wound_location': wound_location,
919
  'wound_duration': wound_duration,
920
  'pain_level': pain_level,
 
 
 
921
  'previous_treatment': previous_treatment,
922
  'medical_history': medical_history,
923
  'medications': medications,
 
924
  'additional_notes': additional_notes
925
  }
926
 
927
+ # 2. Save questionnaire in DB first
928
  questionnaire_id = self.database_manager.save_questionnaire(questionnaire_data)
929
 
930
+ # 3. Run AI analysis using the AIProcessor class
931
  try:
932
+ logging.info(f"Starting AI analysis for image: {wound_image}")
 
 
933
 
934
+ # Use the AIProcessor analyze_wound method
935
  analysis_result = self.wound_analyzer.analyze_wound(wound_image, questionnaire_data)
936
+
937
+ if not analysis_result.get('success', False):
938
+ raise Exception(analysis_result.get('error', 'Analysis failed for unknown reason'))
939
+
940
+ # 4. Save AI analysis result to database
941
+ self.database_manager.save_analysis_result(questionnaire_id, analysis_result)
942
+
943
+ # 5. Format comprehensive analysis results with all images
944
+ formatted_analysis = self._format_comprehensive_analysis_results(
945
+ analysis_result, wound_image, questionnaire_data
946
+ )
947
+
948
+ return formatted_analysis
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
949
 
950
+ except Exception as analysis_error:
951
+ logging.error(f"AI analysis error: {analysis_error}")
952
+ return f"""
953
+ <div class='status-error' style='padding: 20px; background: #ffeeee; border-left: 5px solid #ff5555; margin: 20px 0;'>
954
+ <h3>❌ Analysis Error</h3>
955
+ <p>There was an error analyzing the wound image:</p>
956
+ <pre style='background: #f5f5f5; padding: 10px; border-radius: 5px;'>{str(analysis_error)}</pre>
957
+ <p>Please try again with a different image or contact support.</p>
958
+ </div>
959
+ """
960
 
961
  except Exception as e:
962
+ logging.error(f"Analysis handler error: {e}")
963
  return f"<div class='status-error'>❌ Analysis failed: {str(e)}</div>"
964
 
 
 
965
  def handle_logout():
966
  self.current_user = {}
967
  return {
 
1057
  inputs=[search_patient_name],
1058
  outputs=[specific_patient_output]
1059
  )
1060
+
1061
  return app
1062
+
1063
+ def _format_comprehensive_analysis_results(self, analysis_result, image_url=None, questionnaire_data=None):
1064
+ """Format comprehensive analysis results with all visualization images from AIProcessor."""
1065
  try:
1066
+ # Extract the core analysis results from AIProcessor
1067
+ success = analysis_result.get('success', False)
1068
+ if not success:
1069
+ error_msg = analysis_result.get('error', 'Unknown error')
1070
+ return f"<div class='status-error'>❌ Analysis failed: {error_msg}</div>"
1071
+
1072
+ visual_analysis = analysis_result.get('visual_analysis', {})
1073
+ report = analysis_result.get('report', '')
1074
+ saved_image_path = analysis_result.get('saved_image_path', '')
1075
+
1076
+ # Extract wound metrics
1077
+ wound_type = visual_analysis.get('wound_type', 'Unknown')
1078
+ length_cm = visual_analysis.get('length_cm', 0)
1079
+ breadth_cm = visual_analysis.get('breadth_cm', 0)
1080
+ area_cm2 = visual_analysis.get('surface_area_cm2', 0)
1081
+ detection_confidence = visual_analysis.get('detection_confidence', 0)
1082
+
1083
+ # Get image paths for visualizations
1084
+ detection_image_path = visual_analysis.get('detection_image_path', '')
1085
+ segmentation_image_path = visual_analysis.get('segmentation_image_path', '')
1086
+ original_image_path = visual_analysis.get('original_image_path', '')
1087
+
1088
+ # Convert images to base64 for embedding
1089
+ original_image_base64 = None
1090
+ detection_image_base64 = None
1091
+ segmentation_image_base64 = None
1092
+
1093
+ # Original uploaded image
1094
+ if image_url and os.path.exists(image_url):
1095
+ original_image_base64 = self.image_to_base64(image_url)
1096
+ elif original_image_path and os.path.exists(original_image_path):
1097
+ original_image_base64 = self.image_to_base64(original_image_path)
1098
+ elif saved_image_path and os.path.exists(saved_image_path):
1099
+ original_image_base64 = self.image_to_base64(saved_image_path)
1100
+
1101
+ # Detection visualization
1102
+ if detection_image_path and os.path.exists(detection_image_path):
1103
+ detection_image_base64 = self.image_to_base64(detection_image_path)
1104
+
1105
+ # Segmentation visualization
1106
+ if segmentation_image_path and os.path.exists(segmentation_image_path):
1107
+ segmentation_image_base64 = self.image_to_base64(segmentation_image_path)
1108
+
1109
+ # Generate risk assessment from questionnaire data
1110
+ risk_assessment = self._generate_risk_assessment(questionnaire_data)
1111
+ risk_level = risk_assessment['risk_level']
1112
+ risk_score = risk_assessment['risk_score']
1113
+ risk_factors = risk_assessment['risk_factors']
1114
+
1115
  # Set risk class for styling
1116
  risk_class = "low"
1117
  if risk_level.lower() == "moderate":
1118
  risk_class = "moderate"
1119
  elif risk_level.lower() == "high":
1120
  risk_class = "high"
1121
+
1122
  # Format risk factors
1123
  risk_factors_html = "<ul>" + "".join(f"<li>{factor}</li>" for factor in risk_factors) + "</ul>" if risk_factors else "<p>No specific risk factors identified.</p>"
1124
+
1125
+ # Create image gallery
1126
+ image_gallery_html = ""
1127
+ if original_image_base64 or detection_image_base64 or segmentation_image_base64:
1128
+ image_gallery_html = '<div class="image-gallery">'
1129
+
1130
+ if original_image_base64:
1131
+ image_gallery_html += f'''
1132
+ <div class="image-item">
1133
+ <img src="{original_image_base64}" alt="Original Wound Image">
1134
+ <h4>📸 Original Wound Image</h4>
1135
+ <p>Uploaded image for analysis</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1136
  </div>
1137
+ '''
1138
+
1139
+ if detection_image_base64:
1140
+ image_gallery_html += f'''
1141
+ <div class="image-item">
1142
+ <img src="{detection_image_base64}" alt="Wound Detection">
1143
+ <h4>🎯 Wound Detection</h4>
1144
+ <p>AI-detected wound boundaries with {detection_confidence:.1%} confidence</p>
1145
  </div>
1146
+ '''
1147
+
1148
+ if segmentation_image_base64:
1149
+ image_gallery_html += f'''
1150
+ <div class="image-item">
1151
+ <img src="{segmentation_image_base64}" alt="Wound Segmentation">
1152
+ <h4>📏 Wound Segmentation</h4>
1153
+ <p>Detailed wound area measurement and analysis</p>
1154
  </div>
1155
+ '''
1156
+
1157
+ image_gallery_html += '</div>'
1158
+
1159
+ # Convert markdown report to HTML
1160
+ report_html = ""
1161
+ if report:
1162
+ report_html = self.markdown_to_html(report)
1163
+
1164
+ # Final comprehensive HTML output
1165
+ html_output = f"""
1166
+ <div style="max-width: 1200px; margin: 0 auto; background: white; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.1); overflow: hidden;">
1167
+ <div style="background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%); color: white; padding: 40px; text-align: center;">
1168
+ <h1 style="margin: 0; font-size: 32px; font-weight: 700;">🔬 SmartHeal AI Comprehensive Analysis</h1>
1169
+ <p style="margin: 15px 0 0 0; opacity: 0.9; font-size: 18px;">Advanced Computer Vision & Medical AI Assessment</p>
1170
+ <div style="background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px; margin-top: 20px;">
1171
+ <p style="margin: 0; font-size: 16px;"><strong>Patient:</strong> {questionnaire_data.get('patient_name', 'Unknown')} | <strong>Analysis Date:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1172
  </div>
1173
+ </div>
1174
+
1175
+ <div style="padding: 40px;">
1176
+ <div class="status-success" style="margin-bottom: 30px;">
1177
+ <strong>✅ Analysis Status:</strong> Analysis completed successfully with comprehensive wound assessment
1178
  </div>
1179
+
1180
+ <!-- Image Gallery Section -->
1181
+ <div style="margin-bottom: 40px;">
1182
+ <h2 style="color: #2d3748; font-size: 24px; margin-bottom: 20px; border-bottom: 2px solid #e53e3e; padding-bottom: 10px;">🖼️ Visual Analysis Gallery</h2>
1183
+ {image_gallery_html}
1184
  </div>
1185
+
1186
+ <!-- Wound Detection & Classification -->
1187
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 12px; margin-bottom: 30px;">
1188
+ <h2 style="color: #2d3748; margin-top: 0;">🔍 Wound Detection & Classification</h2>
1189
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0;">
1190
+ <div style="background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
1191
+ <h3 style="color: #3182ce; margin: 0 0 10px 0;">Wound Type</h3>
1192
+ <p style="font-weight: 600; font-size: 18px; color: #2d3748; margin: 0;">{wound_type}</p>
1193
+ </div>
1194
+ <div style="background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
1195
+ <h3 style="color: #3182ce; margin: 0 0 10px 0;">Detection Confidence</h3>
1196
+ <p style="font-weight: 600; font-size: 18px; color: #2d3748; margin: 0;">{detection_confidence:.1%}</p>
1197
+ </div>
1198
+ <div style="background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
1199
+ <h3 style="color: #3182ce; margin: 0 0 10px 0;">Location</h3>
1200
+ <p style="font-weight: 600; font-size: 18px; color: #2d3748; margin: 0;">{questionnaire_data.get('wound_location', 'Not specified')}</p>
1201
+ </div>
 
 
 
 
 
1202
  </div>
1203
  </div>
1204
+
1205
+ <!-- Wound Measurements -->
1206
+ <div style="background: #e7f5ff; padding: 30px; border-radius: 12px; margin-bottom: 30px;">
1207
+ <h2 style="color: #2d3748; margin-top: 0;">📏 Wound Measurements</h2>
1208
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 20px; margin: 20px 0;">
1209
+ <div style="background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
1210
+ <h3 style="color: #e53e3e; margin: 0 0 10px 0;">Length</h3>
1211
+ <p style="font-weight: 700; font-size: 24px; color: #2d3748; margin: 0;">{length_cm:.2f} cm</p>
1212
+ </div>
1213
+ <div style="background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
1214
+ <h3 style="color: #e53e3e; margin: 0 0 10px 0;">Width</h3>
1215
+ <p style="font-weight: 700; font-size: 24px; color: #2d3748; margin: 0;">{breadth_cm:.2f} cm</p>
1216
+ </div>
1217
+ <div style="background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
1218
+ <h3 style="color: #e53e3e; margin: 0 0 10px 0;">Surface Area</h3>
1219
+ <p style="font-weight: 700; font-size: 24px; color: #2d3748; margin: 0;">{area_cm2:.2f} cm²</p>
1220
+ </div>
1221
+ </div>
1222
  </div>
1223
+
1224
+ <!-- Risk Assessment -->
1225
+ <div style="background: #fff4e6; padding: 30px; border-radius: 12px; margin-bottom: 30px;">
1226
+ <h2 style="color: #2d3748; margin-top: 0;">⚠️ Risk Assessment</h2>
1227
+ <div style="display: flex; align-items: center; margin: 20px 0; flex-wrap: wrap;">
 
1228
  <div style="background: {'#d4edda' if risk_class == 'low' else '#fff3cd' if risk_class == 'moderate' else '#f8d7da'};
1229
  color: {'#155724' if risk_class == 'low' else '#856404' if risk_class == 'moderate' else '#721c24'};
1230
+ padding: 15px 30px;
1231
  border-radius: 30px;
1232
  font-weight: 700;
1233
+ font-size: 20px;
1234
  text-transform: uppercase;
1235
  letter-spacing: 1px;
1236
+ margin-right: 20px;
1237
+ margin-bottom: 10px;">
1238
  {risk_level} RISK
1239
  </div>
1240
+ <div style="font-size: 18px; color: #2d3748;">
1241
  <strong>Risk Score:</strong> {risk_score}/10
1242
  </div>
1243
  </div>
1244
+ <div style="background: white; padding: 25px; border-radius: 8px; margin: 15px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
1245
+ <h3 style="margin-top: 0; color: #2d3748;">Identified Risk Factors:</h3>
1246
  {risk_factors_html}
1247
  </div>
1248
  </div>
1249
+
1250
+ <!-- Patient Information Summary -->
1251
+ <div style="background: #f0f8f0; padding: 30px; border-radius: 12px; margin-bottom: 30px;">
1252
+ <h2 style="color: #2d3748; margin-top: 0;">👤 Patient Information Summary</h2>
1253
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px;">
1254
+ <div><strong>Age:</strong> {questionnaire_data.get('age', 'Not specified')} years</div>
1255
+ <div><strong>Gender:</strong> {questionnaire_data.get('patient_gender', 'Not specified')}</div>
1256
+ <div><strong>Diabetic Status:</strong> {questionnaire_data.get('diabetic', 'Unknown')}</div>
1257
+ <div><strong>Pain Level:</strong> {questionnaire_data.get('pain_level', 'Not assessed')}/10</div>
1258
+ <div><strong>Wound Duration:</strong> {questionnaire_data.get('wound_duration', 'Not specified')}</div>
1259
+ <div><strong>Moisture Level:</strong> {questionnaire_data.get('moisture', 'Not assessed')}</div>
1260
  </div>
1261
+ {f"<div style='margin-top: 20px;'><strong>Medical History:</strong> {questionnaire_data.get('medical_history', 'None provided')}</div>" if questionnaire_data.get('medical_history') else ""}
1262
+ {f"<div style='margin-top: 10px;'><strong>Current Medications:</strong> {questionnaire_data.get('medications', 'None listed')}</div>" if questionnaire_data.get('medications') else ""}
1263
+ {f"<div style='margin-top: 10px;'><strong>Known Allergies:</strong> {questionnaire_data.get('allergies', 'None listed')}</div>" if questionnaire_data.get('allergies') else ""}
1264
+ </div>
1265
+
1266
+ <!-- AI Generated Report -->
1267
+ {f'<div style="background: #f8f9fa; padding: 30px; border-radius: 12px; margin-bottom: 30px;"><h2 style="color: #2d3748; margin-top: 0;">🤖 AI-Generated Clinical Report</h2><div style="background: white; padding: 25px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">{report_html}</div></div>' if report_html else ''}
1268
+
1269
+ <!-- Important Disclaimers -->
1270
+ <div style="background: #fff5f5; border: 2px solid #feb2b2; padding: 25px; border-radius: 12px; margin: 30px 0;">
1271
+ <h3 style="color: #c53030; margin-top: 0;">⚠️ Important Medical Disclaimers</h3>
1272
+ <ul style="color: #742a2a; line-height: 1.6;">
1273
+ <li><strong>Not a Medical Diagnosis:</strong> This AI analysis is for informational purposes only and does not constitute medical advice, diagnosis, or treatment.</li>
1274
+ <li><strong>Professional Consultation Required:</strong> Always consult with qualified healthcare professionals for proper clinical assessment and treatment decisions.</li>
1275
+ <li><strong>Measurement Accuracy:</strong> All measurements are estimates based on computer vision algorithms and should be verified with clinical tools.</li>
1276
+ <li><strong>Risk Assessment Limitations:</strong> Risk factors are based on provided information and may not reflect the complete clinical picture.</li>
1277
+ </ul>
1278
  </div>
1279
+
1280
+ <!-- Footer -->
1281
+ <div style="text-align: center; padding: 30px 0; border-top: 2px solid #e2e8f0; margin-top: 30px;">
1282
+ <p style="color: #6c757d; font-style: italic; font-size: 16px; margin: 0;">
1283
+ 🏥 Analysis completed by <strong>SmartHeal AI</strong> - Advanced Wound Care Assistant<br>
1284
+ <small>Report generated on {datetime.now().strftime('%B %d, %Y at %I:%M %p')}</small>
1285
  </p>
1286
  </div>
1287
  </div>
1288
  </div>
1289
  """
1290
+
1291
  return html_output
1292
 
1293
  except Exception as e:
1294
+ logging.error(f"Error formatting comprehensive results: {e}")
1295
+ return f"<div class='status-error'>❌ Error displaying results: {str(e)}</div>"
1296
+
1297
+ def _generate_risk_assessment(self, questionnaire_data):
1298
+ """Generate risk assessment based on questionnaire data"""
1299
+ if not questionnaire_data:
1300
+ return {'risk_level': 'Unknown', 'risk_score': 0, 'risk_factors': []}
1301
+
1302
+ risk_factors = []
1303
+ risk_score = 0
1304
+
1305
+ try:
1306
+ # Age assessment
1307
+ age = questionnaire_data.get('age', 0)
1308
+ if isinstance(age, str):
1309
+ try:
1310
+ age = int(age)
1311
+ except ValueError:
1312
+ age = 0
1313
+
1314
+ if age > 65:
1315
+ risk_factors.append("Advanced age (>65 years)")
1316
+ risk_score += 2
1317
+ elif age > 50:
1318
+ risk_factors.append("Older adult (50-65 years)")
1319
+ risk_score += 1
1320
+
1321
+ # Diabetic status
1322
+ diabetic_status = str(questionnaire_data.get('diabetic', '')).lower()
1323
+ if 'yes' in diabetic_status:
1324
+ risk_factors.append("Diabetes mellitus")
1325
+ risk_score += 3
1326
+
1327
+ # Infection signs
1328
+ infection = str(questionnaire_data.get('infection', '')).lower()
1329
+ if 'yes' in infection:
1330
+ risk_factors.append("Signs of infection present")
1331
+ risk_score += 3
1332
+
1333
+ # Pain level
1334
+ pain_level = questionnaire_data.get('pain_level', 0)
1335
+ if isinstance(pain_level, str):
1336
+ try:
1337
+ pain_level = float(pain_level)
1338
+ except ValueError:
1339
+ pain_level = 0
1340
+
1341
+ if pain_level >= 7:
1342
+ risk_factors.append("High pain level (≥7/10)")
1343
+ risk_score += 2
1344
+ elif pain_level >= 5:
1345
+ risk_factors.append("Moderate pain level (5-6/10)")
1346
+ risk_score += 1
1347
+
1348
+ # Wound duration
1349
+ duration = str(questionnaire_data.get('wound_duration', '')).lower()
1350
+ if any(term in duration for term in ['month', 'months', 'year', 'years']):
1351
+ risk_factors.append("Chronic wound (>4 weeks)")
1352
+ risk_score += 3
1353
+
1354
+ # Moisture level
1355
+ moisture = str(questionnaire_data.get('moisture', '')).lower()
1356
+ if any(term in moisture for term in ['wet', 'saturated']):
1357
+ risk_factors.append("Excessive wound exudate")
1358
+ risk_score += 1
1359
+
1360
+ # Medical history analysis
1361
+ medical_history = str(questionnaire_data.get('medical_history', '')).lower()
1362
+ if any(term in medical_history for term in ['vascular', 'circulation', 'heart']):
1363
+ risk_factors.append("Cardiovascular disease")
1364
+ risk_score += 2
1365
+ if any(term in medical_history for term in ['immune', 'cancer', 'steroid']):
1366
+ risk_factors.append("Immune system compromise")
1367
+ risk_score += 2
1368
+ if any(term in medical_history for term in ['smoking', 'tobacco']):
1369
+ risk_factors.append("Smoking history")
1370
+ risk_score += 2
1371
+
1372
+ # Determine risk level
1373
+ if risk_score >= 8:
1374
+ risk_level = "Very High"
1375
+ elif risk_score >= 6:
1376
+ risk_level = "High"
1377
+ elif risk_score >= 3:
1378
+ risk_level = "Moderate"
1379
+ else:
1380
+ risk_level = "Low"
1381
+
1382
+ return {
1383
+ 'risk_score': risk_score,
1384
+ 'risk_level': risk_level,
1385
+ 'risk_factors': risk_factors
1386
+ }
1387
+
1388
+ except Exception as e:
1389
+ logging.error(f"Risk assessment error: {e}")
1390
+ return {
1391
+ 'risk_score': 0,
1392
+ 'risk_level': 'Unknown',
1393
+ 'risk_factors': ['Unable to assess risk due to data processing error']
1394
+ }