VaneshDev commited on
Commit
32e6860
Β·
verified Β·
1 Parent(s): 2adfe45

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1047 -67
app.py CHANGED
@@ -1,11 +1,789 @@
1
- import gradio as gr
2
- from PIL import Image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import torch
4
  from torchvision import models, transforms
5
  import PyPDF2
6
 
7
- model = models.densenet121(pretrained=True)
8
- model.eval()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  def preprocess_image(image):
11
  transform = transforms.Compose([
@@ -15,9 +793,12 @@ def preprocess_image(image):
15
  return transform(image).unsqueeze(0)
16
 
17
  def predict_xray(image):
 
 
 
18
  image_tensor = preprocess_image(image)
19
  with torch.no_grad():
20
- outputs = model(image_tensor)
21
  probs = torch.nn.functional.softmax(outputs[0], dim=0)
22
 
23
  conditions = ["Normal", "Pneumonia", "Cancer", "TB", "Other"]
@@ -69,66 +850,265 @@ def analyze_report(file):
69
  report_summary = f"Patient Report (Preview): {text[:300]}..."
70
  return report_summary
71
 
72
- def create_interface():
73
- with gr.Blocks() as demo:
74
- custom_css = """
75
- .gradio-container {
76
- background-color: #f4f6f9;
77
- border-radius: 15px;
78
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
79
- padding: 30px;
80
- font-family: 'Segoe UI', sans-serif;
81
- }
82
- .title {
83
- font-size: 30px;
84
- text-align: center;
85
- color: #4C6A92;
86
- }
87
- .gradio-button {
88
- background-color: #3B82F6;
89
- color: white;
90
- border-radius: 10px;
91
- padding: 15px;
92
- }
93
- .result-box {
94
- background-color: #ffffff;
95
- border-radius: 10px;
96
- padding: 20px;
97
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
98
- margin-top: 20px;
99
- }
100
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- gr.Markdown("<h1 class='title'>RadiologyScan AI</h1>")
103
- gr.Markdown("""
104
- <h3>🩻 Radiology Areas Covered:</h3>
105
- <table style='width:100%; border: 1px solid #ccc;'>
106
- <tr><th>Area</th><th>Common Tools</th><th>Focused Problems</th></tr>
107
- <tr><td>Lungs</td><td>X-ray, CT</td><td>Pneumonia, TB, Lung cancer</td></tr>
108
- <tr><td>Brain</td><td>MRI, CT</td><td>Stroke, Tumors</td></tr>
109
- <tr><td>Bones/Joints</td><td>X-ray, CT, MRI</td><td>Fractures, Arthritis</td></tr>
110
- <tr><td>Abdomen/Pelvis</td><td>Ultrasound, CT</td><td>Liver/kidney issues, tumors, appendicitis</td></tr>
111
- <tr><td>Cancer Anywhere</td><td>MRI, CT, PET</td><td>Tumors, cancer spread, biopsy guidance</td></tr>
112
- </table>
113
- """)
114
-
115
- with gr.Row():
116
- xray_input = gr.Image(label="Upload Chest X-ray", type="pil")
117
- report_input = gr.File(label="Upload Patient Report (PDF)", file_count="single")
118
-
119
- with gr.Row():
120
- predict_button = gr.Button("Analyze X-ray", elem_classes="gradio-button")
121
- report_button = gr.Button("Analyze Report", elem_classes="gradio-button")
122
-
123
- xray_output = gr.HTML(label="X-ray Diagnosis Summary", elem_classes="result-box")
124
- xray_result = gr.HTML(label="Detailed X-ray Results", elem_classes="result-box")
125
- additional_feedback = gr.Textbox(label="Additional Feedback", interactive=False, elem_classes="result-box")
126
- report_output = gr.Textbox(label="Report Summary", interactive=False, elem_classes="result-box")
127
-
128
- predict_button.click(predict_xray, inputs=xray_input, outputs=[xray_output, xray_result, additional_feedback])
129
- report_button.click(analyze_report, inputs=report_input, outputs=report_output)
130
-
131
- return demo
132
-
133
- demo = create_interface()
134
- demo.launch(share=True)
 
1
+ import streamlit as st
2
+ import cv2
3
+ import numpy as np
4
+ import mediapipe as mp
5
+ from sklearn.linear_model import LinearRegression
6
+ import random
7
+ import base64
8
+ import joblib
9
+ from datetime import datetime
10
+ import io
11
+ from reportlab.lib.pagesizes import A4
12
+ from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, PageBreak, PageTemplate, Frame, Image
13
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
14
+ from reportlab.lib.units import inch
15
+ from reportlab.lib import colors
16
+ from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
17
+ from reportlab.graphics.shapes import Drawing, Line
18
+ from PIL import Image as PILImage
19
+ import tempfile
20
+ import os
21
+ import logging
22
+ import re
23
+ import pandas as pd
24
  import torch
25
  from torchvision import models, transforms
26
  import PyPDF2
27
 
28
+ # Set up logging
29
+ logging.basicConfig(level=logging.DEBUG)
30
+ logger = logging.getLogger(__name__)
31
+
32
+ # Configure Streamlit page
33
+ st.set_page_config(
34
+ page_title="AI Health Report Generator",
35
+ page_icon="🧠",
36
+ layout="wide",
37
+ initial_sidebar_state="expanded"
38
+ )
39
+
40
+ # Custom CSS for enhanced UI with DataFrame styling
41
+ st.markdown("""
42
+ <style>
43
+ body {
44
+ font-family: 'Helvetica', Arial, sans-serif;
45
+ }
46
+ .main-header {
47
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
48
+ padding: 1.5rem;
49
+ border-radius: 10px;
50
+ color: white;
51
+ text-align: center;
52
+ margin-bottom: 1.5rem;
53
+ box-shadow: 0 8px 20px rgba(0,0,0,0.15);
54
+ }
55
+ .main-header h1 {
56
+ font-size: 2.5rem;
57
+ font-weight: 500;
58
+ margin-bottom: 0.5rem;
59
+ }
60
+ .main-header p {
61
+ font-size: 1rem;
62
+ font-weight: 400;
63
+ }
64
+ .patient-form {
65
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
66
+ padding: 1.5rem;
67
+ border-radius: 10px;
68
+ color: white;
69
+ margin-bottom: 1.5rem;
70
+ }
71
+ .patient-form input, .patient-form select {
72
+ border-radius: 8px;
73
+ margin-bottom: 0.5rem;
74
+ font-size: 0.9rem;
75
+ }
76
+ .upload-area {
77
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
78
+ padding: 1.5rem;
79
+ border-radius: 10px;
80
+ border: 1.5px dashed white;
81
+ text-align: center;
82
+ color: white;
83
+ margin-bottom: 1.5rem;
84
+ }
85
+ .upload-area h3 {
86
+ font-size: 1.5rem;
87
+ font-weight: 500;
88
+ margin-bottom: 0.5rem;
89
+ }
90
+ .upload-area p {
91
+ font-size: 0.9rem;
92
+ }
93
+ .health-card {
94
+ background: linear-gradient(135deg, #E6E6FA 0%, #F0F8FF 100%);
95
+ border: 1px solid #CCCCCC;
96
+ border-radius: 12px;
97
+ padding: 1.5rem;
98
+ margin: 0.5rem 0;
99
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
100
+ }
101
+ .stButton > button {
102
+ background: linear-gradient(135deg, #2E8B57, #228B22);
103
+ color: white;
104
+ border: 1px solid #228B22;
105
+ border-radius: 8px;
106
+ padding: 0.5rem 1.5rem;
107
+ font-weight: 500;
108
+ font-size: 14px;
109
+ box-shadow: 0 3px 10px rgba(46, 139, 87, 0.2);
110
+ transition: all 0.3s;
111
+ width: 100%;
112
+ }
113
+ .stButton > button:hover {
114
+ background: linear-gradient(135deg, #228B22, #1B6B1B);
115
+ transform: translateY(-1px);
116
+ box-shadow: 0 4px 12px rgba(46, 139, 87, 0.3);
117
+ }
118
+ .metric-card {
119
+ background: white;
120
+ padding: 0.8rem;
121
+ border-radius: 8px;
122
+ border-left: 3px solid #2E8B57;
123
+ margin: 0.5rem 0;
124
+ box-shadow: 0 2px 6px rgba(0,0,0,0.1);
125
+ transition: transform 0.2s;
126
+ }
127
+ .metric-card:hover {
128
+ transform: scale(1.02);
129
+ }
130
+ .metric-card p {
131
+ font-size: 0.9rem;
132
+ margin: 0.3rem 0;
133
+ }
134
+ .category-container {
135
+ border: 1px solid transparent;
136
+ border-image: linear-gradient(135deg, #2E8B57, #228B22) 1;
137
+ border-radius: 10px;
138
+ padding: 0.5rem;
139
+ margin-bottom: 1rem;
140
+ background-color: #E6E6FA;
141
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
142
+ border-top: 2px solid #2E8B57;
143
+ }
144
+ .table-container {
145
+ width: 100%;
146
+ overflow-x: auto;
147
+ display: block;
148
+ }
149
+ .stDataFrame table {
150
+ width: 100 !important;
151
+ border-collapse: collapse !important;
152
+ font-size: 12px !important;
153
+ background-color: #FFFFFF !important;
154
+ }
155
+ .stDataFrame thead tr th {
156
+ background-color: #2E8B57 !important;
157
+ color: white !important;
158
+ font-weight: bold !important;
159
+ font-size: 14px !important;
160
+ padding: 0.5rem !important;
161
+ border: 1px solid #228B22 !important;
162
+ text-align: center !important;
163
+ }
164
+ .stDataFrame thead tr th:first-child {
165
+ text-align: left !important;
166
+ padding-left: 0.7rem !important;
167
+ }
168
+ .stDataFrame tbody tr td {
169
+ padding: 0.5rem !important;
170
+ border: 1px solid #CCCCCC !important;
171
+ text-align: center !important;
172
+ }
173
+ .stDataFrame tbody tr td:first-child {
174
+ text-align: left !important;
175
+ padding-left: 0.7rem !important;
176
+ }
177
+ .stDataFrame tbody tr:nth-child(even) {
178
+ background-color: #F8F8FF !important;
179
+ }
180
+ .stDataFrame tbody tr:nth-child(odd) {
181
+ background-color: #F0F8FF !important;
182
+ }
183
+ .stDataFrame tbody tr:hover {
184
+ background-color: #E0E0FF !important;
185
+ }
186
+ .status-normal {
187
+ color: #2E8B57 !important;
188
+ }
189
+ .status-low {
190
+ color: #ffca28 !important;
191
+ }
192
+ .status-high {
193
+ color: #d32f2f !important;
194
+ }
195
+ .summary-card {
196
+ background-color: #E6E6FA;
197
+ border-left: 3px solid #2E8B57;
198
+ border-radius: 8px;
199
+ padding: 1rem;
200
+ margin: 1rem 0;
201
+ box-shadow: 0 2px 6px rgba(0,0,0,0.1);
202
+ }
203
+ .summary-card p {
204
+ font-size: 0.95rem;
205
+ margin: 0.3rem 0;
206
+ color: #333;
207
+ }
208
+ .summary-card b {
209
+ color: #2E8B57;
210
+ }
211
+ .help-report {
212
+ background: linear-gradient(135deg, #E6E6FA 0%, #F0F8FF 100%);
213
+ border: 1px solid #CCCCCC;
214
+ border-radius: 12px;
215
+ padding: 1.5rem;
216
+ margin: 1rem 0;
217
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
218
+ }
219
+ .help-report-content {
220
+ display: flex;
221
+ align-items: center;
222
+ }
223
+ .help-report img {
224
+ max-width: 150px;
225
+ max-height: 150px;
226
+ border-radius: 8px;
227
+ margin-right: 1rem;
228
+ }
229
+ .help-report-details {
230
+ display: inline-block;
231
+ }
232
+ .help-report-details p {
233
+ font-size: 0.9rem;
234
+ margin: 0.3rem 0;
235
+ color: #333;
236
+ }
237
+ .help-report-details b {
238
+ color: #2E8B57;
239
+ }
240
+ .xray-analysis {
241
+ margin-top: 1rem;
242
+ padding: 1rem;
243
+ background-color: #ffffff;
244
+ border-radius: 10px;
245
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
246
+ }
247
+ </style>
248
+ """, unsafe_allow_html=True)
249
+
250
+ # Initialize MediaPipe
251
+ @st.cache_resource
252
+ def load_face_mesh():
253
+ try:
254
+ mp_face_mesh = mp.solutions.face_mesh
255
+ face_mesh = mp_face_mesh.FaceMesh(
256
+ static_image_mode=True,
257
+ max_num_faces=1,
258
+ refine_landmarks=True,
259
+ min_detection_confidence=0.5
260
+ )
261
+ logger.info("MediaPipe FaceMesh initialized successfully")
262
+ return face_mesh
263
+ except Exception as e:
264
+ st.error(f"Failed to initialize MediaPipe: {str(e)}")
265
+ logger.error(f"MediaPipe initialization failed: {str(e)}")
266
+ return None
267
+
268
+ # Initialize MediaPipe drawing utilities
269
+ @st.cache_resource
270
+ def load_drawing_utils():
271
+ try:
272
+ mp_drawing = mp.solutions.drawing_utils
273
+ mp_drawing_styles = mp.solutions.drawing_styles
274
+ logger.info("MediaPipe drawing utilities initialized successfully")
275
+ return mp_drawing, mp_drawing_styles
276
+ except Exception as e:
277
+ logger.error(f"MediaPipe drawing utilities initialization failed: {str(e)}")
278
+ return None, None
279
+
280
+ # Load models
281
+ @st.cache_resource
282
+ def load_models():
283
+ try:
284
+ hemoglobin_model = joblib.load("hemoglobin_model_from_anemia_dataset.pkl")
285
+ except FileNotFoundError:
286
+ st.warning("Hemoglobin model not found. Training a temporary model.")
287
+ hemoglobin_model = train_model((13.5, 17.5))
288
+ try:
289
+ spo2_model = joblib.load("spo2_model_simulated.pkl")
290
+ except FileNotFoundError:
291
+ st.warning("SpO2 model not found. Training a temporary model.")
292
+ spo2_model = train_model((95, 100))
293
+ try:
294
+ hr_model = joblib.load("heart_rate_model.pkl")
295
+ except FileNotFoundError:
296
+ st.warning("Heart rate model not found. Training a temporary model.")
297
+ hr_model = train_model((60, 100))
298
+ logger.info("Models loaded or trained successfully")
299
+ return hemoglobin_model, spo2_model, hr_model
300
+
301
+ @st.cache_resource
302
+ def load_xray_model():
303
+ try:
304
+ model = models.densenet121(pretrained=True)
305
+ model.eval()
306
+ logger.info("X-ray model (DenseNet121) loaded successfully")
307
+ return model
308
+ except Exception as e:
309
+ st.error(f"Failed to load X-ray model: {str(e)}")
310
+ logger.error(f"X-ray model loading failed: {str(e)}")
311
+ return None
312
+
313
+ def train_model(output_range):
314
+ try:
315
+ X = [[random.uniform(0.2, 0.5), random.uniform(0.05, 0.2), random.uniform(0.05, 0.2),
316
+ random.uniform(0.2, 0.5), random.uniform(0.2, 0.5), random.uniform(0.2, 0.5),
317
+ random.uniform(0.2, 0.5)] for _ in range(100)]
318
+ y = [random.uniform(*output_range) for _ in X]
319
+ model = LinearRegression().fit(X, y)
320
+ logger.info(f"Model trained for range {output_range}")
321
+ return model
322
+ except Exception as e:
323
+ st.error(f"Failed to train model: {str(e)}")
324
+ logger.error(f"Model training failed: {str(e)}")
325
+ return None
326
+
327
+ def extract_features(image, landmarks):
328
+ try:
329
+ if len(image.shape) < 3 or image.shape[2] != 3:
330
+ st.error("Invalid image format: Expected RGB image.")
331
+ logger.error("Invalid image format: Not RGB")
332
+ return None
333
+ red_channel = image[:, :, 2]
334
+ green_channel = image[:, :, 1]
335
+ blue_channel = image[:, :, 0]
336
+ red_percent = 100 * np.mean(red_channel) / 255
337
+ green_percent = 100 * np.mean(green_channel) / 255
338
+ blue_percent = 100 * np.mean(blue_channel) / 255
339
+ logger.info("Features extracted successfully")
340
+ return [red_percent, green_percent, blue_percent]
341
+ except Exception as e:
342
+ st.error(f"Failed to extract features: {str(e)}")
343
+ logger.error(f"Feature extraction failed: {str(e)}")
344
+ return None
345
+
346
+ def get_risk_level(value, normal_range):
347
+ try:
348
+ low, high = normal_range
349
+ if value < low:
350
+ return "Low", "#ffca28"
351
+ elif value > high:
352
+ return "High", "#d32f2f"
353
+ else:
354
+ return "Normal", "#2E8B57"
355
+ except Exception as e:
356
+ st.error(f"Failed to determine risk level: {str(e)}")
357
+ logger.error(f"Risk level determination failed: {str(e)}")
358
+ return "Unknown", "#ffffff"
359
+
360
+ def draw_analyzed_image(image, landmarks):
361
+ try:
362
+ mp_drawing, mp_drawing_styles = load_drawing_utils()
363
+ if mp_drawing is None or mp_drawing_styles is None:
364
+ logger.error("Drawing utilities not initialized")
365
+ return image
366
+ annotated_image = image.copy()
367
+ h, w = annotated_image.shape[:2]
368
+ # Draw facial landmarks
369
+ mp_drawing.draw_landmarks(
370
+ image=annotated_image,
371
+ landmark_list=landmarks,
372
+ connections=mp.solutions.face_mesh.FACEMESH_TESSELATION,
373
+ landmark_drawing_spec=None,
374
+ connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_tesselation_style()
375
+ )
376
+ # Highlight analysis regions (cheeks, forehead, nose)
377
+ cheek_left_points = [landmarks.landmark[i] for i in range(50, 151)]
378
+ cheek_right_points = [landmarks.landmark[i] for i in range(280, 381)]
379
+ forehead_points = [landmarks.landmark[i] for i in range(10, 51)]
380
+ nose_points = [landmarks.landmark[i] for i in range(1, 5)]
381
+ def normalize_to_pixel(landmark):
382
+ return (int(landmark.x * w), int(landmark.y * h))
383
+ def get_bounding_box(points):
384
+ x_coords = [p.x * w for p in points]
385
+ y_coords = [p.y * h for p in points]
386
+ x_min, x_max = int(min(x_coords)), int(max(x_coords))
387
+ y_min, y_max = int(min(y_coords)), int(max(y_coords))
388
+ return x_min, y_min, x_max, y_max
389
+ # Draw semi-transparent colored rectangles
390
+ overlay = annotated_image.copy()
391
+ # Left cheek (red)
392
+ x_min, y_min, x_max, y_max = get_bounding_box(cheek_left_points)
393
+ cv2.rectangle(overlay, (x_min, y_min), (x_max, y_max), (255, 0, 0), -1)
394
+ logger.debug(f"Left cheek box: ({x_min}, {y_min}, {x_max}, {y_max})")
395
+ # Right cheek (red)
396
+ x_min, y_min, x_max, y_max = get_bounding_box(cheek_right_points)
397
+ cv2.rectangle(overlay, (x_min, y_min), (x_max, y_max), (255, 0, 0), -1)
398
+ logger.debug(f"Right cheek box: ({x_min}, {y_min}, {x_max}, {y_max})")
399
+ # Forehead (green)
400
+ x_min, y_min, x_max, y_max = get_bounding_box(forehead_points)
401
+ cv2.rectangle(overlay, (x_min, y_min), (x_max, y_max), (0, 255, 0), -1)
402
+ logger.debug(f"Forehead box: ({x_min}, {y_min}, {x_max}, {y_max})")
403
+ # Nose (blue)
404
+ x_min, y_min, x_max, y_max = get_bounding_box(nose_points)
405
+ cv2.rectangle(overlay, (x_min, y_min), (x_max, y_max), (0, 0, 255), -1)
406
+ logger.debug(f"Nose box: ({x_min}, {y_min}, {x_max}, {y_max})")
407
+ # Apply transparency
408
+ alpha = 0.4 # 40% opacity
409
+ cv2.addWeighted(overlay, alpha, annotated_image, 1 - alpha, 0, annotated_image)
410
+ logger.info("Analyzed image generated with landmarks and region highlights")
411
+ return annotated_image
412
+ except Exception as e:
413
+ logger.error(f"Failed to draw analyzed image: {str(e)}")
414
+ st.error(f"Analyzed image generation failed: {str(e)}")
415
+ return image
416
+
417
+ def create_pdf_report(patient_data, test_results, profile_image):
418
+ try:
419
+ logger.info("Starting PDF generation")
420
+ buffer = io.BytesIO()
421
+ doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=30, leftMargin=30, topMargin=50, bottomMargin=80)
422
+ elements = []
423
+ styles = getSampleStyleSheet()
424
+ # Define custom styles
425
+ header_logo_style = ParagraphStyle(
426
+ 'HeaderLogoStyle',
427
+ parent=styles['Heading1'],
428
+ fontName='Helvetica-Bold',
429
+ fontSize=16,
430
+ spaceAfter=8,
431
+ alignment=TA_LEFT,
432
+ textColor=colors.HexColor('#2E8B57'),
433
+ leftIndent=0,
434
+ spaceBefore=0
435
+ )
436
+ test_report_badge_style = ParagraphStyle(
437
+ 'TestReportBadgeStyle',
438
+ parent=styles['Normal'],
439
+ fontName='Helvetica-Bold',
440
+ fontSize=12,
441
+ alignment=TA_RIGHT,
442
+ textColor=colors.HexColor('#2E8B57'),
443
+ borderWidth=1,
444
+ borderColor=colors.HexColor('#2E8B57'),
445
+ borderPadding=8,
446
+ spaceBefore=0,
447
+ spaceAfter=0
448
+ )
449
+ patient_info_style = ParagraphStyle(
450
+ 'PatientInfoStyle',
451
+ parent=styles['Normal'],
452
+ fontName='Helvetica',
453
+ fontSize=9,
454
+ spaceAfter=6,
455
+ textColor=colors.black,
456
+ alignment=TA_LEFT
457
+ )
458
+ section_header_style = ParagraphStyle(
459
+ 'SectionHeaderStyle',
460
+ parent=styles['Heading2'],
461
+ fontName='Helvetica-Bold',
462
+ fontSize=12,
463
+ spaceAfter=8,
464
+ spaceBefore=12,
465
+ textColor=colors.black,
466
+ alignment=TA_CENTER,
467
+ backColor=colors.HexColor('#f0f0f0'),
468
+ borderWidth=1,
469
+ borderColor=colors.black,
470
+ borderPadding=6
471
+ )
472
+ footer_style = ParagraphStyle(
473
+ 'FooterStyle',
474
+ parent=styles['Normal'],
475
+ fontName='Helvetica',
476
+ fontSize=8,
477
+ textColor=colors.black,
478
+ alignment=TA_CENTER,
479
+ spaceAfter=4
480
+ )
481
+ signatory_style = ParagraphStyle(
482
+ 'SignatoryStyle',
483
+ parent=styles['Normal'],
484
+ fontName='Helvetica-Bold',
485
+ fontSize=9,
486
+ spaceAfter=6,
487
+ textColor=colors.black,
488
+ alignment=TA_RIGHT
489
+ )
490
+ # Footer content
491
+ footer_text = """
492
+ Sathkrutha Tech Solutions Pvt. Ltd Registered Office: H.No: 2-3-685/5/1, Flat N Venkateshwara Nagar, Amberpet, Hyderabad, Telangana 500013, INDIA<br/>
493
+ T: +91 4027264141 F: +91 4027263667 E: helpdesk@sathkrutha.com
494
+ """
495
+ def add_page_footer(canvas, doc):
496
+ canvas.saveState()
497
+ canvas.setLineWidth(2)
498
+ canvas.setStrokeColor(colors.black)
499
+ canvas.rect(20, 20, A4[0]-40, A4[1]-40)
500
+ canvas.setFont('Helvetica', 8)
501
+ page_num = canvas.getPageNumber()
502
+ if hasattr(doc, '_total_pages'):
503
+ page_text = f"Page {page_num} of {doc._total_pages}"
504
+ else:
505
+ page_text = f"Page {page_num}"
506
+ canvas.drawRightString(A4[0]-40, 25, page_text)
507
+ footer_para = Paragraph(footer_text, footer_style)
508
+ w, h = footer_para.wrap(A4[0]-60, 40)
509
+ footer_para.drawOn(canvas, 30, 30)
510
+ canvas.restoreState()
511
+ doc.addPageTemplates([
512
+ PageTemplate(id='AllPages', frames=[Frame(30, 80, A4[0]-60, A4[1]-130)], onPage=add_page_footer)
513
+ ])
514
+ # Header
515
+ header_table_data = [
516
+ [Paragraph("<b>Sathkrutha</b><br/><font color='#FF8C00'>Clinical Diagnostics</font>", header_logo_style),
517
+ Paragraph("Test Report", test_report_badge_style)]
518
+ ]
519
+ header_table = Table(header_table_data, colWidths=[4*inch, 2*inch])
520
+ header_table.setStyle(TableStyle([
521
+ ('VALIGN', (0, 0), (-1, -1), 'TOP'),
522
+ ('ALIGN', (0, 0), (0, 0), 'LEFT'),
523
+ ('ALIGN', (1, 0), (1, 0), 'RIGHT'),
524
+ ('LEFTPADDING', (0, 0), (-1, -1), 0),
525
+ ('RIGHTPADDING', (0, 0), (-1, -1), 0),
526
+ ('TOPPADING', (0, 0), (-1, -1), 0),
527
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 12),
528
+ ]))
529
+ elements.append(header_table)
530
+ elements.append(Spacer(1, 10))
531
+ # Issued to section
532
+ issued_to_data = [["Issued to :", ""]]
533
+ issued_table = Table(issued_to_data, colWidths=[1*inch, 5*inch])
534
+ issued_table.setStyle(TableStyle([
535
+ ('FONT', (0, 0), (-1, -1), 'Helvetica-Bold', 9),
536
+ ('GRID', (0, 0), (-1, -1), 1, colors.black),
537
+ ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#e6e6e6')),
538
+ ('LEFTPADDING', (0, 0), (-1, -1), 4),
539
+ ('TOPPADING', (0, 0), (-1, -1), 4),
540
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
541
+ ]))
542
+ elements.append(issued_table)
543
+ # Patient details with photo
544
+ img_buffer = io.BytesIO()
545
+ profile_image.save(img_buffer, format="PNG")
546
+ img_buffer.seek(0)
547
+ img = Image(img_buffer, width=1.5*inch, height=1.5*inch)
548
+ patient_details = [
549
+ [img, Paragraph(f"Name: {patient_data.get('name', 'Unknown Patient')}<br/>"
550
+ f"Age: {patient_data.get('age', 'N/A')} Years<br/>"
551
+ f"Gender: {patient_data.get('gender', 'Male')}<br/>"
552
+ f"ID: {patient_data.get('id', 'N/A')}<br/>"
553
+ f"Date: {datetime.now().strftime('%d-%b-%Y %H:%M')}", patient_info_style)]
554
+ ]
555
+ patient_table = Table(patient_details, colWidths=[1.5*inch, 5.5*inch])
556
+ patient_table.setStyle(TableStyle([
557
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
558
+ ('LEFTPADDING', (0, 0), (-1, -1), 10),
559
+ ('RIGHTPADDING', (0, 0), (-1, -1), 10),
560
+ ('TOPPADING', (0, 0), (-1, -1), 10),
561
+ ('BOTTOMPADING', (0, 0), (-1, -1), 10),
562
+ ]))
563
+ elements.append(patient_table)
564
+ elements.append(Spacer(1, 15))
565
+ # Test categories
566
+ for category_index, (category, tests) in enumerate(test_results.items()):
567
+ if category_index > 0:
568
+ elements.append(PageBreak())
569
+ clean_category = category.replace("β– ", "").strip().upper()
570
+ elements.append(Paragraph(clean_category, section_header_style))
571
+ elements.append(Spacer(1, 10))
572
+ table_data = [["Test Description", "Value Observed", "Unit", "Biological Reference Interval"]]
573
+ for test_name, result, range_val, level_info in tests:
574
+ level, _ = level_info
575
+ status_indicator = " L" if level == "Low" else " H" if level == "High" else ""
576
+ if "Count" in test_name or test_name == "Respiratory Rate":
577
+ value_str = f"{result:.0f}{status_indicator}"
578
+ elif test_name in ["Temperature", "SpO2"]:
579
+ value_str = f"{result:.1f}{status_indicator}"
580
+ else:
581
+ value_str = f"{result:.1f}{status_indicator}"
582
+ unit = "" if "BP" in test_name else ("g/dL" if "Hemoglobin" in test_name else
583
+ "cu/mm" if "WBC Count" in test_name else
584
+ "Thousand/Β΅L" if "Platelet Count" in test_name else
585
+ "Β΅g/dL" if "Iron" in test_name or "TIBC" in test_name else
586
+ "ng/mL" if "Ferritin" in test_name else
587
+ "mg/dL" if "Bilirubin" in test_name or "Creatinine" in test_name or "Urea" in test_name else
588
+ "mEq/L" if "Sodium" in test_name or "Potassium" in test_name else
589
+ "%" if "SpO2" in test_name else
590
+ "bpm" if "Heart Rate" in test_name else
591
+ "/min" if "Respiratory Rate" in test_name else
592
+ "Β°F" if "Temperature" in test_name else "mmHg")
593
+ range_str = f"{range_val[0]:.0f} - {range_val[1]:.0f}" if "Count" in test_name or test_name == "Respiratory Rate" else f"{range_val[0]:.1f} - {range_val[1]:.1f}"
594
+ table_data.append([test_name, value_str, unit, range_str])
595
+ test_table = Table(table_data, colWidths=[2.5*inch, 1.2*inch, 0.8*inch, 1.5*inch])
596
+ test_table.setStyle(TableStyle([
597
+ ('FONT', (0, 0), (-1, 0), 'Helvetica-Bold', 9),
598
+ ('FONT', (0, 1), (-1, -1), 'Helvetica', 9),
599
+ ('BACKGROUND', (0, 0), (-1, 0), colors.white),
600
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.black),
601
+ ('BOX', (0, 0), (-1, -1), 1, colors.black),
602
+ ('LEFTPADDING', (0, 0), (-1, -1), 6),
603
+ ('RIGHTPADDING', (0, 0), (-1, -1), 6),
604
+ ('TOPPADING', (0, 0), (-1, -1), 6),
605
+ ('BOTTOMPADING', (0, 0), (-1, -1), 6),
606
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
607
+ ('ALIGN', (0, 0), (0, -1), 'LEFT'),
608
+ ('ALIGN', (1, 1), (-1, -1), 'CENTER'),
609
+ ]))
610
+ elements.append(test_table)
611
+ elements.append(Spacer(1, 30))
612
+ signatory_table_data = [["", ""], ["", "DR.SATHAIAH BEGARI"], ["", "MBBS,DCP, Clinical Pathologist"], ["", "AUTHORISED SIGNATORY"]]
613
+ signatory_table = Table(signatory_table_data, colWidths=[4*inch, 2*inch])
614
+ signatory_table.setStyle(TableStyle([
615
+ ('FONT', (1, 1), (1, -1), 'Helvetica-Bold', 9),
616
+ ('ALIGN', (1, 1), (1, -1), 'RIGHT'),
617
+ ('VALIGN', (1, 1), (1, -1), 'TOP'),
618
+ ('LEFTPADDING', (0, 0), (-1, -1), 0),
619
+ ('RIGHTPADDING', (0, 0), (-1, -1), 0),
620
+ ('TOPPADING', (0, 0), (-1, -1), 2),
621
+ ('BOTTOMPADING', (0, 0), (-1, -1), 2),
622
+ ]))
623
+ elements.append(signatory_table)
624
+ try:
625
+ doc.build(elements)
626
+ except MemoryError as me:
627
+ st.error(f"PDF generation failed due to memory issue: {str(me)}")
628
+ logger.error(f"Memory error during PDF build: {str(me)}")
629
+ return None
630
+ except Exception as e:
631
+ st.error(f"PDF generation failed: {str(e)}")
632
+ logger.error(f"PDF building failed: {str(e)}")
633
+ return None
634
+ buffer.seek(0)
635
+ logger.info("PDF buffer ready")
636
+ return buffer
637
+ except Exception as e:
638
+ st.error(f"Unexpected error in PDF generation: {str(e)}")
639
+ logger.error(f"Unexpected PDF generation error: {str(e)}")
640
+ return None
641
+
642
+ def process_input(input_data):
643
+ if input_data is None:
644
+ return None, None
645
+ if input_data.name.endswith(('.jpg', '.jpeg', '.png')):
646
+ try:
647
+ image = PILImage.open(input_data)
648
+ logger.info("Image processed successfully")
649
+ return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR), image
650
+ except Exception as e:
651
+ st.error(f"Failed to process image: {str(e)}")
652
+ logger.error(f"Image processing failed: {str(e)}")
653
+ return None, None
654
+ elif input_data.name.endswith(('.mp4', '.avi', '.mov')):
655
+ tmp_path = None
656
+ try:
657
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp:
658
+ tmp.write(input_data.read())
659
+ tmp_path = tmp.name
660
+ cap = cv2.VideoCapture(tmp_path)
661
+ ret, frame = cap.read()
662
+ cap.release()
663
+ if ret:
664
+ image = PILImage.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
665
+ logger.info("Video frame extracted successfully")
666
+ return frame, image
667
+ else:
668
+ st.error("Failed to extract frame from video.")
669
+ logger.error("Failed to extract video frame")
670
+ return None, None
671
+ except Exception as e:
672
+ st.error(f"Failed to process video: {str(e)}")
673
+ logger.error(f"Video processing failed: {str(e)}")
674
+ return None, None
675
+ finally:
676
+ if tmp_path and os.path.exists(tmp_path):
677
+ try:
678
+ os.unlink(tmp_path)
679
+ logger.info(f"Temporary file {tmp_path} cleaned up")
680
+ except Exception as e:
681
+ logger.warning(f"Failed to clean up temporary file {tmp_path}: {str(e)}")
682
+ return None, None
683
+
684
+ def analyze_face(image, patient_data):
685
+ face_mesh = load_face_mesh()
686
+ if face_mesh is None:
687
+ return None, None, None, "Failed to initialize face mesh."
688
+ hemoglobin_model, spo2_model, hr_model = load_models()
689
+ try:
690
+ frame = cv2.resize(image, (640, 480))
691
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
692
+ result = face_mesh.process(frame_rgb)
693
+ logger.info("Image processed for face detection")
694
+ except Exception as e:
695
+ st.error(f"Image processing failed: {str(e)}")
696
+ logger.error(f"Image processing failed: {str(e)}")
697
+ return None, None, None, "Image processing error."
698
+ if not result.multi_face_landmarks:
699
+ return None, None, None, "Face not detected. Please try another image or video."
700
+ landmarks = result.multi_face_landmarks[0]
701
+ features = extract_features(frame_rgb, landmarks.landmark)
702
+ if features is None:
703
+ return None, None, None, "Failed to extract image features."
704
+ analyzed_image = draw_analyzed_image(frame_rgb, landmarks)
705
+ models = {
706
+ "Hemoglobin": hemoglobin_model,
707
+ "WBC Count": train_model((4.0, 11.0)),
708
+ "Platelet Count": train_model((150, 450)),
709
+ "Iron": train_model((60, 170)),
710
+ "Ferritin": train_model((30, 300)),
711
+ "TIBC": train_model((250, 400)),
712
+ "Bilirubin": train_model((0.3, 1.2)),
713
+ "Creatinine": train_model((0.6, 1.2)),
714
+ "Urea": train_model((7, 20)),
715
+ "Sodium": train_model((135, 145)),
716
+ "Potassium": train_model((3.5, 5.1)),
717
+ "Temperature": train_model((97, 99)),
718
+ "BP Systolic": train_model((90, 120)),
719
+ "BP Diastolic": train_model((60, 80))
720
+ }
721
+ test_values = {}
722
+ for label in models:
723
+ if models[label] is None:
724
+ st.error(f"Model for {label} is not initialized.")
725
+ logger.error(f"Model not initialized for {label}")
726
+ return None, None, None, f"Model error for {label}."
727
+ try:
728
+ if label == "Hemoglobin":
729
+ prediction = models[label].predict([features])[0]
730
+ test_values[label] = prediction
731
+ else:
732
+ value = models[label].predict([[random.uniform(0.2, 0.5) for _ in range(7)]])[0]
733
+ test_values[label] = value
734
+ except Exception as e:
735
+ st.error(f"Prediction failed for {label}: {str(e)}")
736
+ logger.error(f"Prediction failed for {label}: {str(e)}")
737
+ return None, None, None, f"Prediction error for {label}."
738
+ try:
739
+ gray = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2GRAY)
740
+ green_std = np.std(frame_rgb[:, :, 1]) / 255
741
+ brightness_std = np.std(gray) / 255
742
+ tone_index = np.mean(frame_rgb[100:150, 100:150]) / 255 if frame_rgb[100:150, 100:150].size else 0.5
743
+ hr_features = [brightness_std, green_std, tone_index]
744
+ heart_rate = float(np.clip(hr_model.predict([hr_features])[0], 60, 100))
745
+ skin_patch = frame_rgb[100:150, 100:150]
746
+ skin_tone_index = np.mean(skin_patch) / 255 if skin_patch.size else 0.5
747
+ brightness_variation = np.std(cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2GRAY)) / 255
748
+ spo2_features = [heart_rate, brightness_variation, skin_tone_index]
749
+ spo2 = spo2_model.predict([spo2_features])[0]
750
+ rr = int(12 + abs(heart_rate % 5 - 2))
751
+ logger.info(f"Vitals calculated: heart_rate={heart_rate}, spo2={spo2}, rr={rr}")
752
+ except Exception as e:
753
+ st.error(f"Vitals calculation failed: {str(e)}")
754
+ logger.error(f"Vitals calculation failed: {str(e)}")
755
+ return None, None, None, "Vitals calculation error."
756
+ test_results = {
757
+ "Hematology": [
758
+ ("Hemoglobin", test_values["Hemoglobin"], (13.5, 17.5), get_risk_level(test_values["Hemoglobin"], (13.5, 17.5))),
759
+ ("WBC Count", test_values["WBC Count"], (4.0, 11.0), get_risk_level(test_values["WBC Count"], (4.0, 11.0))),
760
+ ("Platelet Count", test_values["Platelet Count"], (150, 450), get_risk_level(test_values["Platelet Count"], (150, 450)))
761
+ ],
762
+ "Iron Panel": [
763
+ ("Iron", test_values["Iron"], (60, 170), get_risk_level(test_values["Iron"], (60, 170))),
764
+ ("Ferritin", test_values["Ferritin"], (30, 300), get_risk_level(test_values["Ferritin"], (30, 300))),
765
+ ("TIBC", test_values["TIBC"], (250, 400), get_risk_level(test_values["TIBC"], (250, 400)))
766
+ ],
767
+ "Liver & Kidney": [
768
+ ("Bilirubin", test_values["Bilirubin"], (0.3, 1.2), get_risk_level(test_values["Bilirubin"], (0.3, 1.2))),
769
+ ("Creatinine", test_values["Creatinine"], (0.6, 1.2), get_risk_level(test_values["Creatinine"], (0.6, 1.2))),
770
+ ("Urea", test_values["Urea"], (7, 20), get_risk_level(test_values["Urea"], (7, 20)))
771
+ ],
772
+ "Electrolytes": [
773
+ ("Sodium", test_values["Sodium"], (135, 145), get_risk_level(test_values["Sodium"], (135, 145))),
774
+ ("Potassium", test_values["Potassium"], (3.5, 5.1), get_risk_level(test_values["Potassium"], (3.5, 5.1)))
775
+ ],
776
+ "Vitals": [
777
+ ("SpO2", spo2, (95, 100), get_risk_level(spo2, (95, 100))),
778
+ ("Heart Rate", heart_rate, (60, 100), get_risk_level(heart_rate, (60, 100))),
779
+ ("Respiratory Rate", rr, (12, 20), get_risk_level(rr, (12, 20))),
780
+ ("Temperature", test_values["Temperature"], (97, 99), get_risk_level(test_values["Temperature"], (97, 99))),
781
+ ("BP Systolic", test_values["BP Systolic"], (90, 120), get_risk_level(test_values["BP Systolic"], (90, 120))),
782
+ ("BP Diastolic", test_values["BP Diastolic"], (60, 80), get_risk_level(test_values["BP Diastolic"], (60, 80)))
783
+ ]
784
+ }
785
+ logger.info(f"Test results generated: {test_results}")
786
+ return test_results, frame_rgb, analyzed_image
787
 
788
  def preprocess_image(image):
789
  transform = transforms.Compose([
 
793
  return transform(image).unsqueeze(0)
794
 
795
  def predict_xray(image):
796
+ xray_model = load_xray_model()
797
+ if xray_model is None:
798
+ return "Error: X-ray model not loaded.", "", ""
799
  image_tensor = preprocess_image(image)
800
  with torch.no_grad():
801
+ outputs = xray_model(image_tensor)
802
  probs = torch.nn.functional.softmax(outputs[0], dim=0)
803
 
804
  conditions = ["Normal", "Pneumonia", "Cancer", "TB", "Other"]
 
850
  report_summary = f"Patient Report (Preview): {text[:300]}..."
851
  return report_summary
852
 
853
+ def main():
854
+ st.markdown("""
855
+ <div class="main-header">
856
+ <h1>🧠 AI Health Report Generator</h1>
857
+ <p>Advanced face-based health analysis using AI technology</p>
858
+ </div>
859
+ """, unsafe_allow_html=True)
860
+ with st.sidebar:
861
+ st.markdown("""
862
+ <div class="patient-form">
863
+ <h2>πŸ‘€ Patient Information</h2>
864
+ </div>
865
+ """, unsafe_allow_html=True)
866
+ patient_name = st.text_input("πŸ‘€ Patient Name", placeholder="Enter full name")
867
+ patient_age = st.number_input("πŸŽ‚ Age", min_value=0, max_value=150, step=1, placeholder="Enter age")
868
+ patient_gender = st.selectbox("⚧ Gender", ["Male", "Female", "Other"])
869
+ patient_id = st.text_input("πŸ†” Patient ID", placeholder="Optional")
870
+ st.markdown("---")
871
+ col1, col2 = st.columns([1, 1], gap="medium")
872
+ with col1:
873
+ st.markdown("""
874
+ <div class="upload-area">
875
+ <h3>πŸ“Έ Capture or Upload Media</h3>
876
+ <p>Use camera (with permission) or upload a file</p>
877
+ </div>
878
+ """, unsafe_allow_html=True)
879
+ use_camera = st.checkbox("Enable Camera Capture", value=False)
880
+ if use_camera:
881
+ camera_input = st.camera_input(
882
+ "Take a photo",
883
+ help="Capture a clear front-facing photo for analysis",
884
+ key="camera_input"
885
+ )
886
+ else:
887
+ camera_input = None
888
+ uploaded_file = st.file_uploader(
889
+ "Or upload an image or video",
890
+ type=['jpg', 'jpeg', 'png', 'mp4', 'avi', 'mov'],
891
+ help="Upload a clear front-facing photo or video for analysis"
892
+ )
893
+ st.info("πŸ‘† Enable camera or upload a photo/video and click 'Generate Health Report' to see results.", icon="ℹ️")
894
+ input_data = camera_input if camera_input else uploaded_file
895
+ if input_data is not None:
896
+ opencv_image, pil_image = process_input(input_data)
897
+ if opencv_image is not None:
898
+ st.image(pil_image, caption="Captured/Uploaded Media", use_container_width=True)
899
+ if st.button("πŸ”¬ Generate Health Report", type="primary"):
900
+ with st.spinner("Analyzing media and generating report..."):
901
+ if not patient_name.strip():
902
+ st.error("Please enter a valid patient name.")
903
+ return
904
+ patient_data = {
905
+ 'name': patient_name or "Unknown Patient",
906
+ 'age': patient_age if patient_age > 0 else 'N/A',
907
+ 'gender': patient_gender or "Male",
908
+ 'id': patient_id or "N/A",
909
+ 'date_time': datetime.now().strftime("%d %b %Y %H:%M"),
910
+ 'analysis_method': 'AI Face-Based Health Scan'
911
+ }
912
+ test_results, processed_frame, analyzed_image = analyze_face(opencv_image, patient_data)
913
+ if test_results:
914
+ analyzed_pil = PILImage.fromarray(cv2.cvtColor(analyzed_image, cv2.COLOR_RGB2BGR))
915
+ st.image(analyzed_pil, caption="Analyzed Face with Highlighted Regions", use_container_width=True)
916
+ # Health Summary
917
+ critical_count = 0
918
+ key_metrics = {}
919
+ for category, tests in test_results.items():
920
+ for test_name, result, range_val, level_info in tests:
921
+ level, _ = level_info
922
+ if level in ["Low", "High"]:
923
+ critical_count += 1
924
+ if test_name in ["Hemoglobin", "SpO2", "Heart Rate"]:
925
+ key_metrics[test_name] = (result, level)
926
+ overall_status = "Requires Attention" if critical_count > 2 else "Normal"
927
+ current_date = datetime.now().strftime("%B %d, %Y %I:%M %p IST") # 10:42 AM IST, Thursday, July 03, 2025
928
+ summary_text = f"""
929
+ <div class='summary-card'>
930
+ <p><b>πŸ“Š Health Summary (as of {current_date}):</b></p>
931
+ <p><b>Hemoglobin:</b> {key_metrics.get('Hemoglobin', ('N/A', 'N/A'))[0]} g/dL ({key_metrics.get('Hemoglobin', ('N/A', 'N/A'))[1]})</p>
932
+ <p><b>SpO2:</b> {key_metrics.get('SpO2', ('N/A', 'N/A'))[0]}% ({key_metrics.get('SpO2', ('N/A', 'N/A'))[1]})</p>
933
+ <p><b>Heart Rate:</b> {key_metrics.get('Heart Rate', ('N/A', 'N/A'))[0]} bpm ({key_metrics.get('Heart Rate', ('N/A', 'N/A'))[1]})</p>
934
+ <p><b>Critical Values:</b> {critical_count}</p>
935
+ <p><b>Overall Status:</b> <span style='color: {"#d32f2f" if critical_count > 2 else "#2E8B57"}'>{overall_status}</span></p>
936
+ </div>
937
+ """
938
+ st.markdown(summary_text, unsafe_allow_html=True)
939
+ st.session_state.test_results = test_results
940
+ st.session_state.patient_data = patient_data
941
+ st.session_state.processed_frame = processed_frame
942
+ st.session_state.pil_image = pil_image
943
+ st.session_state.analyzed_image = analyzed_pil
944
+ st.success("βœ… Analysis complete! View results in the next column.")
945
+ logger.info(f"Stored test_results in session_state: {test_results}")
946
+ else:
947
+ st.error(f"❌ {analyzed_image}")
948
+ logger.error(f"Analysis failed: {analyzed_image}")
949
+ else:
950
+ st.error("❌ Failed to process media. Please try again.")
951
+ with col2:
952
+ if 'test_results' in st.session_state:
953
+ st.markdown("""
954
+ <div class="health-card">
955
+ <h2>πŸ“‹ Health Report</h2>
956
+ </div>
957
+ """, unsafe_allow_html=True)
958
+ patient_data = st.session_state.patient_data
959
+ current_date = datetime.now().strftime("%B %d, %Y %I:%M %p IST") # 10:42 AM IST, Thursday, July 03, 2025
960
+ st.markdown("""
961
+ <div class='metric-card'>
962
+ <p><b>πŸ‘€ Patient:</b> {}</p>
963
+ <p><b>πŸŽ‚ Age:</b> {}</p>
964
+ <p><b>⚧ Gender:</b> {}</p>
965
+ <p><b>πŸ†” ID:</b> {}</p>
966
+ <p><b>πŸ“… Date:</b> {}</p>
967
+ </div>
968
+ """.format(patient_data['name'], patient_data['age'], patient_data['gender'], patient_data['id'], current_date), unsafe_allow_html=True)
969
+ st.markdown("---")
970
+ # Category icons
971
+ category_icons = {
972
+ "Hematology": "🩺",
973
+ "Iron Panel": "πŸ§ͺ",
974
+ "Liver & Kidney": "πŸ«€",
975
+ "Electrolytes": "⚑",
976
+ "Vitals": "πŸ“ˆ"
977
+ }
978
+ for category, tests in st.session_state.test_results.items():
979
+ st.subheader(f"{category_icons.get(category, 'πŸ“‹')} {category}")
980
+ with st.container():
981
+ st.markdown('<div class="category-container"><div class="table-container">', unsafe_allow_html=True)
982
+ try:
983
+ table_data = []
984
+ for test_name, result, range_val, level_info in tests:
985
+ level, color = level_info
986
+ status_indicator = " L" if level == "Low" else " H" if level == "High" else ""
987
+ if "Count" in test_name or test_name == "Respiratory Rate":
988
+ value_str = f"{result:.0f}{status_indicator}"
989
+ elif test_name in ["Temperature", "SpO2"]:
990
+ value_str = f"{result:.1f}{status_indicator}"
991
+ else:
992
+ value_str = f"{result:.1f}{status_indicator}"
993
+ unit = "" if "BP" in test_name else ("g/dL" if "Hemoglobin" in test_name else
994
+ "cu/mm" if "WBC Count" in test_name else
995
+ "Thousand/Β΅L" if "Platelet Count" in test_name else
996
+ "Β΅g/dL" if "Iron" in test_name or "TIBC" in test_name else
997
+ "ng/mL" if "Ferritin" in test_name else
998
+ "mg/dL" if "Bilirubin" in test_name or "Creatinine" in test_name or "Urea" in test_name else
999
+ "mEq/L" if "Sodium" in test_name or "Potassium" in test_name else
1000
+ "%" if "SpO2" in test_name else
1001
+ "bpm" if "Heart Rate" in test_name else
1002
+ "/min" if "Respiratory Rate" in test_name else
1003
+ "Β°F" if "Temperature" in test_name else "mmHg")
1004
+ range_str = f"{range_val[0]:.0f} - {range_val[1]:.0f}" if "Count" in test_name or test_name == "Respiratory Rate" else f"{range_val[0]:.1f} - {range_val[1]:.1f}"
1005
+ table_data.append({
1006
+ "Test Description": f"{category_icons.get(category, 'πŸ“‹')} {test_name}",
1007
+ "Value Observed": value_str,
1008
+ "Unit": unit,
1009
+ "Biological Reference Interval": range_str,
1010
+ "_color": color
1011
+ })
1012
+ df = pd.DataFrame(table_data)
1013
+ df_display = df.drop(columns=['_color'])
1014
+ def style_row(row):
1015
+ idx = row.name
1016
+ color = df.loc[idx, '_color']
1017
+ return [
1018
+ 'text-align: left; padding-left: 0.7rem;',
1019
+ f'color: {color}; text-align: center;',
1020
+ 'text-align: center;',
1021
+ 'text-align: center;'
1022
+ ]
1023
+ styled_df = df_display.style.set_properties(**{
1024
+ 'font-family': 'Helvetica',
1025
+ 'font-size': '12px',
1026
+ 'border': '1px solid #CCCCCC'
1027
+ }).apply(style_row, axis=1).set_table_styles([
1028
+ {'selector': 'th', 'props': [
1029
+ ('background-color', '#2E8B57'),
1030
+ ('color', 'white'),
1031
+ ('font-weight', 'bold'),
1032
+ ('font-size', '14px'),
1033
+ ('padding', '0.5rem'),
1034
+ ('border', '1px solid #228B22'),
1035
+ ('text-align', 'center')
1036
+ ]},
1037
+ {'selector': 'th:first-child', 'props': [
1038
+ ('text-align', 'left'),
1039
+ ('padding-left', '0.7rem')
1040
+ ]},
1041
+ {'selector': 'tr:nth-child(even)', 'props': [
1042
+ ('background-color', '#F8F8FF')
1043
+ ]},
1044
+ {'selector': 'tr:nth-child(odd)', 'props': [
1045
+ ('background-color', '#F0F8FF')
1046
+ ]},
1047
+ {'selector': 'tr:hover', 'props': [
1048
+ ('background-color', '#E0E0FF')
1049
+ ]}
1050
+ ])
1051
+ st.dataframe(styled_df, use_container_width=True)
1052
+ except Exception as e:
1053
+ st.error(f"Failed to render table for {category}: {str(e)}")
1054
+ logger.error(f"Table rendering failed for {category}: {str(e)}")
1055
+ st.table(table_data)
1056
+ st.markdown('</div></div>', unsafe_allow_html=True)
1057
+ st.markdown("---")
1058
+ if st.button("Download PDF Report"):
1059
+ with st.spinner("Generating PDF..."):
1060
+ try:
1061
+ pdf_buffer = create_pdf_report(
1062
+ st.session_state.patient_data,
1063
+ st.session_state.test_results,
1064
+ st.session_state.pil_image
1065
+ )
1066
+ if pdf_buffer:
1067
+ st.download_button(
1068
+ label="Download PDF Report",
1069
+ data=pdf_buffer,
1070
+ file_name=f"health_report_{re.sub(r'[^a-zA-Z0-9]', '_', patient_data['name'])}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
1071
+ mime="application/pdf"
1072
+ )
1073
+ st.success("βœ… PDF generated successfully!")
1074
+ logger.info("PDF download button created")
1075
+ else:
1076
+ st.error("❌ Failed to generate PDF.")
1077
+ logger.error("PDF buffer is None")
1078
+ except Exception as e:
1079
+ st.error(f"❌ Error generating PDF: {str(e)}")
1080
+ logger.error(f"PDF generation error: {str(e)}")
1081
+ # Help Report Section with Patient Photo and X-ray Analysis
1082
+ if 'pil_image' in st.session_state:
1083
+ st.markdown("""
1084
+ <div class="help-report">
1085
+ <h2>ℹ️ Help Report</h2>
1086
+ <div class="help-report-content">
1087
+ <img src="data:image/png;base64,{}" alt="Patient Photo">
1088
+ <div class="help-report-details">
1089
+ <p><b>πŸ‘€ Name:</b> {}</p>
1090
+ <p><b>πŸŽ‚ Age:</b> {}</p>
1091
+ <p><b>⚧ Gender:</b> {}</p>
1092
+ <p><b>πŸ†” ID:</b> {}</p>
1093
+ <p><b>πŸ“… Date:</b> {}</p>
1094
+ </div>
1095
+ </div>
1096
+ </div>
1097
+ """.format(
1098
+ base64.b64encode(st.session_state.pil_image.tobytes()).decode(),
1099
+ st.session_state.patient_data['name'],
1100
+ st.session_state.patient_data['age'],
1101
+ st.session_state.patient_data['gender'],
1102
+ st.session_state.patient_data['id'],
1103
+ current_date
1104
+ ), unsafe_allow_html=True)
1105
+ # X-ray Analysis
1106
+ if st.button("Analyze X-ray"):
1107
+ with st.spinner("Analyzing X-ray..."):
1108
+ summary, detailed_results, additional_feedback = predict_xray(st.session_state.pil_image)
1109
+ st.markdown(f'<div class="xray-analysis"><h3>X-ray Diagnosis</h3>{summary}</div>', unsafe_allow_html=True)
1110
+ st.markdown(f'<div class="xray-analysis">{detailed_results}</div>', unsafe_allow_html=True)
1111
+ st.markdown(f'<div class="xray-analysis"><p><b>Additional Feedback:</b> {additional_feedback}</p></div>', unsafe_allow_html=True)
1112
 
1113
+ if __name__ == "__main__":
1114
+ main()