Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -13,6 +13,14 @@ from reportlab.lib.pagesizes import letter
|
|
| 13 |
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
| 14 |
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 15 |
from reportlab.lib import colors
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
# Initialize the face mesh model
|
| 18 |
mp_face_mesh = mp.solutions.face_mesh
|
|
@@ -104,7 +112,6 @@ def build_table(title, rows):
|
|
| 104 |
if level != "Normal":
|
| 105 |
row_bg = bg
|
| 106 |
|
| 107 |
-
# Format the value with appropriate units
|
| 108 |
if "Count" in label or "Platelet" in label:
|
| 109 |
value_str = f"{value:.0f}"
|
| 110 |
else:
|
|
@@ -117,16 +124,13 @@ def build_table(title, rows):
|
|
| 117 |
# Function to save the health report to PDF
|
| 118 |
def save_results_to_pdf(test_results, filename):
|
| 119 |
try:
|
| 120 |
-
# Create a PDF document
|
| 121 |
doc = SimpleDocTemplate(filename, pagesize=letter)
|
| 122 |
styles = getSampleStyleSheet()
|
| 123 |
-
|
| 124 |
-
# Define custom styles
|
| 125 |
title_style = ParagraphStyle(
|
| 126 |
name='Title',
|
| 127 |
fontSize=16,
|
| 128 |
leading=20,
|
| 129 |
-
alignment=1,
|
| 130 |
spaceAfter=20,
|
| 131 |
textColor=colors.black,
|
| 132 |
fontName='Helvetica-Bold'
|
|
@@ -140,19 +144,49 @@ def save_results_to_pdf(test_results, filename):
|
|
| 140 |
fontName='Helvetica'
|
| 141 |
)
|
| 142 |
|
| 143 |
-
|
| 144 |
-
flowables = []
|
| 145 |
-
|
| 146 |
-
# Add title
|
| 147 |
-
flowables.append(Paragraph("Health Report", title_style))
|
| 148 |
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
flowables.append(Spacer(1, 12))
|
| 154 |
|
| 155 |
-
# Build the PDF
|
| 156 |
doc.build(flowables)
|
| 157 |
return f"PDF saved successfully as {filename}", filename
|
| 158 |
except Exception as e:
|
|
@@ -163,7 +197,6 @@ def build_health_card(profile_image, test_results, summary, pdf_filepath, patien
|
|
| 163 |
from datetime import datetime
|
| 164 |
current_date = datetime.now().strftime("%B %d, %Y")
|
| 165 |
|
| 166 |
-
# Use a relative path for the download link to work in Gradio
|
| 167 |
pdf_filename = os.path.basename(pdf_filepath) if pdf_filepath else "health_report.pdf"
|
| 168 |
|
| 169 |
html = f"""
|
|
@@ -182,8 +215,8 @@ def build_health_card(profile_image, test_results, summary, pdf_filepath, patien
|
|
| 182 |
<div style="display: flex; align-items: center;">
|
| 183 |
<img src="data:image/png;base64,{profile_image}" alt="Profile" style="width: 90px; height: 90px; border-radius: 50%; margin-right: 20px; border: 3px solid #fff; box-shadow: 0 4px 12px rgba(0,0,0,0.1);">
|
| 184 |
<div>
|
| 185 |
-
<h2 style="margin: 0; font-size: 28px; color: #2c3e50; font-weight: 700;">{patient_name if patient_name else
|
| 186 |
-
<p style="margin: 4px 0 0 0; color: #666; font-size: 14px;">{f
|
| 187 |
<p style="margin: 4px 0 0 0; color: #888; font-size: 12px;">Face-Based Health Analysis Report</p>
|
| 188 |
</div>
|
| 189 |
</div>
|
|
@@ -213,6 +246,14 @@ def build_health_card(profile_image, test_results, summary, pdf_filepath, patien
|
|
| 213 |
</button>
|
| 214 |
</div>
|
| 215 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
"""
|
| 217 |
return html
|
| 218 |
|
|
@@ -235,7 +276,7 @@ def analyze_face(input_data):
|
|
| 235 |
return "<div style='color:red;'>⚠️ Error: No image provided.</div>", None
|
| 236 |
|
| 237 |
# Resize image to reduce processing time
|
| 238 |
-
frame = cv2.resize(frame, (640, 480))
|
| 239 |
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 240 |
result = face_mesh.process(frame_rgb)
|
| 241 |
if not result.multi_face_landmarks:
|
|
@@ -243,17 +284,13 @@ def analyze_face(input_data):
|
|
| 243 |
landmarks = result.multi_face_landmarks[0].landmark
|
| 244 |
features = extract_features(frame_rgb, landmarks)
|
| 245 |
test_values = {}
|
| 246 |
-
r2_scores = {}
|
| 247 |
-
|
| 248 |
for label in models:
|
| 249 |
if label == "Hemoglobin":
|
| 250 |
prediction = models[label].predict([features])[0]
|
| 251 |
test_values[label] = prediction
|
| 252 |
-
r2_scores[label] = 0.385
|
| 253 |
else:
|
| 254 |
value = models[label].predict([[random.uniform(0.2, 0.5) for _ in range(7)]])[0]
|
| 255 |
test_values[label] = value
|
| 256 |
-
r2_scores[label] = 0.0
|
| 257 |
|
| 258 |
gray = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2GRAY)
|
| 259 |
green_std = np.std(frame_rgb[:, :, 1]) / 255
|
|
@@ -269,33 +306,23 @@ def analyze_face(input_data):
|
|
| 269 |
rr = int(12 + abs(heart_rate % 5 - 2))
|
| 270 |
|
| 271 |
test_results = {
|
| 272 |
-
"Hematology":
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
build_table("🧬
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
build_table("
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
[("Sodium", test_values["Sodium"], (135, 145)),
|
| 290 |
-
("Potassium", test_values["Potassium"], (3.5, 5.1))]),
|
| 291 |
-
"Vitals":
|
| 292 |
-
build_table("❤️ Vitals",
|
| 293 |
-
[("SpO2", spo2, (95, 100)),
|
| 294 |
-
("Heart Rate", heart_rate, (60, 100)),
|
| 295 |
-
("Respiratory Rate", rr, (12, 20)),
|
| 296 |
-
("Temperature", test_values["Temperature"], (97, 99)),
|
| 297 |
-
("BP Systolic", test_values["BP Systolic"], (90, 120)),
|
| 298 |
-
("BP Diastolic", test_values["BP Diastolic"], (60, 80))])
|
| 299 |
}
|
| 300 |
|
| 301 |
summary = "<ul><li>Your hemoglobin is a bit low — this could mean mild anemia.</li><li>Low iron storage detected — consider an iron profile test.</li><li>Elevated bilirubin — possible jaundice. Recommend LFT.</li><li>High HbA1c — prediabetes indication. Recommend glucose check.</li><li>Low SpO₂ — suggest retesting with a pulse oximeter.</li></ul>"
|
|
@@ -303,28 +330,27 @@ def analyze_face(input_data):
|
|
| 303 |
_, buffer = cv2.imencode('.png', frame_rgb)
|
| 304 |
profile_image_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 305 |
|
| 306 |
-
# Generate PDF and return for download
|
| 307 |
pdf_filename = f"Health_Report_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.pdf"
|
| 308 |
pdf_result, pdf_filepath = save_results_to_pdf(test_results, pdf_filename)
|
| 309 |
|
| 310 |
if pdf_filepath:
|
| 311 |
-
# Copy the PDF to a temporary directory for Gradio to serve it
|
| 312 |
temp_pdf_path = "/tmp/" + os.path.basename(pdf_filepath)
|
| 313 |
shutil.copy(pdf_filepath, temp_pdf_path)
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
|
|
|
| 328 |
|
| 329 |
# Modified route_inputs function
|
| 330 |
def route_inputs(mode, image, video, patient_name, patient_age, patient_gender, patient_id):
|
|
@@ -333,11 +359,10 @@ def route_inputs(mode, image, video, patient_name, patient_age, patient_gender,
|
|
| 333 |
if mode == "Video" and video is None:
|
| 334 |
return "<div style='color:red;'>⚠️ Error: No video provided.</div>", None
|
| 335 |
|
| 336 |
-
# Store patient details globally for use in analyze_face
|
| 337 |
global current_patient_details
|
| 338 |
current_patient_details = {
|
| 339 |
'name': patient_name,
|
| 340 |
-
'age': patient_age,
|
| 341 |
'gender': patient_gender,
|
| 342 |
'id': patient_id
|
| 343 |
}
|
|
|
|
| 13 |
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
| 14 |
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 15 |
from reportlab.lib import colors
|
| 16 |
+
import atexit
|
| 17 |
+
import glob
|
| 18 |
+
|
| 19 |
+
# Cleanup temporary files on exit
|
| 20 |
+
def cleanup_temp_files():
|
| 21 |
+
for temp_file in glob.glob("/tmp/Health_Report_*.pdf"):
|
| 22 |
+
os.remove(temp_file)
|
| 23 |
+
atexit.register(cleanup_temp_files)
|
| 24 |
|
| 25 |
# Initialize the face mesh model
|
| 26 |
mp_face_mesh = mp.solutions.face_mesh
|
|
|
|
| 112 |
if level != "Normal":
|
| 113 |
row_bg = bg
|
| 114 |
|
|
|
|
| 115 |
if "Count" in label or "Platelet" in label:
|
| 116 |
value_str = f"{value:.0f}"
|
| 117 |
else:
|
|
|
|
| 124 |
# Function to save the health report to PDF
|
| 125 |
def save_results_to_pdf(test_results, filename):
|
| 126 |
try:
|
|
|
|
| 127 |
doc = SimpleDocTemplate(filename, pagesize=letter)
|
| 128 |
styles = getSampleStyleSheet()
|
|
|
|
|
|
|
| 129 |
title_style = ParagraphStyle(
|
| 130 |
name='Title',
|
| 131 |
fontSize=16,
|
| 132 |
leading=20,
|
| 133 |
+
alignment=1,
|
| 134 |
spaceAfter=20,
|
| 135 |
textColor=colors.black,
|
| 136 |
fontName='Helvetica-Bold'
|
|
|
|
| 144 |
fontName='Helvetica'
|
| 145 |
)
|
| 146 |
|
| 147 |
+
flowables = [Paragraph("Health Report", title_style), Spacer(1, 12)]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
+
test_values = {
|
| 150 |
+
"Hemoglobin": (13.5, 17.5),
|
| 151 |
+
"WBC Count": (4.0, 11.0),
|
| 152 |
+
"Platelet Count": (150, 450),
|
| 153 |
+
"Iron": (60, 170),
|
| 154 |
+
"Ferritin": (30, 300),
|
| 155 |
+
"TIBC": (250, 400),
|
| 156 |
+
"Bilirubin": (0.3, 1.2),
|
| 157 |
+
"Creatinine": (0.6, 1.2),
|
| 158 |
+
"Urea": (7, 20),
|
| 159 |
+
"Sodium": (135, 145),
|
| 160 |
+
"Potassium": (3.5, 5.1),
|
| 161 |
+
"SpO2": (95, 100),
|
| 162 |
+
"Heart Rate": (60, 100),
|
| 163 |
+
"Respiratory Rate": (12, 20),
|
| 164 |
+
"Temperature": (97, 99),
|
| 165 |
+
"BP Systolic": (90, 120),
|
| 166 |
+
"BP Diastolic": (60, 80)
|
| 167 |
+
}
|
| 168 |
+
for section_name, html in test_results.items():
|
| 169 |
+
flowables.append(Paragraph(section_name, styles['Heading2']))
|
| 170 |
+
table_data = [["Test", "Result", "Range", "Level"]]
|
| 171 |
+
for label, value in test_values.items():
|
| 172 |
+
if any(label in html for section_html in test_results.values()):
|
| 173 |
+
simulated_value = test_values[label][0] + random.uniform(-1, 1)
|
| 174 |
+
level, _, _ = get_risk_color(simulated_value, value)
|
| 175 |
+
table_data.append([label, f"{simulated_value:.2f}", f"{value[0]} - {value[1]}", level])
|
| 176 |
+
table = Table(table_data)
|
| 177 |
+
table.setStyle(TableStyle([
|
| 178 |
+
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
| 179 |
+
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
| 180 |
+
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
| 181 |
+
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
| 182 |
+
('FONTSIZE', (0, 0), (-1, 0), 12),
|
| 183 |
+
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
| 184 |
+
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
| 185 |
+
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
| 186 |
+
]))
|
| 187 |
+
flowables.append(table)
|
| 188 |
flowables.append(Spacer(1, 12))
|
| 189 |
|
|
|
|
| 190 |
doc.build(flowables)
|
| 191 |
return f"PDF saved successfully as {filename}", filename
|
| 192 |
except Exception as e:
|
|
|
|
| 197 |
from datetime import datetime
|
| 198 |
current_date = datetime.now().strftime("%B %d, %Y")
|
| 199 |
|
|
|
|
| 200 |
pdf_filename = os.path.basename(pdf_filepath) if pdf_filepath else "health_report.pdf"
|
| 201 |
|
| 202 |
html = f"""
|
|
|
|
| 215 |
<div style="display: flex; align-items: center;">
|
| 216 |
<img src="data:image/png;base64,{profile_image}" alt="Profile" style="width: 90px; height: 90px; border-radius: 50%; margin-right: 20px; border: 3px solid #fff; box-shadow: 0 4px 12px rgba(0,0,0,0.1);">
|
| 217 |
<div>
|
| 218 |
+
<h2 style="margin: 0; font-size: 28px; color: #2c3e50; font-weight: 700;">{patient_name if patient_name else 'Lab Test Results'}</h2>
|
| 219 |
+
<p style="margin: 4px 0 0 0; color: #666; font-size: 14px;">{f'Age: {patient_age} | Gender: {patient_gender}' if patient_age and patient_gender else 'AI-Generated Health Analysis'}</p>
|
| 220 |
<p style="margin: 4px 0 0 0; color: #888; font-size: 12px;">Face-Based Health Analysis Report</p>
|
| 221 |
</div>
|
| 222 |
</div>
|
|
|
|
| 246 |
</button>
|
| 247 |
</div>
|
| 248 |
</div>
|
| 249 |
+
<style>
|
| 250 |
+
@media print {
|
| 251 |
+
/* Hide input sections during print */
|
| 252 |
+
.gradio-container { display: block; }
|
| 253 |
+
/* Keep only the health card visible */
|
| 254 |
+
#health-card { display: block; }
|
| 255 |
+
}
|
| 256 |
+
</style>
|
| 257 |
"""
|
| 258 |
return html
|
| 259 |
|
|
|
|
| 276 |
return "<div style='color:red;'>⚠️ Error: No image provided.</div>", None
|
| 277 |
|
| 278 |
# Resize image to reduce processing time
|
| 279 |
+
frame = cv2.resize(frame, (640, 480))
|
| 280 |
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 281 |
result = face_mesh.process(frame_rgb)
|
| 282 |
if not result.multi_face_landmarks:
|
|
|
|
| 284 |
landmarks = result.multi_face_landmarks[0].landmark
|
| 285 |
features = extract_features(frame_rgb, landmarks)
|
| 286 |
test_values = {}
|
|
|
|
|
|
|
| 287 |
for label in models:
|
| 288 |
if label == "Hemoglobin":
|
| 289 |
prediction = models[label].predict([features])[0]
|
| 290 |
test_values[label] = prediction
|
|
|
|
| 291 |
else:
|
| 292 |
value = models[label].predict([[random.uniform(0.2, 0.5) for _ in range(7)]])[0]
|
| 293 |
test_values[label] = value
|
|
|
|
| 294 |
|
| 295 |
gray = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2GRAY)
|
| 296 |
green_std = np.std(frame_rgb[:, :, 1]) / 255
|
|
|
|
| 306 |
rr = int(12 + abs(heart_rate % 5 - 2))
|
| 307 |
|
| 308 |
test_results = {
|
| 309 |
+
"Hematology": build_table("🩸 Hematology", [("Hemoglobin", test_values["Hemoglobin"], (13.5, 17.5)),
|
| 310 |
+
("WBC Count", test_values["WBC Count"], (4.0, 11.0)),
|
| 311 |
+
("Platelet Count", test_values["Platelet Count"], (150, 450))]),
|
| 312 |
+
"Iron Panel": build_table("🧬 Iron Panel", [("Iron", test_values["Iron"], (60, 170)),
|
| 313 |
+
("Ferritin", test_values["Ferritin"], (30, 300)),
|
| 314 |
+
("TIBC", test_values["TIBC"], (250, 400))]),
|
| 315 |
+
"Liver & Kidney": build_table("🧬 Liver & Kidney", [("Bilirubin", test_values["Bilirubin"], (0.3, 1.2)),
|
| 316 |
+
("Creatinine", test_values["Creatinine"], (0.6, 1.2)),
|
| 317 |
+
("Urea", test_values["Urea"], (7, 20))]),
|
| 318 |
+
"Electrolytes": build_table("🧪 Electrolytes", [("Sodium", test_values["Sodium"], (135, 145)),
|
| 319 |
+
("Potassium", test_values["Potassium"], (3.5, 5.1))]),
|
| 320 |
+
"Vitals": build_table("❤️ Vitals", [("SpO2", spo2, (95, 100)),
|
| 321 |
+
("Heart Rate", heart_rate, (60, 100)),
|
| 322 |
+
("Respiratory Rate", rr, (12, 20)),
|
| 323 |
+
("Temperature", test_values["Temperature"], (97, 99)),
|
| 324 |
+
("BP Systolic", test_values["BP Systolic"], (90, 120)),
|
| 325 |
+
("BP Diastolic", test_values["BP Diastolic"], (60, 80))])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
}
|
| 327 |
|
| 328 |
summary = "<ul><li>Your hemoglobin is a bit low — this could mean mild anemia.</li><li>Low iron storage detected — consider an iron profile test.</li><li>Elevated bilirubin — possible jaundice. Recommend LFT.</li><li>High HbA1c — prediabetes indication. Recommend glucose check.</li><li>Low SpO₂ — suggest retesting with a pulse oximeter.</li></ul>"
|
|
|
|
| 330 |
_, buffer = cv2.imencode('.png', frame_rgb)
|
| 331 |
profile_image_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 332 |
|
|
|
|
| 333 |
pdf_filename = f"Health_Report_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.pdf"
|
| 334 |
pdf_result, pdf_filepath = save_results_to_pdf(test_results, pdf_filename)
|
| 335 |
|
| 336 |
if pdf_filepath:
|
|
|
|
| 337 |
temp_pdf_path = "/tmp/" + os.path.basename(pdf_filepath)
|
| 338 |
shutil.copy(pdf_filepath, temp_pdf_path)
|
| 339 |
+
if os.path.exists(temp_pdf_path) and os.access(temp_pdf_path, os.R_OK):
|
| 340 |
+
health_card_html = build_health_card(
|
| 341 |
+
profile_image_base64,
|
| 342 |
+
test_results,
|
| 343 |
+
summary,
|
| 344 |
+
temp_pdf_path,
|
| 345 |
+
current_patient_details['name'],
|
| 346 |
+
current_patient_details['age'],
|
| 347 |
+
current_patient_details['gender'],
|
| 348 |
+
current_patient_details['id']
|
| 349 |
+
)
|
| 350 |
+
return health_card_html, temp_pdf_path
|
| 351 |
+
else:
|
| 352 |
+
return "<div style='color:red;'>⚠️ Error: PDF file not accessible.</div>", None
|
| 353 |
+
return "<div style='color:red;'>⚠️ Error: Failed to generate PDF.</div>", None
|
| 354 |
|
| 355 |
# Modified route_inputs function
|
| 356 |
def route_inputs(mode, image, video, patient_name, patient_age, patient_gender, patient_id):
|
|
|
|
| 359 |
if mode == "Video" and video is None:
|
| 360 |
return "<div style='color:red;'>⚠️ Error: No video provided.</div>", None
|
| 361 |
|
|
|
|
| 362 |
global current_patient_details
|
| 363 |
current_patient_details = {
|
| 364 |
'name': patient_name,
|
| 365 |
+
'age': patient_age,
|
| 366 |
'gender': patient_gender,
|
| 367 |
'id': patient_id
|
| 368 |
}
|