Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -9,18 +9,21 @@ import base64
|
|
9 |
import joblib
|
10 |
from datetime import datetime
|
11 |
import shutil
|
12 |
-
import pdfkit
|
13 |
-
import atexit
|
14 |
import glob
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
# Cleanup temporary files on exit
|
17 |
def cleanup_temp_files():
|
18 |
-
for temp_file in glob.glob("
|
19 |
try:
|
20 |
os.remove(temp_file)
|
21 |
except Exception as e:
|
22 |
print(f"Failed to clean up {temp_file}: {e}")
|
23 |
-
for temp_file in glob.glob("
|
24 |
try:
|
25 |
os.remove(temp_file)
|
26 |
except Exception as e:
|
@@ -95,159 +98,105 @@ models = {
|
|
95 |
def get_risk_color(value, normal_range):
|
96 |
low, high = normal_range
|
97 |
if value < low:
|
98 |
-
return ("Low", "🔻",
|
99 |
elif value > high:
|
100 |
-
return ("High", "🔺",
|
101 |
else:
|
102 |
-
return ("Normal", "✅",
|
103 |
-
|
104 |
-
# Function to build table for
|
105 |
-
def
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
}
|
111 |
-
|
112 |
-
html = (
|
113 |
-
f'<div style="margin-bottom: 25px; border-radius: 8px; overflow: hidden; border: 1px solid #e0e0e0;">'
|
114 |
-
f'<div style="background: linear-gradient(135deg, #f5f7fa, #c3cfe2); padding: 12px 16px; border-bottom: 1px solid #e0e0e0;">'
|
115 |
-
f'<h4 style="margin: 0; color: #2c3e50; font-size: 16px; font-weight: 600;">{title}</h4>'
|
116 |
-
f'</div>'
|
117 |
-
f'<table style="width:100%; border-collapse:collapse; background: white;">'
|
118 |
-
f'<thead><tr style="background:#f8f9fa;"><th style="padding:12px 8px;border-bottom:2px solid #dee2e6;color:#495057;font-weight:600;text-align:left;font-size:13px;">Test</th><th style="padding:12px 8px;border-bottom:2px solid #dee2e6;color:#495057;font-weight:600;text-align:center;font-size:13px;">Result</th><th style="padding:12px 8px;border-bottom:2px solid #dee2e6;color:#495057;font-weight:600;text-align:center;font-size:13px;">Range</th><th style="padding:12px 8px;border-bottom:2px solid #dee2e6;color:#495057;font-weight:600;text-align:center;font-size:13px;">Level</th></tr></thead><tbody>'
|
119 |
-
)
|
120 |
-
for i, (label, value, ref) in enumerate(rows):
|
121 |
-
level, icon, bg = get_risk_color(value, ref)
|
122 |
-
row_bg = "#f8f9fa" if i % 2 == 0 else "white"
|
123 |
-
if level != "Normal":
|
124 |
-
row_bg = bg
|
125 |
-
|
126 |
if "Count" in label or "Platelet" in label:
|
127 |
value_str = f"{value:.0f}"
|
128 |
else:
|
129 |
value_str = f"{value:.2f}"
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
|
|
|
|
|
|
136 |
def save_results_to_pdf(profile_image_base64, test_results, summary, patient_name, patient_age, patient_gender, patient_id, filename):
|
137 |
try:
|
138 |
-
#
|
139 |
-
|
140 |
-
|
141 |
-
<!DOCTYPE html>
|
142 |
-
<html>
|
143 |
-
<head>
|
144 |
-
<style>
|
145 |
-
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 700px; margin: 20px auto; color: #1a1a1a; }}
|
146 |
-
#health-card {{ border-radius: 16px; background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%); border: 2px solid #ddd; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); padding: 30px; }}
|
147 |
-
.header {{ background-color: rgba(255, 255, 255, 0.9); border-radius: 12px; padding: 20px; margin-bottom: 25px; border: 1px solid #e0e0e0; display: flex; align-items: center; }}
|
148 |
-
.header-title {{ background: linear-gradient(135deg, #64b5f6, #42a5f5); padding: 8px 16px; border-radius: 8px; margin-right: 20px; }}
|
149 |
-
.header-title h3 {{ margin: 0; color: white; font-size: 16px; font-weight: 600; }}
|
150 |
-
.header-date {{ margin-left: auto; text-align: right; color: #666; font-size: 12px; }}
|
151 |
-
.profile {{ display: flex; align-items: center; }}
|
152 |
-
.profile img {{ 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); }}
|
153 |
-
.results {{ background-color: rgba(255, 255, 255, 0.95); border-radius: 12px; padding: 25px; margin-bottom: 25px; border: 1px solid #e0e0e0; }}
|
154 |
-
.summary {{ background-color: rgba(255, 255, 255, 0.95); padding: 20px; border-radius: 12px; border: 1px solid #e0e0e0; margin-bottom: 25px; }}
|
155 |
-
.summary h4 {{ margin: 0 0 15px 0; color: #2c3e50; font-size: 18px; font-weight: 600; }}
|
156 |
-
.buttons {{ display: flex; gap: 15px; justify-content: center; flex-wrap: wrap; }}
|
157 |
-
button:disabled {{ padding: 12px 24px; background: #ccc; color: white; border: none; border-radius: 8px; cursor: not-allowed; font-weight: 600; font-size: 14px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); }}
|
158 |
-
button {{ padding: 12px 24px; background: linear-gradient(135deg, #2196f3, #1976d2); color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px; box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3); }}
|
159 |
-
@media print {{ .gradio-container {{ display: block; }} #health-card {{ display: block; }} }}
|
160 |
-
</style>
|
161 |
-
</head>
|
162 |
-
<body>
|
163 |
-
<div id="health-card">
|
164 |
-
<div class="header">
|
165 |
-
<div class="header-title"><h3>HEALTH CARD</h3></div>
|
166 |
-
<div class="header-date">
|
167 |
-
<div>Report Date: {current_date}</div>
|
168 |
-
{f'<div>Patient ID: {patient_id}</div>' if patient_id else ''}
|
169 |
-
</div>
|
170 |
-
</div>
|
171 |
-
<div class="profile">
|
172 |
-
<img src="data:image/png;base64,{profile_image_base64}" alt="Profile">
|
173 |
-
<div>
|
174 |
-
<h2>{patient_name if patient_name else 'Lab Test Results'}</h2>
|
175 |
-
<p>{f'Age: {patient_age} | Gender: {patient_gender}' if patient_age and patient_gender else 'AI-Generated Health Analysis'}</p>
|
176 |
-
<p>Face-Based Health Analysis Report</p>
|
177 |
-
</div>
|
178 |
-
</div>
|
179 |
-
<div class="results">
|
180 |
-
{test_results['Hematology']}
|
181 |
-
{test_results['Iron Panel']}
|
182 |
-
{test_results['Liver & Kidney']}
|
183 |
-
{test_results['Electrolytes']}
|
184 |
-
{test_results['Vitals']}
|
185 |
-
</div>
|
186 |
-
<div class="summary">
|
187 |
-
<h4>📝 Summary & Recommendations</h4>
|
188 |
-
<div>{summary}</div>
|
189 |
-
</div>
|
190 |
-
<div class="buttons">
|
191 |
-
<button disabled>📥 Download Report</button>
|
192 |
-
<button>📞 Find Labs Near Me</button>
|
193 |
-
</div>
|
194 |
-
</div>
|
195 |
-
</body>
|
196 |
-
</html>
|
197 |
-
"""
|
198 |
|
199 |
-
#
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
|
235 |
# Move to /tmp for Gradio access
|
236 |
temp_pdf_path = "/tmp/" + os.path.basename(filename)
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
return "Error: PDF file not accessible.", None
|
243 |
-
except Exception as e:
|
244 |
-
print(f"Error moving PDF: {e}")
|
245 |
-
return f"Error: Failed to move PDF - {str(e)}", None
|
246 |
except Exception as e:
|
247 |
-
print(f"
|
248 |
-
return f"Error:
|
249 |
|
250 |
-
# Build health card layout
|
251 |
def build_health_card(profile_image, test_results, summary, pdf_filepath, patient_name="", patient_age="", patient_gender="", patient_id=""):
|
252 |
current_date = datetime.now().strftime("%B %d, %Y %I:%M %p IST")
|
253 |
|
@@ -305,7 +254,7 @@ def build_health_card(profile_image, test_results, summary, pdf_filepath, patien
|
|
305 |
/* Hide input sections during print */
|
306 |
.gradio-container {{ display: block; }}
|
307 |
/* Keep only the health card visible */
|
308 |
-
#health-card {{ display: block; }}
|
309 |
}}
|
310 |
</style>
|
311 |
"""
|
@@ -366,23 +315,23 @@ def analyze_face(input_data):
|
|
366 |
})
|
367 |
|
368 |
test_results = {
|
369 |
-
"Hematology":
|
370 |
-
|
371 |
-
|
372 |
-
"Iron Panel":
|
373 |
-
|
374 |
-
|
375 |
-
"Liver & Kidney":
|
376 |
-
|
377 |
-
|
378 |
-
"Electrolytes":
|
379 |
-
|
380 |
-
"Vitals":
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
}
|
387 |
|
388 |
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>"
|
|
|
9 |
import joblib
|
10 |
from datetime import datetime
|
11 |
import shutil
|
|
|
|
|
12 |
import glob
|
13 |
+
from reportlab.lib import colors
|
14 |
+
from reportlab.lib.pagesizes import letter
|
15 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
16 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
17 |
+
import atexit
|
18 |
|
19 |
# Cleanup temporary files on exit
|
20 |
def cleanup_temp_files():
|
21 |
+
for temp_file in glob.glob("Health_Report_*.pdf"):
|
22 |
try:
|
23 |
os.remove(temp_file)
|
24 |
except Exception as e:
|
25 |
print(f"Failed to clean up {temp_file}: {e}")
|
26 |
+
for temp_file in glob.glob("health_card*.html"):
|
27 |
try:
|
28 |
os.remove(temp_file)
|
29 |
except Exception as e:
|
|
|
98 |
def get_risk_color(value, normal_range):
|
99 |
low, high = normal_range
|
100 |
if value < low:
|
101 |
+
return ("Low", "🔻", colors.yellow)
|
102 |
elif value > high:
|
103 |
+
return ("High", "🔺", colors.red)
|
104 |
else:
|
105 |
+
return ("Normal", "✅", colors.green)
|
106 |
+
|
107 |
+
# Function to build table data for PDF
|
108 |
+
def build_table_data(title, rows):
|
109 |
+
data = [[Paragraph(title, getSampleStyleSheet()['Heading2'])]]
|
110 |
+
data.append(["Test", "Result", "Range", "Level"])
|
111 |
+
for label, value, ref in rows:
|
112 |
+
level, icon, color = get_risk_color(value, ref)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
if "Count" in label or "Platelet" in label:
|
114 |
value_str = f"{value:.0f}"
|
115 |
else:
|
116 |
value_str = f"{value:.2f}"
|
117 |
+
data.append([
|
118 |
+
label,
|
119 |
+
value_str,
|
120 |
+
f"{ref[0]} - {ref[1]}",
|
121 |
+
Paragraph(f"{icon} {level}", ParagraphStyle('Custom', textColor=color))
|
122 |
+
])
|
123 |
+
return data
|
124 |
+
|
125 |
+
# Function to save the health report to PDF using reportlab
|
126 |
def save_results_to_pdf(profile_image_base64, test_results, summary, patient_name, patient_age, patient_gender, patient_id, filename):
|
127 |
try:
|
128 |
+
# Create PDF document
|
129 |
+
pdf = SimpleDocTemplate(filename, pagesize=letter)
|
130 |
+
elements = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
|
132 |
+
# Header
|
133 |
+
styles = getSampleStyleSheet()
|
134 |
+
current_date = datetime.now().strftime("%B %d, %Y %I:%M %p IST")
|
135 |
+
header_text = f"HEALTH CARD - Report Date: {current_date}"
|
136 |
+
if patient_id:
|
137 |
+
header_text += f" | Patient ID: {patient_id}"
|
138 |
+
elements.append(Paragraph(header_text, styles['Title']))
|
139 |
+
elements.append(Spacer(1, 12))
|
140 |
+
|
141 |
+
# Profile Section
|
142 |
+
profile_text = f"{patient_name if patient_name else 'Lab Test Results'}"
|
143 |
+
if patient_age and patient_gender:
|
144 |
+
profile_text += f" | Age: {patient_age} | Gender: {patient_gender}"
|
145 |
+
else:
|
146 |
+
profile_text += " | AI-Generated Health Analysis"
|
147 |
+
profile_text += " | Face-Based Health Analysis Report"
|
148 |
+
elements.append(Paragraph(profile_text, styles['Heading1']))
|
149 |
+
elements.append(Spacer(1, 12))
|
150 |
+
|
151 |
+
# Test Results Tables
|
152 |
+
for title, rows in [
|
153 |
+
("Hematology", test_results['Hematology']),
|
154 |
+
("Iron Panel", test_results['Iron Panel']),
|
155 |
+
("Liver & Kidney", test_results['Liver & Kidney']),
|
156 |
+
("Electrolytes", test_results['Electrolytes']),
|
157 |
+
("Vitals", test_results['Vitals'])
|
158 |
+
]:
|
159 |
+
table_data = build_table_data(title, rows)
|
160 |
+
table = Table(table_data, colWidths=[100, 70, 70, 70])
|
161 |
+
table.setStyle(TableStyle([
|
162 |
+
('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
|
163 |
+
('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
|
164 |
+
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
165 |
+
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
166 |
+
('FONTSIZE', (0, 0), (-1, 0), 12),
|
167 |
+
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
168 |
+
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
169 |
+
('TEXTCOLOR', (0, 1), (-1, -1), colors.black),
|
170 |
+
('ALIGN', (0, 1), (-1, -1), 'LEFT'),
|
171 |
+
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
|
172 |
+
('FONTSIZE', (0, 1), (-1, -1), 10),
|
173 |
+
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
174 |
+
('VALIGN', (0, 0), (-1, -1), 'MIDDLE')
|
175 |
+
]))
|
176 |
+
elements.append(table)
|
177 |
+
elements.append(Spacer(1, 12))
|
178 |
+
|
179 |
+
# Summary
|
180 |
+
elements.append(Paragraph("Summary & Recommendations", styles['Heading2']))
|
181 |
+
elements.append(Paragraph(summary, styles['Normal']))
|
182 |
+
elements.append(Spacer(1, 12))
|
183 |
+
|
184 |
+
# Build PDF
|
185 |
+
pdf.build(elements)
|
186 |
+
print(f"PDF generated successfully at: {filename}")
|
187 |
|
188 |
# Move to /tmp for Gradio access
|
189 |
temp_pdf_path = "/tmp/" + os.path.basename(filename)
|
190 |
+
shutil.copy(filename, temp_pdf_path)
|
191 |
+
if os.path.exists(temp_pdf_path) and os.access(temp_pdf_path, os.R_OK):
|
192 |
+
return f"PDF saved successfully as {filename}", temp_pdf_path
|
193 |
+
else:
|
194 |
+
return "Error: PDF file not accessible.", None
|
|
|
|
|
|
|
|
|
195 |
except Exception as e:
|
196 |
+
print(f"Error generating PDF: {str(e)}")
|
197 |
+
return f"Error: Failed to generate PDF - {str(e)}", None
|
198 |
|
199 |
+
# Build health card layout (unchanged, using HTML for display)
|
200 |
def build_health_card(profile_image, test_results, summary, pdf_filepath, patient_name="", patient_age="", patient_gender="", patient_id=""):
|
201 |
current_date = datetime.now().strftime("%B %d, %Y %I:%M %p IST")
|
202 |
|
|
|
254 |
/* Hide input sections during print */
|
255 |
.gradio-container {{ display: block; }}
|
256 |
/* Keep only the health card visible */
|
257 |
+
#health-card {{ display: block; }}
|
258 |
}}
|
259 |
</style>
|
260 |
"""
|
|
|
315 |
})
|
316 |
|
317 |
test_results = {
|
318 |
+
"Hematology": build_table_data("🩸 Hematology", [("Hemoglobin", test_values["Hemoglobin"], (13.5, 17.5)),
|
319 |
+
("WBC Count", test_values["WBC Count"], (4.0, 11.0)),
|
320 |
+
("Platelet Count", test_values["Platelet Count"], (150, 450))]),
|
321 |
+
"Iron Panel": build_table_data("🧬 Iron Panel", [("Iron", test_values["Iron"], (60, 170)),
|
322 |
+
("Ferritin", test_values["Ferritin"], (30, 300)),
|
323 |
+
("TIBC", test_values["TIBC"], (250, 400))]),
|
324 |
+
"Liver & Kidney": build_table_data("🧬 Liver & Kidney", [("Bilirubin", test_values["Bilirubin"], (0.3, 1.2)),
|
325 |
+
("Creatinine", test_values["Creatinine"], (0.6, 1.2)),
|
326 |
+
("Urea", test_values["Urea"], (7, 20))]),
|
327 |
+
"Electrolytes": build_table_data("🧪 Electrolytes", [("Sodium", test_values["Sodium"], (135, 145)),
|
328 |
+
("Potassium", test_values["Potassium"], (3.5, 5.1))]),
|
329 |
+
"Vitals": build_table_data("❤️ Vitals", [("SpO2", test_values["SpO2"], (95, 100)),
|
330 |
+
("Heart Rate", test_values["Heart Rate"], (60, 100)),
|
331 |
+
("Respiratory Rate", test_values["Respiratory Rate"], (12, 20)),
|
332 |
+
("Temperature", test_values["Temperature"], (97, 99)),
|
333 |
+
("BP Systolic", test_values["BP Systolic"], (90, 120)),
|
334 |
+
("BP Diastolic", test_values["BP Diastolic"], (60, 80))])
|
335 |
}
|
336 |
|
337 |
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>"
|