VaneshDev commited on
Commit
11ecb78
·
verified ·
1 Parent(s): 32e6860

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -1066
app.py CHANGED
@@ -1,790 +1,14 @@
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([
790
  transforms.Resize((224, 224)),
@@ -792,323 +16,136 @@ def preprocess_image(image):
792
  ])
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"]
805
  results = {conditions[i]: float(probs[i]) for i in range(len(conditions))}
806
-
 
807
  most_likely_condition = max(results, key=results.get)
808
- confidence = results[most_likely_condition] * 100
809
-
 
810
  summary = f"**Summary**: Based on the X-ray analysis, the most likely diagnosis is: <b>{most_likely_condition}</b> with a confidence of <b>{confidence:.2f}%</b>."
811
-
 
812
  condition_details = {
813
  "Normal": {
814
  "description": "The X-ray shows no abnormal signs, and the lungs appear healthy.",
815
  "recommendation": "No further tests are required. Continue with regular health check-ups."
816
  },
817
  "Pneumonia": {
818
- "description": "Pneumonia is an infection that causes inflammation in the lungs.",
819
- "recommendation": "Consult a healthcare provider for treatment."
820
  },
821
  "Cancer": {
822
- "description": "Lung cancer may appear as abnormal growths in the lungs.",
823
- "recommendation": "Consult an oncologist for further diagnostic procedures."
824
  },
825
  "TB": {
826
- "description": "Tuberculosis is a bacterial infection that affects the lungs.",
827
- "recommendation": "Seek immediate medical attention for treatment."
828
  },
829
  "Other": {
830
- "description": "There may be other conditions requiring investigation.",
831
- "recommendation": "Consult a radiologist for further analysis."
832
  }
833
  }
834
-
 
835
  detailed_results = "<ul>"
836
  for condition, prob in results.items():
837
  detailed_results += f"<li><b>{condition}:</b> {prob*100:.2f}%</li>"
838
  detailed_results += "</ul>"
839
-
840
- additional_feedback = condition_details.get(most_likely_condition, "Consult a doctor for more details.")
841
-
 
842
  return summary, detailed_results, additional_feedback
843
 
 
844
  def analyze_report(file):
845
  text = ""
846
  if file.name.endswith(".pdf"):
847
  pdf_reader = PyPDF2.PdfReader(file)
848
  for page in pdf_reader.pages:
849
  text += page.extract_text()
 
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()
 
1
+ import gradio as gr
2
+ from PIL import Image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import torch
4
  from torchvision import models, transforms
5
+ import PyPDF2 # For reading patient reports (PDFs)
6
 
7
+ # Load the pre-trained model (ResNet18 with new weight parameter)
8
+ model = models.resnet18(weights="IMAGENET1K_V1") # Updated to the new weight parameter in torchvision 0.13+
9
+ model.eval()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ # Define image preprocessing function
12
  def preprocess_image(image):
13
  transform = transforms.Compose([
14
  transforms.Resize((224, 224)),
 
16
  ])
17
  return transform(image).unsqueeze(0)
18
 
19
+ # Define a prediction function for X-ray images with detailed output
20
  def predict_xray(image):
 
 
 
21
  image_tensor = preprocess_image(image)
22
  with torch.no_grad():
23
+ outputs = model(image_tensor)
24
  probs = torch.nn.functional.softmax(outputs[0], dim=0)
25
+
26
+ # Define the conditions (replace these with the actual conditions your model predicts)
27
  conditions = ["Normal", "Pneumonia", "Cancer", "TB", "Other"]
28
  results = {conditions[i]: float(probs[i]) for i in range(len(conditions))}
29
+
30
+ # Determine the most likely condition and provide detailed feedback
31
  most_likely_condition = max(results, key=results.get)
32
+ confidence = results[most_likely_condition] * 100 # Convert to percentage
33
+
34
+ # Provide a more detailed summary of the results
35
  summary = f"**Summary**: Based on the X-ray analysis, the most likely diagnosis is: <b>{most_likely_condition}</b> with a confidence of <b>{confidence:.2f}%</b>."
