sudarshanpatel commited on
Commit
1a08a8c
·
verified ·
1 Parent(s): df3df95

Upload appp.py

Browse files
Files changed (1) hide show
  1. appp.py +857 -0
appp.py ADDED
@@ -0,0 +1,857 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image
3
+ import os
4
+ import base64
5
+ import io
6
+ from dotenv import load_dotenv
7
+ from groq import Groq
8
+ from reportlab.lib.pagesizes import letter
9
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as ReportLabImage
10
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
11
+ from reportlab.lib.units import inch
12
+ from datetime import datetime
13
+ import re
14
+ from reportlab.lib import colors
15
+ import random
16
+ import streamlit.components.v1 as components
17
+ from openai import OpenAI
18
+
19
+ # ======================
20
+ # CONFIGURATION SETTINGS
21
+ # ======================
22
+ PAGE_CONFIG = {
23
+ "page_title": "Radiology Analyzer",
24
+ "page_icon": "🩺",
25
+ "layout": "wide",
26
+ "initial_sidebar_state": "expanded"
27
+ }
28
+
29
+ ALLOWED_FILE_TYPES = ['png', 'jpg', 'jpeg']
30
+
31
+ CSS_STYLES = """
32
+ <style>
33
+ /* Main background and text colors */
34
+ .main {
35
+ background-color: #0e1117;
36
+ color: #ffffff;
37
+ }
38
+ .sidebar .sidebar-content {
39
+ background-color: #1a1d24;
40
+ color: #ffffff;
41
+ }
42
+
43
+ /* Custom title styling */
44
+ .main-title {
45
+ font-size: 2.8rem;
46
+ font-weight: 700;
47
+ color: #ffffff;
48
+ margin-bottom: 0.2rem;
49
+ text-align: center;
50
+ }
51
+ .sub-title {
52
+ font-size: 1.5rem;
53
+ color: #9ca3af;
54
+ margin-top: 0.2rem;
55
+ text-align: center;
56
+ margin-bottom: 2rem;
57
+ }
58
+
59
+ /* Button styling */
60
+ .stButton>button {
61
+ background-color: #21b9e1 !important;
62
+ color: white !important;
63
+ border-radius: 8px !important;
64
+ padding: 0.5rem 1rem !important;
65
+ border: none !important;
66
+ transition: all 0.3s ease !important;
67
+ }
68
+ .stButton>button:hover {
69
+ background-color: #17a2b8 !important;
70
+ transform: translateY(-2px);
71
+ box-shadow: 0 4px 12px rgba(0, 200, 225, 0.3);
72
+ }
73
+
74
+ /* Report container */
75
+ .report-container {
76
+ background-color: #1a1d24;
77
+ border-radius: 10px;
78
+ padding: 25px;
79
+ margin-top: 20px;
80
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
81
+ border-left: 5px solid #21b9e1;
82
+ }
83
+ .report-text {
84
+ font-family: 'Inter', sans-serif;
85
+ font-size: 14px;
86
+ line-height: 1.6;
87
+ color: #e2e8f0;
88
+ }
89
+
90
+ /* File uploader */
91
+ .uploadedFile {
92
+ background-color: #1a1d24 !important;
93
+ border-radius: 10px !important;
94
+ padding: 10px !important;
95
+ border: 2px dashed #21b9e1 !important;
96
+ }
97
+
98
+ /* Sidebar items */
99
+ .sidebar-item {
100
+ padding: 10px 0;
101
+ border-bottom: 1px solid #2d3748;
102
+ }
103
+ .sidebar-title {
104
+ font-weight: bold;
105
+ color: #21b9e1;
106
+ margin-bottom: 10px;
107
+ }
108
+
109
+ /* Logo container */
110
+ .logo-container {
111
+ display: flex;
112
+ justify-content: center;
113
+ margin-bottom: 20px;
114
+ }
115
+ .logo-pulse {
116
+ width: 100px;
117
+ height: 100px;
118
+ border-radius: 50%;
119
+ animation: pulse 2s infinite;
120
+ display: flex;
121
+ justify-content: center;
122
+ align-items: center;
123
+ background-color: rgba(33, 185, 225, 0.1);
124
+ }
125
+ @keyframes pulse {
126
+ 0% {
127
+ box-shadow: 0 0 0 0 rgba(33, 185, 225, 0.4);
128
+ }
129
+ 70% {
130
+ box-shadow: 0 0 0 20px rgba(33, 185, 225, 0);
131
+ }
132
+ 100% {
133
+ box-shadow: 0 0 0 0 rgba(33, 185, 225, 0);
134
+ }
135
+ }
136
+
137
+ /* Progress bar */
138
+ .stProgress > div > div {
139
+ background-color: #21b9e1 !important;
140
+ }
141
+
142
+ /* Analysis status indicator */
143
+ .analysis-complete {
144
+ display: inline-flex;
145
+ align-items: center;
146
+ background-color: rgba(33, 225, 185, 0.2);
147
+ color: #21e1b9;
148
+ padding: 8px 16px;
149
+ border-radius: 20px;
150
+ font-weight: 600;
151
+ margin-bottom: 20px;
152
+ }
153
+ .analysis-complete svg {
154
+ margin-right: 8px;
155
+ }
156
+
157
+ /* Drop zone */
158
+ .drop-zone {
159
+ background-color: #1a1d24;
160
+ border: 2px dashed #21b9e1;
161
+ border-radius: 10px;
162
+ padding: 40px 20px;
163
+ text-align: center;
164
+ transition: all 0.3s ease;
165
+ margin-bottom: 20px;
166
+ }
167
+ .drop-zone:hover {
168
+ border-color: #17a2b8;
169
+ background-color: #242830;
170
+ }
171
+ .drop-icon {
172
+ font-size: 3rem;
173
+ color: #21b9e1;
174
+ margin-bottom: 10px;
175
+ }
176
+
177
+ /* Markdown adjustments */
178
+ .markdown-text-container p {
179
+ color: #e2e8f0 !important;
180
+ }
181
+
182
+ /* Image styling */
183
+ .stImage img {
184
+ border-radius: 10px;
185
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
186
+ border: 3px solid #1a1d24;
187
+ }
188
+
189
+ /* Card styles */
190
+ .card {
191
+ background-color: #1a1d24;
192
+ border-radius: 10px;
193
+ padding: 20px;
194
+ margin-bottom: 20px;
195
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
196
+ transition: all 0.3s ease;
197
+ }
198
+ .card:hover {
199
+ transform: translateY(-5px);
200
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
201
+ }
202
+ .card-title {
203
+ color: #21b9e1;
204
+ font-size: 1.2rem;
205
+ font-weight: 600;
206
+ margin-bottom: 10px;
207
+ }
208
+
209
+ /* Tooltip */
210
+ .tooltip {
211
+ position: relative;
212
+ display: inline-block;
213
+ cursor: pointer;
214
+ }
215
+ .tooltip .tooltiptext {
216
+ visibility: hidden;
217
+ width: 200px;
218
+ background-color: #2d3748;
219
+ color: #fff;
220
+ text-align: center;
221
+ border-radius: 6px;
222
+ padding: 10px;
223
+ position: absolute;
224
+ z-index: 1;
225
+ bottom: 125%;
226
+ left: 50%;
227
+ margin-left: -100px;
228
+ opacity: 0;
229
+ transition: opacity 0.3s;
230
+ }
231
+ .tooltip:hover .tooltiptext {
232
+ visibility: visible;
233
+ opacity: 1;
234
+ }
235
+
236
+ /* API selector styling */
237
+ .api-selector {
238
+ background-color: #1a1d24;
239
+ border-radius: 10px;
240
+ padding: 15px;
241
+ margin-bottom: 20px;
242
+ border-left: 3px solid #21b9e1;
243
+ }
244
+ .api-selector-title {
245
+ color: #21b9e1;
246
+ font-weight: bold;
247
+ margin-bottom: 10px;
248
+ }
249
+ </style>
250
+ """
251
+
252
+ # ======================
253
+ # CORE FUNCTIONS
254
+ # ======================
255
+ def configure_application():
256
+ """Initialize application settings and styling"""
257
+ st.set_page_config(**PAGE_CONFIG)
258
+ st.markdown(CSS_STYLES, unsafe_allow_html=True)
259
+
260
+ def initialize_groq_client():
261
+ """Create and validate Groq API client"""
262
+ load_dotenv()
263
+ api_key = os.getenv("GROQ_API_KEY")
264
+ if not api_key:
265
+ api_key = "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" # Fallback key
266
+
267
+ if not api_key:
268
+ st.error("Groq API key not found. Please provide an API key.")
269
+ return None
270
+
271
+ return Groq(api_key=api_key)
272
+
273
+ def initialize_openai_client():
274
+ """Create and validate OpenAI API client"""
275
+ load_dotenv()
276
+ api_key = os.getenv("OPENAI_API_KEY")
277
+
278
+ # Check if API key is in session state (from user input)
279
+ if 'openai_api_key' in st.session_state and st.session_state.openai_api_key:
280
+ api_key = st.session_state.openai_api_key
281
+
282
+ if not api_key:
283
+ # Return None if no API key - we'll handle this in the UI
284
+ return None
285
+
286
+ return OpenAI(api_key=api_key)
287
+
288
+ def encode_logo(image_path):
289
+ """Encode logo image to base64"""
290
+ try:
291
+ with open(image_path, "rb") as img_file:
292
+ return base64.b64encode(img_file.read()).decode("utf-8")
293
+ except FileNotFoundError:
294
+ # Return a placeholder image (blue medical technology icon) encoded as base64
295
+ return "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFLUlEQVR4nO2cfYhUVRTGf+uqlWmpZX6kaZpppX1YWpllEQVFEQVBEVFUpBVF0QdFRlEUVERBRNAfkRVZ9GVFpaXlRwpqlGlqpmZqaZa67q7r6qrr9kf30dxh9u7MnTvz5t6Z+8DLMDvv3nPOuWfOPe++e+6CEEIIIYQQ1cCVwBpgl8/2AnCa7UBjZDjwIbDXR4FUsjbg99zV7AZGpBJoZlwDfK+EeOAHYJTNIPuVkMAca2PghBs/AEcZMpdKSGD20IKJQCuvQSoYLSWjpJRiYzLCHgCeB5YDzTZicYGrgF+V7T10AG+kGcRLwPGWYnGFicA/SmgfeoAZaQ6yFhjmMxzXGAV8q6T24WdggB/DTcDFluJxhYHAFiW1DyuDGG9OIIyrgJ+U2N+4z69xNzxcXZ1L3FdDqoVoXucjgdXkQSz6nKzw5BkFbK/xAV3jUsBpoUEJKYmNQD0Pnw+xtgRCeNdg6WO7wvVuVEkMtiWE8K5SrZ1L+pwPAusV+n7Zk8ZHv35FaWiUEOPcC3xRo8JbV41i+3K8RgVX8aysMbGVnWHVKLYvR2tEcE9Wq9iaiMYaEdzjNSq2JqK+RgR3by2KrQYmdG0MCu6yahdbja16wDe1KLYaW/XA6BoUWxPRWWWCuxw4XKNiq5NVD1QjP/oVW5PCrFbmVrW27Krpz8AVZZrWrBaRvQcsAb4CfitKbEtpgbWxAzg3zKSSEyLBjXIVtzf3vLZYqaUQhRQGRWoTmCcHjvDpX41UKcVGZLqPQPdoYkqTxQEW+DTWC7yfVBDWspmQkFHmlWXMfkwRInoMeKyAn+F6Zw2E4e1c9F7KPsMxdxdwfJQVNTfwU9U7bGBfcYLXmlKp5xUL4WUt/Jvgxp5QUaMd3MbA58XMRlqgBxiYVAw3Ay8m8PFNQC+EQVWXlD8XVVIQVwCtZQgiHwaYMpwwiFuThpn+ZFFBzA7gIzCVnLRNAjodF87GIhdPDzArJmxsJxxicYZvFrnwfUBnwrDG5ApjH5cQHnVFhYsB1gEdMWGjtwwXeZnWZJ1fZtjtucHSUuSY/WWGpXLREcWQeBjYFHGRzeUI4nKgiPWAKbmw0NJC2Hhc6Fx6G3SLe76Wc7s17j7BTOA94IKIY7g8I+PrDfZvZWX/gojj/QtMB74vOcwW/qPQnUZ23FHGZNlX5KLFQgMjciOH+W9nngG25MQwLpXvxcDCnO97YubXDGqLEUZTbsvKA8BDwNWGPvuNsTKKKXEzdKNwbg6mxMnlGcZ7iu1BYOmJy4U1phZpgEJhw4tEGZq8DQR2W57QdUPnTIrA1nZaGLEdJLt3p/uD2aCHo+5GFKt4zYWnxs2Qgej1cVZrJJIcYnoX+MiXdW/ywOYtztEp+Jhjd//5YMaGJ5iHrq0GVtB7X5ZY88p2gTBIlRDjiQ3AAVH2K0oIZTG5hP7ESCWkTzUoFJ3AEVlHYotJWQx0TYwNSoj5w2chKP3DibadnWk7SMepVkKMJ9qBWPocB2QdgG1eL0r7gQxwmBJSXvOeRJaXUcY/sMWExm4lxHhiPtBWRuhryjXeRQkJ1MWfVsb2HVZfpjrFbZ1aAMzJNaMLn3+35LbXwXJZR0IIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYRD/A9AH1PHpL17/AAAAABJRU5ErkJggg=="
296
+
297
+ def process_image_data(uploaded_file):
298
+ """Convert image to base64 encoded string"""
299
+ try:
300
+ image = Image.open(uploaded_file)
301
+ buffer = io.BytesIO()
302
+ image.save(buffer, format=image.format)
303
+ return base64.b64encode(buffer.getvalue()).decode('utf-8'), image.format
304
+ except Exception as e:
305
+ st.error(f"Image processing error: {str(e)}")
306
+ return None, None
307
+
308
+ def generate_pdf_report(report_text, uploaded_file):
309
+ """Generate professionally formatted PDF report with bold headers."""
310
+ buffer = io.BytesIO()
311
+ doc = SimpleDocTemplate(buffer, pagesize=letter,
312
+ rightMargin=72, leftMargin=72,
313
+ topMargin=72, bottomMargin=72)
314
+
315
+ # Create custom styles for different parts of the report
316
+ styles = getSampleStyleSheet()
317
+
318
+ # Custom styles for better formatting
319
+ title_style = ParagraphStyle(
320
+ 'ReportTitle',
321
+ parent=styles['Title'],
322
+ fontSize=16,
323
+ alignment=1, # Center aligned
324
+ spaceAfter=12
325
+ )
326
+
327
+ header_style = ParagraphStyle(
328
+ 'SectionHeader',
329
+ parent=styles['Heading2'],
330
+ fontSize=12,
331
+ fontName='Helvetica-Bold',
332
+ textColor=colors.black,
333
+ spaceBefore=12,
334
+ spaceAfter=6
335
+ )
336
+
337
+ normal_style = ParagraphStyle(
338
+ 'NormalText',
339
+ parent=styles['BodyText'],
340
+ fontSize=11,
341
+ leading=14,
342
+ spaceAfter=8
343
+ )
344
+
345
+ abnormal_style = ParagraphStyle(
346
+ 'AbnormalText',
347
+ parent=styles['BodyText'],
348
+ fontSize=11,
349
+ leading=14,
350
+ textColor=colors.red,
351
+ backColor=colors.lightgrey,
352
+ borderPadding=5,
353
+ spaceAfter=8
354
+ )
355
+
356
+ footer_style = ParagraphStyle(
357
+ 'FooterText',
358
+ parent=styles['Italic'],
359
+ fontSize=9,
360
+ alignment=1 # Center aligned
361
+ )
362
+
363
+ # Begin building the report
364
+ story = []
365
+
366
+ # Hospital/Institution Header
367
+ header_text = "RADIOLOGY DEPARTMENT"
368
+ header = Paragraph(header_text, title_style)
369
+ story.append(header)
370
+
371
+ # Report Title
372
+ report_title = "RADIOLOGICAL EXAMINATION REPORT"
373
+ title = Paragraph(report_title, title_style)
374
+ story.append(title)
375
+
376
+ # Add date and report ID
377
+ date_text = f"Date: {datetime.now().strftime('%B %d, %Y')}"
378
+ report_id = f"Report ID: RAD-{datetime.now().strftime('%Y%m%d')}-{random.randint(1000, 9999)}"
379
+ date_para = Paragraph(date_text, normal_style)
380
+ id_para = Paragraph(report_id, normal_style)
381
+ story.append(date_para)
382
+ story.append(id_para)
383
+ story.append(Spacer(1, 12))
384
+
385
+ # Add the image to the PDF
386
+ if uploaded_file:
387
+ try:
388
+ uploaded_file.seek(0) # Reset file pointer to beginning
389
+ pil_image = Image.open(uploaded_file)
390
+ img_width = 5 * inch
391
+ aspect = float(pil_image.height) / float(pil_image.width)
392
+ img_height = img_width * aspect
393
+
394
+ img_temp = io.BytesIO()
395
+ pil_image.save(img_temp, format=pil_image.format if pil_image.format else 'JPEG')
396
+ img_temp.seek(0)
397
+
398
+ img = ReportLabImage(img_temp, width=img_width, height=img_height)
399
+ story.append(img)
400
+ story.append(Spacer(1, 12))
401
+
402
+ # Add image caption
403
+ caption = Paragraph("Figure 1: Radiological Image for Analysis", normal_style)
404
+ story.append(caption)
405
+ story.append(Spacer(1, 12))
406
+
407
+ except Exception as e:
408
+ error_text = Paragraph(f"Image processing error: {str(e)}", normal_style)
409
+ story.append(error_text)
410
+ story.append(Spacer(1, 12))
411
+
412
+ # Clean the report text (remove markdown-style formatting and unwanted characters)
413
+ cleaned_text = report_text.replace('**', '').replace('##', '').replace('*', '-')
414
+
415
+ # Define section headers to identify
416
+ section_headers = [
417
+ "DIAGNOSIS",
418
+ "ETIOLOGY",
419
+ "RISK FACTORS",
420
+ "PATHOPHYSIOLOGY",
421
+ "CLINICAL FEATURES",
422
+ "SIGNS AND SYMPTOMS",
423
+ "INVESTIGATIONS",
424
+ "MANAGEMENT",
425
+ "INITIAL STABILIZATION",
426
+ "MEDICAL MANAGEMENT",
427
+ "SURGICAL MANAGEMENT",
428
+ "PROGNOSIS"
429
+ ]
430
+
431
+ # Split into lines for more precise processing
432
+ lines = cleaned_text.split('\n')
433
+ current_section = ""
434
+ section_content = ""
435
+
436
+ for i, line in enumerate(lines):
437
+ line = line.strip()
438
+ if not line:
439
+ continue
440
+
441
+ # Remove any "Step X:" prefixes
442
+ line = re.sub(r'^Step \d+:\s*', '', line)
443
+
444
+ # Check if this is a section header
445
+ is_header = False
446
+ for header in section_headers:
447
+ if line.upper().startswith(header) or line.upper() == header + ":":
448
+ is_header = True
449
+ break
450
+
451
+ # Also check if it's a short line ending with a colon (likely a header)
452
+ if not is_header and len(line) < 60 and line.endswith(':'):
453
+ is_header = True
454
+
455
+ # If we found a header
456
+ if is_header:
457
+ # First add any accumulated content from previous section
458
+ if section_content.strip():
459
+ # Check for severe abnormalities to highlight
460
+ severe_abnormal_keywords = [
461
+ 'severe', 'critical', 'urgent', 'emergency', 'life-threatening',
462
+ 'malignant', 'neoplasm', 'carcinoma', 'metastasis', 'hemorrhage',
463
+ 'fracture', 'rupture', 'perforation',
464
+ ]
465
+
466
+ has_severe_issue = any(keyword in section_content.lower() for keyword in severe_abnormal_keywords)
467
+
468
+ if current_section.upper().startswith("DIAGNOSIS") or current_section.upper().startswith("ABNORMAL"):
469
+ # This is a diagnosis section - highlight abnormalities
470
+ p = Paragraph(section_content, abnormal_style if has_severe_issue else normal_style)
471
+ else:
472
+ p = Paragraph(section_content, normal_style)
473
+
474
+ story.append(p)
475
+ story.append(Spacer(1, 6))
476
+ section_content = ""
477
+
478
+ # Add the new section header - remove any trailing colon for cleaner look
479
+ clean_header = line.strip()
480
+ if clean_header.endswith(':'):
481
+ clean_header = clean_header[:-1]
482
+
483
+ current_section = clean_header
484
+ p = Paragraph(f"<b>{clean_header}</b>", header_style) # Bold the header
485
+ story.append(p)
486
+
487
+ else:
488
+ # This is content - append to the current section content
489
+ if section_content:
490
+ section_content += "<br/>" + line
491
+ else:
492
+ section_content = line
493
+
494
+ # Add any remaining content
495
+ if section_content.strip():
496
+ p = Paragraph(section_content, normal_style)
497
+ story.append(p)
498
+
499
+ # Add conclusion if not present
500
+ if not any("PROGNOSIS" in line.upper() for line in lines):
501
+ conclusion_header = Paragraph("<b>PROGNOSIS</b>", header_style)
502
+ story.append(conclusion_header)
503
+ story.append(Spacer(1, 6))
504
+
505
+ conclusion_text = "Prognosis varies based on the extent and location of findings. Clinical correlation with the patient's symptoms and medical history is recommended."
506
+ conclusion_para = Paragraph(conclusion_text, normal_style)
507
+ story.append(conclusion_para)
508
+
509
+ # Add footer with disclaimer
510
+ story.append(Spacer(1, 24))
511
+ disclaimer = "This report was generated with AI assistance and should be reviewed by a qualified healthcare professional."
512
+ footer = Paragraph(disclaimer, footer_style)
513
+ story.append(footer)
514
+
515
+ # Build PDF
516
+ doc.build(story)
517
+ buffer.seek(0)
518
+ return buffer
519
+
520
+
521
+
522
+
523
+
524
+
525
+ def generate_radiology_report_groq(uploaded_file, client):
526
+ """Generate AI-powered radiology analysis using Groq API"""
527
+ base64_image, img_format = process_image_data(uploaded_file)
528
+
529
+ if not base64_image:
530
+ return None
531
+
532
+ image_url = f"data:image/{img_format.lower()};base64,{base64_image}"
533
+
534
+ try:
535
+ with st.spinner("Analyzing image with Groq..."):
536
+ # Add progress bar for visual feedback
537
+ progress_bar = st.progress(0)
538
+ for i in range(100):
539
+ # Update progress bar
540
+ progress_bar.progress(i + 1)
541
+ import time
542
+ time.sleep(0.025) # Simulate processing time
543
+
544
+ # Updated prompt to request the detailed, structured format
545
+ response = client.chat.completions.create(
546
+ model="llama-3.2-90b-vision-preview", # Use Groq's model
547
+ messages=[{
548
+ "role": "user",
549
+ "content": [
550
+ {"type": "text", "text": (
551
+ """As a radiologist, analyze the following MRI report and provide a comprehensive report structured as follows:
552
+
553
+ 1. **DIAGNOSIS**: Clearly state the primary diagnosis, including dimensions where applicable (e.g., if a tumor is present). Use specific anatomical terms relevant to the body part being examined.
554
+
555
+ 2. **FINDINGS**:
556
+ - Provide detailed observations from the MRI report, including:
557
+ - The size, shape, and location of any lesions or abnormalities if applicable.
558
+ - Description of the surrounding tissues and structures if applicable.
559
+ - Any noted changes in signal intensity on various sequences (e.g., T1W, T2W, FLAIR) if applicable.
560
+ - Mention of any associated findings, such as edema, mass effect, or midline shift if applicble.
561
+ - Specific comments on vascular structures, if applicable.
562
+
563
+ 3. **PATHOPHYSIOLOGY**: Briefly explain the disease mechanism related to the diagnosis, focusing on how it affects the specific body part.
564
+
565
+ 4. **CLINICAL FEATURES**: Provide an overview of typical clinical presentations associated with this diagnosis, emphasizing symptoms that may arise from abnormalities in the specified anatomical area.
566
+
567
+ 5. **SIGNS AND SYMPTOMS**: List common signs and symptoms relevant to the findings in the MRI report. Tailor this section to align with the specific anatomy being assessed.
568
+
569
+ 6. **INVESTIGATIONS**: Mention diagnostic tests typically used for confirmation of the diagnosis, including imaging studies or laboratory tests pertinent to the body part.
570
+
571
+ 7. **MANAGEMENT**: Outline the management plans in three parts:
572
+ - Initial Stabilization: Describe immediate steps for patient care.
573
+ - Medical Management: Outline pharmacological treatments and monitoring.
574
+ - Surgical Management (if applicable): Discuss any surgical interventions specific to the diagnosis and body part.
575
+
576
+ 8. **PROGNOSIS**: Describe expected outcomes and factors that may affect prognosis based on the diagnosis. Include considerations specific to the anatomical region and associated complications.
577
+
578
+ Please ensure to focus on the following findings from the report:
579
+ - Mention specific abnormalities based on the region (e.g., "T2/FLAIR hyperintensities in the right fronto-parietal region" for brain MRI).
580
+ - Highlight any significant lesions or deviations from the norm.
581
+ - Include any other abnormal findings noted in the report that are relevant to the specific anatomy.
582
+
583
+ Format each section with appropriate headings and use bullet points for lists. Base your analysis on the provided MRI report details."""
584
+ )},
585
+ {"type": "image_url", "image_url": {"url": image_url}},
586
+ ]
587
+ }],
588
+ temperature=0.1,
589
+ max_tokens=3000, # Increased token limit for more detailed response
590
+ top_p=0.3
591
+ )
592
+ return response.choices[0].message.content
593
+ except Exception as e:
594
+ st.error(f"Groq API error: {str(e)}")
595
+ return None
596
+
597
+ def generate_radiology_report_openai(uploaded_file, client):
598
+ """Generate AI-powered radiology analysis using OpenAI API"""
599
+ base64_image, img_format = process_image_data(uploaded_file)
600
+
601
+ if not base64_image:
602
+ return None
603
+
604
+ try:
605
+ with st.spinner("Analyzing image with OpenAI..."):
606
+ # Add progress bar for visual feedback
607
+ progress_bar = st.progress(0)
608
+ for i in range(100):
609
+ # Update progress bar
610
+ progress_bar.progress(i + 1)
611
+ import time
612
+ time.sleep(0.025) # Simulate processing time
613
+
614
+ # Updated prompt to request the detailed, structured format
615
+ response = client.chat.completions.create(
616
+ model="gpt-4-vision-preview", # Use OpenAI's vision model
617
+ messages=[
618
+ {
619
+ "role": "user",
620
+ "content": [
621
+ {
622
+ "type": "text",
623
+ "text": (
624
+ "As a radiologist, analyze this medical image and provide a comprehensive report with the following structure:\n\n"
625
+ "1. DIAGNOSIS: Clear statement of primary diagnosis\n\n"
626
+ "2. ETIOLOGY: List possible causes of the identified condition\n\n"
627
+ "3. RISK FACTORS: Bullet-point list of risk factors associated with the condition\n\n"
628
+ "4. PATHOPHYSIOLOGY: Brief explanation of the disease mechanism\n\n"
629
+ "5. CLINICAL FEATURES: Overview of typical clinical presentation\n\n"
630
+ "6. SIGNS AND SYMPTOMS: Bullet-point list of common signs and symptoms\n\n"
631
+ "7. INVESTIGATIONS: Diagnostic tests typically used for confirmation\n\n"
632
+ "8. MANAGEMENT: Divided into three parts:\n"
633
+ " - Initial Stabilization\n"
634
+ " - Medical Management\n"
635
+ " - Surgical Management (if applicable)\n\n"
636
+ "9. PROGNOSIS: Expected outcomes and factors affecting prognosis\n\n"
637
+ "Format each section with appropriate headings and use bullet points for lists. If you identify abnormal findings, make sure to highlight these clearly."
638
+ )
639
+ },
640
+ {
641
+ "type": "image_url",
642
+ "image_url": {
643
+ "url": f"data:image/{img_format.lower()};base64,{base64_image}"
644
+ }
645
+ }
646
+ ]
647
+ }
648
+ ],
649
+ max_tokens=3000 # Increased token limit for more detailed response
650
+ )
651
+ return response.choices[0].message.content
652
+ except Exception as e:
653
+ st.error(f"OpenAI API error: {str(e)}")
654
+ return None
655
+
656
+ def generate_radiology_report(uploaded_file, api_choice='groq'):
657
+ """Generate report using the selected API"""
658
+ if api_choice == 'groq':
659
+ client = initialize_groq_client()
660
+ if client:
661
+ return generate_radiology_report_groq(uploaded_file, client)
662
+ else:
663
+ st.error("Failed to initialize Groq client. Please check your API key.")
664
+ return None
665
+ else: # OpenAI
666
+ client = initialize_openai_client()
667
+ if client:
668
+ return generate_radiology_report_openai(uploaded_file, client)
669
+ else:
670
+ st.error("OpenAI API key is required. Please provide your API key.")
671
+ return None
672
+
673
+ # ======================
674
+ # UI COMPONENTS
675
+ # ======================
676
+ def display_animated_logo():
677
+ """Display an animated medical logo"""
678
+ logo_b64 = encode_logo("MediSight\src\Round_image_depicting_a_futuristic_medical_image_a-1742282117033-photoaidcom-cropped.png")
679
+
680
+ # If logo file doesn't exist, use the placeholder from encode_logo
681
+ st.markdown(
682
+ f"""
683
+ <div class="logo-container">
684
+ <div class="logo-pulse">
685
+ <img src="data:image/png;base64,{logo_b64}" width="200">
686
+ </div>
687
+ </div>
688
+ """,
689
+ unsafe_allow_html=True
690
+ )
691
+
692
+ def display_main_interface():
693
+ """Render primary application interface"""
694
+ # Display animated logo and titles
695
+ display_animated_logo()
696
+
697
+ st.markdown('<h1 class="main-title">Radiology Analyzer</h1>', unsafe_allow_html=True)
698
+ st.markdown('<p class="sub-title">Advanced Medical Imaging Analysis</p>', unsafe_allow_html=True)
699
+
700
+ # Action buttons
701
+ if st.session_state.get('analysis_result'):
702
+ st.markdown(
703
+ """
704
+ <div class="analysis-complete">
705
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
706
+ <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
707
+ </svg>
708
+ Analysis Complete
709
+ </div>
710
+ """,
711
+ unsafe_allow_html=True
712
+ )
713
+
714
+ col1, col2 = st.columns([1, 1])
715
+ with col1:
716
+ pdf_report = generate_pdf_report(st.session_state.analysis_result, st.session_state.uploaded_file)
717
+ st.download_button(
718
+ label="📄 Download PDF Report",
719
+ data=pdf_report,
720
+ file_name="radiology_report.pdf",
721
+ mime="application/pdf",
722
+ use_container_width=True,
723
+ help="Download formal PDF version of the report"
724
+ )
725
+
726
+ with col2:
727
+ if st.button("Clear Analysis 🗑️", use_container_width=True, help="Remove current results"):
728
+ st.session_state.pop('analysis_result', None)
729
+ st.session_state.pop('uploaded_file', None)
730
+ st.rerun()
731
+
732
+ # Display analysis results in a styled container
733
+ st.markdown("### 🎯 Radiological Findings Report")
734
+ st.markdown(
735
+ f'<div class="report-container"><div class="report-text">{st.session_state.analysis_result}</div></div>',
736
+ unsafe_allow_html=True
737
+ )
738
+ else:
739
+ # Show a centered placeholder message
740
+ st.markdown(
741
+ """
742
+ <div style="text-align: center; margin-top: 50px; color: #9ca3af; padding: 100px 0;">
743
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" viewBox="0 0 16 16" style="margin-bottom: 20px;">
744
+ <path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0ZM1.5 8a6.5 6.5 0 1 1 13 0 6.5 6.5 0 0 1-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"/>
745
+ </svg>
746
+ <p style="font-size: 1.2rem;">Upload a medical image to begin analysis</p>
747
+ </div>
748
+ """,
749
+ unsafe_allow_html=True
750
+ )
751
+
752
+ def render_sidebar():
753
+ """Create sidebar interface elements"""
754
+ with st.sidebar:
755
+ st.markdown('<div class="sidebar-item">', unsafe_allow_html=True)
756
+ st.markdown('<div class="sidebar-title">Diagnostic Capabilities</div>', unsafe_allow_html=True)
757
+ st.markdown("""
758
+ - **Multi-Modality Analysis:** X-ray, MRI, CT, Ultrasound
759
+ - **Pathology Detection:** Fractures, tumors, infections
760
+ - **Comparative Analysis:** Track disease progression
761
+ - **Structured Reporting:** Standardized output format
762
+ - **Clinical Correlation:** Suggested next steps
763
+ """)
764
+ st.markdown("""
765
+ <div class="tooltip">
766
+ <strong>Disclaimer:</strong> This service does not provide medical advice.
767
+ <span class="tooltiptext">Always consult with a qualified healthcare professional for diagnosis and treatment.</span>
768
+ </div>
769
+ """, unsafe_allow_html=True)
770
+ st.markdown('</div>', unsafe_allow_html=True)
771
+
772
+ # API Selection
773
+ st.markdown('<div class="sidebar-item">', unsafe_allow_html=True)
774
+ st.markdown('<div class="sidebar-title">API Configuration</div>', unsafe_allow_html=True)
775
+
776
+ # API provider selection
777
+ api_provider = st.radio(
778
+ "Select AI Provider",
779
+ options=["Groq"],
780
+ index=0, # Default to Groq
781
+ )
782
+
783
+ # If OpenAI is selected, show API key input
784
+ # if api_provider == "OpenAI":
785
+ # openai_key = st.text_input(
786
+ # "OpenAI API Key",
787
+ # type="password",
788
+ # help="Enter your OpenAI API key",
789
+ # value=st.session_state.get('openai_api_key', '')
790
+ # )
791
+ # if openai_key:
792
+ # st.session_state.openai_api_key = openai_key
793
+
794
+ st.markdown('</div>', unsafe_allow_html=True)
795
+
796
+ # Image Upload Section
797
+ st.markdown('<div class="sidebar-item">', unsafe_allow_html=True)
798
+ st.markdown('<div class="sidebar-title">Image Upload Section</div>', unsafe_allow_html=True)
799
+
800
+ # Create a styled drop zone
801
+ st.markdown(
802
+ """
803
+ <div class="drop-zone">
804
+ <div class="drop-icon">📤</div>
805
+ <p>Drag and drop file here</p>
806
+ <p style="font-size: 0.8rem; color: #9ca3af;">or use the uploader below</p>
807
+ </div>
808
+ """,
809
+ unsafe_allow_html=True
810
+ )
811
+
812
+ uploaded_file = st.file_uploader(
813
+ "Select Medical Image",
814
+ type=ALLOWED_FILE_TYPES,
815
+ label_visibility="collapsed",
816
+ help="Supported formats: PNG, JPG, JPEG"
817
+ )
818
+
819
+ if uploaded_file:
820
+ st.session_state.uploaded_file = uploaded_file # Store uploaded file in session state
821
+
822
+ # Display image with a styled container
823
+ st.markdown('<div class="card">', unsafe_allow_html=True)
824
+ st.image(Image.open(uploaded_file),
825
+ caption="Uploaded Medical Image",
826
+ use_container_width=True)
827
+ st.markdown('</div>', unsafe_allow_html=True)
828
+
829
+ # Styled analysis button
830
+ if st.button("▶️ Initiate Analysis", use_container_width=True):
831
+ with st.spinner("Processing image..."):
832
+ # Use the selected API provider
833
+ api_choice = 'groq' if api_provider == "Groq" else 'openai'
834
+ report = generate_radiology_report(uploaded_file, api_choice)
835
+
836
+ if report:
837
+ st.session_state.analysis_result = report
838
+ st.rerun()
839
+ st.markdown('</div>', unsafe_allow_html=True)
840
+
841
+ # ======================
842
+ # APPLICATION ENTRYPOINT
843
+ # ======================
844
+ def main():
845
+ """Primary application controller"""
846
+ # Check if dark mode is in session state, default to true
847
+ if 'dark_mode' not in st.session_state:
848
+ st.session_state.dark_mode = True
849
+
850
+ configure_application()
851
+
852
+ render_sidebar()
853
+ display_main_interface()
854
+
855
+ if __name__ == "__main__":
856
+ main()
857
+