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 |
}
|