36
+
37
+ # Additional detailed descriptions and recommendations for each condition
38
  condition_details = {
39
  "Normal": {
40
  "description": "The X-ray shows no abnormal signs, and the lungs appear healthy.",
41
  "recommendation": "No further tests are required. Continue with regular health check-ups."
42
  },
43
  "Pneumonia": {
44
+ "description": "Pneumonia is an infection that causes inflammation in the lungs. It can be caused by bacteria, viruses, or fungi.",
45
+ "recommendation": "Consult a healthcare provider immediately for possible antibiotics and further treatment."
46
  },
47
  "Cancer": {
48
+ "description": "Lung cancer can manifest in the form of abnormal growths in the lungs. It may require more advanced diagnostic tools like biopsies.",
49
+ "recommendation": "Consult an oncologist for further tests and treatment options."
50
  },
51
  "TB": {
52
+ "description": "Tuberculosis (TB) is a serious bacterial infection that mainly affects the lungs. It is treatable but requires proper care.",
53
+ "recommendation": "Seek immediate medical attention for a proper treatment plan, which may include antibiotics."
54
  },
55
  "Other": {
56
+ "description": "The X-ray may show signs of other possible conditions that need further investigation.",
57
+ "recommendation": "Consult with a doctor to rule out any other serious conditions."
58
  }
59
  }
60
+
61
+ # Displaying the results in a structured way (bullet points)
62
  detailed_results = "<ul>"
63
  for condition, prob in results.items():
64
  detailed_results += f"<li><b>{condition}:</b> {prob*100:.2f}%</li>"
65
  detailed_results += "</ul>"
66
+
67
+ # Additional advice based on the most likely condition
68
+ additional_feedback = condition_details.get(most_likely_condition, "Please consult with a doctor for further details.")
69
+
70
  return summary, detailed_results, additional_feedback
71
 
72
+ # Define a function to read and analyze patient reports (PDFs)
73
  def analyze_report(file):
74
  text = ""
75
  if file.name.endswith(".pdf"):
76
  pdf_reader = PyPDF2.PdfReader(file)
77
  for page in pdf_reader.pages:
78
  text += page.extract_text()
79
+ # For simplicity, we are just summarizing the first 300 characters
80
  report_summary = f"Patient Report (Preview): {text[:300]}..."
81
  return report_summary
82
 
83
+ # Gradio Interface with enhanced UI
84
+ def create_interface():
85
+ with gr.Blocks() as demo:
86
+ # Custom CSS for UI
87
+ custom_css = """
88
+ .gradio-container {
89
+ background-color: #f4f6f9;
90
+ border-radius: 15px;
91
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
92
+ padding: 30px;
93
+ font-family: 'Segoe UI', sans-serif;
94
+ }
95
+ .title {
96
+ font-size: 30px;
97
+ text-align: center;
98
+ color: #4C6A92;
99
+ }
100
+ .gradio-button {
101
+ background-color: #3B82F6;
102
+ color: white;
103
+ border-radius: 10px;
104
+ padding: 15px;
105
+ }
106
+ .result-box {
107
+ background-color: #ffffff;
108
+ border-radius: 10px;
109
+ padding: 20px;
110
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
111
+ margin-top: 20px;
112
+ }
113
+ .result-list {
114
+ padding-left: 20px;
115
+ }
116
+ .result-summary {
117
+ font-size: 18px;
118
+ color: #2F4F4F;
119
+ }
120
+ """
121
+
122
+ # Title section
123
+ gr.Markdown("<h1 class='title'>RadiologyScan AI</h1>")
124
+
125
+ # Upload X-ray image section
126
+ with gr.Row():
127
+ xray_input = gr.Image(label="Upload Chest X-ray", type="pil")
128
+ report_input = gr.File(label="Upload Patient Report (PDF)", file_count="single")
129
+
130
+ # Buttons for analysis
131
+ with gr.Row():
132
+ predict_button = gr.Button("Analyze X-ray", elem_classes="gradio-button")
133
+ report_button = gr.Button("Analyze Report", elem_classes="gradio-button")
134
+
135
+ # Results section for the X-ray image
136
+ xray_output = gr.HTML(label="X-ray Diagnosis Summary", elem_classes="result-box") # Removed interactive=False
137
+ xray_result = gr.HTML(label="Detailed X-ray Results", elem_classes="result-box") # Removed interactive=False
138
+ additional_feedback = gr.Textbox(label="Additional Feedback", interactive=False, elem_classes="result-box")
139
+
140
+ # Results section for the patient report
141
+ report_output = gr.Textbox(label="Report Summary", interactive=False, elem_classes="result-box")
142
+
143
+ # Event handlers for buttons
144
+ predict_button.click(predict_xray, inputs=xray_input, outputs=[xray_output, xray_result, additional_feedback])
145
+ report_button.click(analyze_report, inputs=report_input, outputs=report_output)
146
+
147
+ return demo
148
+
149
+ # Launch the Gradio interface
150
+ demo = create_interface()
151
+ demo.launch(share=True)