blackshadow1 commited on
Commit
572db01
·
verified ·
1 Parent(s): 9645453

Added the end consultation section ✅✅

Browse files
Files changed (1) hide show
  1. mediSync/app.py +627 -560
mediSync/app.py CHANGED
@@ -1,560 +1,627 @@
1
- import logging
2
- import os
3
- import sys
4
- import tempfile
5
- from pathlib import Path
6
-
7
- import gradio as gr
8
- import matplotlib.pyplot as plt
9
- from PIL import Image
10
-
11
- # Add parent directory to path
12
- parent_dir = os.path.dirname(os.path.abspath(__file__))
13
- sys.path.append(parent_dir)
14
-
15
- # Import our modules
16
- from models.multimodal_fusion import MultimodalFusion
17
- from utils.preprocessing import enhance_xray_image, normalize_report_text
18
- from utils.visualization import (
19
- plot_image_prediction,
20
- plot_multimodal_results,
21
- plot_report_entities,
22
- )
23
-
24
- # Set up logging
25
- logging.basicConfig(
26
- level=logging.INFO,
27
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
28
- handlers=[logging.StreamHandler(), logging.FileHandler("mediSync.log")],
29
- )
30
- logger = logging.getLogger(__name__)
31
-
32
- # Create temporary directory for sample data if it doesn't exist
33
- os.makedirs(os.path.join(parent_dir, "data", "sample"), exist_ok=True)
34
-
35
-
36
- class MediSyncApp:
37
- """
38
- Main application class for the MediSync multi-modal medical analysis system.
39
- """
40
-
41
- def __init__(self):
42
- """Initialize the application and load models."""
43
- self.logger = logging.getLogger(__name__)
44
- self.logger.info("Initializing MediSync application")
45
-
46
- # Initialize models with None for lazy loading
47
- self.fusion_model = None
48
- self.image_model = None
49
- self.text_model = None
50
-
51
- def load_models(self):
52
- """
53
- Load models if not already loaded.
54
-
55
- Returns:
56
- bool: True if models loaded successfully, False otherwise
57
- """
58
- try:
59
- if self.fusion_model is None:
60
- self.logger.info("Loading models...")
61
- self.fusion_model = MultimodalFusion()
62
- self.image_model = self.fusion_model.image_analyzer
63
- self.text_model = self.fusion_model.text_analyzer
64
- self.logger.info("Models loaded successfully")
65
- return True
66
-
67
- except Exception as e:
68
- self.logger.error(f"Error loading models: {e}")
69
- return False
70
-
71
- def analyze_image(self, image):
72
- """
73
- Analyze a medical image.
74
-
75
- Args:
76
- image: Image file uploaded through Gradio
77
-
78
- Returns:
79
- tuple: (image, image_results_html, plot_as_html)
80
- """
81
- try:
82
- # Ensure models are loaded
83
- if not self.load_models() or self.image_model is None:
84
- return image, "Error: Models not loaded properly.", None
85
-
86
- # Save uploaded image to a temporary file
87
- temp_dir = tempfile.mkdtemp()
88
- temp_path = os.path.join(temp_dir, "upload.png")
89
-
90
- if isinstance(image, str):
91
- # Copy the file if it's a path
92
- from shutil import copyfile
93
-
94
- copyfile(image, temp_path)
95
- else:
96
- # Save if it's a Gradio UploadButton image
97
- image.save(temp_path)
98
-
99
- # Run image analysis
100
- self.logger.info(f"Analyzing image: {temp_path}")
101
- results = self.image_model.analyze(temp_path)
102
-
103
- # Create visualization
104
- fig = plot_image_prediction(
105
- image,
106
- results.get("predictions", []),
107
- f"Primary Finding: {results.get('primary_finding', 'Unknown')}",
108
- )
109
-
110
- # Convert to HTML for display
111
- plot_html = self.fig_to_html(fig)
112
-
113
- # Format results as HTML
114
- html_result = f"""
115
- <h2>X-ray Analysis Results</h2>
116
- <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
117
- <p><strong>Confidence:</strong> {results.get("confidence", 0):.1%}</p>
118
- <p><strong>Abnormality Detected:</strong> {"Yes" if results.get("has_abnormality", False) else "No"}</p>
119
-
120
- <h3>Top Predictions:</h3>
121
- <ul>
122
- """
123
-
124
- # Add top 5 predictions
125
- for label, prob in results.get("predictions", [])[:5]:
126
- html_result += f"<li>{label}: {prob:.1%}</li>"
127
-
128
- html_result += "</ul>"
129
-
130
- # Add explanation
131
- explanation = self.image_model.get_explanation(results)
132
- html_result += f"<h3>Analysis Explanation:</h3><p>{explanation}</p>"
133
-
134
- return image, html_result, plot_html
135
-
136
- except Exception as e:
137
- self.logger.error(f"Error in image analysis: {e}")
138
- return image, f"Error analyzing image: {str(e)}", None
139
-
140
- def analyze_text(self, text):
141
- """
142
- Analyze a medical report text.
143
-
144
- Args:
145
- text: Report text input through Gradio
146
-
147
- Returns:
148
- tuple: (text, text_results_html, entities_plot_html)
149
- """
150
- try:
151
- # Ensure models are loaded
152
- if not self.load_models() or self.text_model is None:
153
- return text, "Error: Models not loaded properly.", None
154
-
155
- # Check for empty text
156
- if not text or len(text.strip()) < 10:
157
- return (
158
- text,
159
- "Error: Please enter a valid medical report text (at least 10 characters).",
160
- None,
161
- )
162
-
163
- # Normalize text
164
- normalized_text = normalize_report_text(text)
165
-
166
- # Run text analysis
167
- self.logger.info("Analyzing medical report text")
168
- results = self.text_model.analyze(normalized_text)
169
-
170
- # Get entities and create visualization
171
- entities = results.get("entities", {})
172
- fig = plot_report_entities(normalized_text, entities)
173
-
174
- # Convert to HTML for display
175
- entities_plot_html = self.fig_to_html(fig)
176
-
177
- # Format results as HTML
178
- html_result = f"""
179
- <h2>Medical Report Analysis Results</h2>
180
- <p><strong>Severity Level:</strong> {results.get("severity", {}).get("level", "Unknown")}</p>
181
- <p><strong>Severity Score:</strong> {results.get("severity", {}).get("score", 0)}/4</p>
182
- <p><strong>Confidence:</strong> {results.get("severity", {}).get("confidence", 0):.1%}</p>
183
-
184
- <h3>Key Findings:</h3>
185
- <ul>
186
- """
187
-
188
- # Add findings
189
- findings = results.get("findings", [])
190
- if findings:
191
- for finding in findings:
192
- html_result += f"<li>{finding}</li>"
193
- else:
194
- html_result += "<li>No specific findings detailed.</li>"
195
-
196
- html_result += "</ul>"
197
-
198
- # Add entities
199
- html_result += "<h3>Extracted Medical Entities:</h3>"
200
-
201
- for category, items in entities.items():
202
- if items:
203
- html_result += f"<p><strong>{category.capitalize()}:</strong> {', '.join(items)}</p>"
204
-
205
- # Add follow-up recommendations
206
- html_result += "<h3>Follow-up Recommendations:</h3><ul>"
207
- followups = results.get("followup_recommendations", [])
208
-
209
- if followups:
210
- for rec in followups:
211
- html_result += f"<li>{rec}</li>"
212
- else:
213
- html_result += "<li>No specific follow-up recommendations.</li>"
214
-
215
- html_result += "</ul>"
216
-
217
- return text, html_result, entities_plot_html
218
-
219
- except Exception as e:
220
- self.logger.error(f"Error in text analysis: {e}")
221
- return text, f"Error analyzing text: {str(e)}", None
222
-
223
- def analyze_multimodal(self, image, text):
224
- """
225
- Perform multimodal analysis of image and text.
226
-
227
- Args:
228
- image: Image file uploaded through Gradio
229
- text: Report text input through Gradio
230
-
231
- Returns:
232
- tuple: (results_html, multimodal_plot_html)
233
- """
234
- try:
235
- # Ensure models are loaded
236
- if not self.load_models() or self.fusion_model is None:
237
- return "Error: Models not loaded properly.", None
238
-
239
- # Check for empty inputs
240
- if image is None:
241
- return "Error: Please upload an X-ray image for analysis.", None
242
-
243
- if not text or len(text.strip()) < 10:
244
- return (
245
- "Error: Please enter a valid medical report text (at least 10 characters).",
246
- None,
247
- )
248
-
249
- # Save uploaded image to a temporary file
250
- temp_dir = tempfile.mkdtemp()
251
- temp_path = os.path.join(temp_dir, "upload.png")
252
-
253
- if isinstance(image, str):
254
- # Copy the file if it's a path
255
- from shutil import copyfile
256
-
257
- copyfile(image, temp_path)
258
- else:
259
- # Save if it's a Gradio UploadButton image
260
- image.save(temp_path)
261
-
262
- # Normalize text
263
- normalized_text = normalize_report_text(text)
264
-
265
- # Run multimodal analysis
266
- self.logger.info("Performing multimodal analysis")
267
- results = self.fusion_model.analyze(temp_path, normalized_text)
268
-
269
- # Create visualization
270
- fig = plot_multimodal_results(results, image, text)
271
-
272
- # Convert to HTML for display
273
- plot_html = self.fig_to_html(fig)
274
-
275
- # Generate explanation
276
- explanation = self.fusion_model.get_explanation(results)
277
-
278
- # Format results as HTML
279
- html_result = f"""
280
- <h2>Multimodal Medical Analysis Results</h2>
281
-
282
- <h3>Overview</h3>
283
- <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
284
- <p><strong>Severity Level:</strong> {results.get("severity", {}).get("level", "Unknown")}</p>
285
- <p><strong>Severity Score:</strong> {results.get("severity", {}).get("score", 0)}/4</p>
286
- <p><strong>Agreement Score:</strong> {results.get("agreement_score", 0):.0%}</p>
287
-
288
- <h3>Detailed Findings</h3>
289
- <ul>
290
- """
291
-
292
- # Add findings
293
- findings = results.get("findings", [])
294
- if findings:
295
- for finding in findings:
296
- html_result += f"<li>{finding}</li>"
297
- else:
298
- html_result += "<li>No specific findings detailed.</li>"
299
-
300
- html_result += "</ul>"
301
-
302
- # Add follow-up recommendations
303
- html_result += "<h3>Recommended Follow-up</h3><ul>"
304
- followups = results.get("followup_recommendations", [])
305
-
306
- if followups:
307
- for rec in followups:
308
- html_result += f"<li>{rec}</li>"
309
- else:
310
- html_result += (
311
- "<li>No specific follow-up recommendations provided.</li>"
312
- )
313
-
314
- html_result += "</ul>"
315
-
316
- # Add confidence note
317
- confidence = results.get("severity", {}).get("confidence", 0)
318
- html_result += f"""
319
- <p><em>Note: This analysis has a confidence level of {confidence:.0%}.
320
- Please consult with healthcare professionals for official diagnosis.</em></p>
321
- """
322
-
323
- return html_result, plot_html
324
-
325
- except Exception as e:
326
- self.logger.error(f"Error in multimodal analysis: {e}")
327
- return f"Error in multimodal analysis: {str(e)}", None
328
-
329
- def enhance_image(self, image):
330
- """
331
- Enhance X-ray image contrast.
332
-
333
- Args:
334
- image: Image file uploaded through Gradio
335
-
336
- Returns:
337
- PIL.Image: Enhanced image
338
- """
339
- try:
340
- if image is None:
341
- return None
342
-
343
- # Save uploaded image to a temporary file
344
- temp_dir = tempfile.mkdtemp()
345
- temp_path = os.path.join(temp_dir, "upload.png")
346
-
347
- if isinstance(image, str):
348
- # Copy the file if it's a path
349
- from shutil import copyfile
350
-
351
- copyfile(image, temp_path)
352
- else:
353
- # Save if it's a Gradio UploadButton image
354
- image.save(temp_path)
355
-
356
- # Enhance image
357
- self.logger.info(f"Enhancing image: {temp_path}")
358
- output_path = os.path.join(temp_dir, "enhanced.png")
359
- enhance_xray_image(temp_path, output_path)
360
-
361
- # Load enhanced image
362
- enhanced = Image.open(output_path)
363
- return enhanced
364
-
365
- except Exception as e:
366
- self.logger.error(f"Error enhancing image: {e}")
367
- return image # Return original image on error
368
-
369
- def fig_to_html(self, fig):
370
- """Convert matplotlib figure to HTML for display in Gradio."""
371
- try:
372
- import base64
373
- import io
374
-
375
- buf = io.BytesIO()
376
- fig.savefig(buf, format="png", bbox_inches="tight")
377
- buf.seek(0)
378
- img_str = base64.b64encode(buf.read()).decode("utf-8")
379
- plt.close(fig)
380
-
381
- return f'<img src="data:image/png;base64,{img_str}" alt="Analysis Plot">'
382
-
383
- except Exception as e:
384
- self.logger.error(f"Error converting figure to HTML: {e}")
385
- return "<p>Error displaying visualization.</p>"
386
-
387
-
388
- def create_interface():
389
- """Create and launch the Gradio interface."""
390
-
391
- app = MediSyncApp()
392
-
393
- # Example medical report for demo
394
- example_report = """
395
- CHEST X-RAY EXAMINATION
396
-
397
- CLINICAL HISTORY: 55-year-old male with cough and fever.
398
-
399
- FINDINGS: The heart size is at the upper limits of normal. The lungs are clear without focal consolidation,
400
- effusion, or pneumothorax. There is mild prominence of the pulmonary vasculature. No pleural effusion is seen.
401
- There is a small nodular opacity noted in the right lower lobe measuring approximately 8mm, which is suspicious
402
- and warrants further investigation. The mediastinum is unremarkable. The visualized bony structures show no acute abnormalities.
403
-
404
- IMPRESSION:
405
- 1. Mild cardiomegaly.
406
- 2. 8mm nodular opacity in the right lower lobe, recommend follow-up CT for further evaluation.
407
- 3. No acute pulmonary parenchymal abnormality.
408
-
409
- RECOMMENDATIONS: Follow-up chest CT to further characterize the nodular opacity in the right lower lobe.
410
- """
411
-
412
- # Get sample image path if available
413
- sample_images_dir = Path(parent_dir) / "data" / "sample"
414
- sample_images = list(sample_images_dir.glob("*.png")) + list(
415
- sample_images_dir.glob("*.jpg")
416
- )
417
-
418
- sample_image_path = None
419
- if sample_images:
420
- sample_image_path = str(sample_images[0])
421
-
422
- # Define interface
423
- with gr.Blocks(
424
- title="MediSync: Multi-Modal Medical Analysis System", theme=gr.themes.Soft()
425
- ) as interface:
426
- gr.Markdown("""
427
- # MediSync: Multi-Modal Medical Analysis System
428
-
429
- This AI-powered healthcare solution combines X-ray image analysis with patient report text processing
430
- to provide comprehensive medical insights.
431
-
432
- ## How to Use
433
- 1. Upload a chest X-ray image
434
- 2. Enter the corresponding medical report text
435
- 3. Choose the analysis type: image-only, text-only, or multimodal (combined)
436
- """)
437
-
438
- with gr.Tab("Multimodal Analysis"):
439
- with gr.Row():
440
- with gr.Column():
441
- multi_img_input = gr.Image(label="Upload X-ray Image", type="pil")
442
- multi_img_enhance = gr.Button("Enhance Image")
443
-
444
- multi_text_input = gr.Textbox(
445
- label="Enter Medical Report Text",
446
- placeholder="Enter the radiologist's report text here...",
447
- lines=10,
448
- value=example_report if sample_image_path is None else None,
449
- )
450
-
451
- multi_analyze_btn = gr.Button(
452
- "Analyze Image & Text", variant="primary"
453
- )
454
-
455
- with gr.Column():
456
- multi_results = gr.HTML(label="Analysis Results")
457
- multi_plot = gr.HTML(label="Visualization")
458
-
459
- # Set up examples if sample image exists
460
- if sample_image_path:
461
- gr.Examples(
462
- examples=[[sample_image_path, example_report]],
463
- inputs=[multi_img_input, multi_text_input],
464
- label="Example X-ray and Report",
465
- )
466
-
467
- with gr.Tab("Image Analysis"):
468
- with gr.Row():
469
- with gr.Column():
470
- img_input = gr.Image(label="Upload X-ray Image", type="pil")
471
- img_enhance = gr.Button("Enhance Image")
472
- img_analyze_btn = gr.Button("Analyze Image", variant="primary")
473
-
474
- with gr.Column():
475
- img_output = gr.Image(label="Processed Image")
476
- img_results = gr.HTML(label="Analysis Results")
477
- img_plot = gr.HTML(label="Visualization")
478
-
479
- # Set up example if sample image exists
480
- if sample_image_path:
481
- gr.Examples(
482
- examples=[[sample_image_path]],
483
- inputs=[img_input],
484
- label="Example X-ray Image",
485
- )
486
-
487
- with gr.Tab("Text Analysis"):
488
- with gr.Row():
489
- with gr.Column():
490
- text_input = gr.Textbox(
491
- label="Enter Medical Report Text",
492
- placeholder="Enter the radiologist's report text here...",
493
- lines=10,
494
- value=example_report,
495
- )
496
- text_analyze_btn = gr.Button("Analyze Text", variant="primary")
497
-
498
- with gr.Column():
499
- text_output = gr.Textbox(label="Processed Text")
500
- text_results = gr.HTML(label="Analysis Results")
501
- text_plot = gr.HTML(label="Entity Visualization")
502
-
503
- # Set up example
504
- gr.Examples(
505
- examples=[[example_report]],
506
- inputs=[text_input],
507
- label="Example Medical Report",
508
- )
509
-
510
- with gr.Tab("About"):
511
- gr.Markdown("""
512
- ## About MediSync
513
-
514
- MediSync is an AI-powered healthcare solution that uses multi-modal analysis to provide comprehensive insights from medical images and reports.
515
-
516
- ### Key Features
517
-
518
- - **X-ray Image Analysis**: Detects abnormalities in chest X-rays using pre-trained vision models
519
- - **Medical Report Processing**: Extracts key information from patient reports using NLP models
520
- - **Multi-modal Integration**: Combines insights from both image and text data for more accurate analysis
521
-
522
- ### Models Used
523
-
524
- - **X-ray Analysis**: facebook/deit-base-patch16-224-medical-cxr
525
- - **Medical Text Analysis**: medicalai/ClinicalBERT
526
-
527
- ### Important Disclaimer
528
-
529
- This tool is for educational and research purposes only. It is not intended to provide medical advice or replace professional healthcare. Always consult with qualified healthcare providers for medical decisions.
530
- """)
531
-
532
- # Set up event handlers
533
- multi_img_enhance.click(
534
- app.enhance_image, inputs=multi_img_input, outputs=multi_img_input
535
- )
536
- multi_analyze_btn.click(
537
- app.analyze_multimodal,
538
- inputs=[multi_img_input, multi_text_input],
539
- outputs=[multi_results, multi_plot],
540
- )
541
-
542
- img_enhance.click(app.enhance_image, inputs=img_input, outputs=img_output)
543
- img_analyze_btn.click(
544
- app.analyze_image,
545
- inputs=img_input,
546
- outputs=[img_output, img_results, img_plot],
547
- )
548
-
549
- text_analyze_btn.click(
550
- app.analyze_text,
551
- inputs=text_input,
552
- outputs=[text_output, text_results, text_plot],
553
- )
554
-
555
- # Run the interface
556
- interface.launch()
557
-
558
-
559
- if __name__ == "__main__":
560
- create_interface()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ import sys
4
+ import tempfile
5
+ from pathlib import Path
6
+
7
+ import gradio as gr
8
+ import matplotlib.pyplot as plt
9
+ from PIL import Image
10
+
11
+ # Add parent directory to path
12
+ parent_dir = os.path.dirname(os.path.abspath(__file__))
13
+ sys.path.append(parent_dir)
14
+
15
+ # Import our modules
16
+ from models.multimodal_fusion import MultimodalFusion
17
+ from utils.preprocessing import enhance_xray_image, normalize_report_text
18
+ from utils.visualization import (
19
+ plot_image_prediction,
20
+ plot_multimodal_results,
21
+ plot_report_entities,
22
+ )
23
+
24
+ # Set up logging
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
28
+ handlers=[logging.StreamHandler(), logging.FileHandler("mediSync.log")],
29
+ )
30
+ logger = logging.getLogger(__name__)
31
+
32
+ # Create temporary directory for sample data if it doesn't exist
33
+ os.makedirs(os.path.join(parent_dir, "data", "sample"), exist_ok=True)
34
+
35
+
36
+ class MediSyncApp:
37
+ """
38
+ Main application class for the MediSync multi-modal medical analysis system.
39
+ """
40
+
41
+ def __init__(self):
42
+ """Initialize the application and load models."""
43
+ self.logger = logging.getLogger(__name__)
44
+ self.logger.info("Initializing MediSync application")
45
+
46
+ # Initialize models with None for lazy loading
47
+ self.fusion_model = None
48
+ self.image_model = None
49
+ self.text_model = None
50
+
51
+ def load_models(self):
52
+ """
53
+ Load models if not already loaded.
54
+
55
+ Returns:
56
+ bool: True if models loaded successfully, False otherwise
57
+ """
58
+ try:
59
+ if self.fusion_model is None:
60
+ self.logger.info("Loading models...")
61
+ self.fusion_model = MultimodalFusion()
62
+ self.image_model = self.fusion_model.image_analyzer
63
+ self.text_model = self.fusion_model.text_analyzer
64
+ self.logger.info("Models loaded successfully")
65
+ return True
66
+
67
+ except Exception as e:
68
+ self.logger.error(f"Error loading models: {e}")
69
+ return False
70
+
71
+ def analyze_image(self, image):
72
+ """
73
+ Analyze a medical image.
74
+
75
+ Args:
76
+ image: Image file uploaded through Gradio
77
+
78
+ Returns:
79
+ tuple: (image, image_results_html, plot_as_html)
80
+ """
81
+ try:
82
+ # Ensure models are loaded
83
+ if not self.load_models() or self.image_model is None:
84
+ return image, "Error: Models not loaded properly.", None
85
+
86
+ # Save uploaded image to a temporary file
87
+ temp_dir = tempfile.mkdtemp()
88
+ temp_path = os.path.join(temp_dir, "upload.png")
89
+
90
+ if isinstance(image, str):
91
+ # Copy the file if it's a path
92
+ from shutil import copyfile
93
+
94
+ copyfile(image, temp_path)
95
+ else:
96
+ # Save if it's a Gradio UploadButton image
97
+ image.save(temp_path)
98
+
99
+ # Run image analysis
100
+ self.logger.info(f"Analyzing image: {temp_path}")
101
+ results = self.image_model.analyze(temp_path)
102
+
103
+ # Create visualization
104
+ fig = plot_image_prediction(
105
+ image,
106
+ results.get("predictions", []),
107
+ f"Primary Finding: {results.get('primary_finding', 'Unknown')}",
108
+ )
109
+
110
+ # Convert to HTML for display
111
+ plot_html = self.fig_to_html(fig)
112
+
113
+ # Format results as HTML
114
+ html_result = f"""
115
+ <h2>X-ray Analysis Results</h2>
116
+ <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
117
+ <p><strong>Confidence:</strong> {results.get("confidence", 0):.1%}</p>
118
+ <p><strong>Abnormality Detected:</strong> {"Yes" if results.get("has_abnormality", False) else "No"}</p>
119
+
120
+ <h3>Top Predictions:</h3>
121
+ <ul>
122
+ """
123
+
124
+ # Add top 5 predictions
125
+ for label, prob in results.get("predictions", [])[:5]:
126
+ html_result += f"<li>{label}: {prob:.1%}</li>"
127
+
128
+ html_result += "</ul>"
129
+
130
+ # Add explanation
131
+ explanation = self.image_model.get_explanation(results)
132
+ html_result += f"<h3>Analysis Explanation:</h3><p>{explanation}</p>"
133
+
134
+ return image, html_result, plot_html
135
+
136
+ except Exception as e:
137
+ self.logger.error(f"Error in image analysis: {e}")
138
+ return image, f"Error analyzing image: {str(e)}", None
139
+
140
+ def analyze_text(self, text):
141
+ """
142
+ Analyze a medical report text.
143
+
144
+ Args:
145
+ text: Report text input through Gradio
146
+
147
+ Returns:
148
+ tuple: (text, text_results_html, entities_plot_html)
149
+ """
150
+ try:
151
+ # Ensure models are loaded
152
+ if not self.load_models() or self.text_model is None:
153
+ return text, "Error: Models not loaded properly.", None
154
+
155
+ # Check for empty text
156
+ if not text or len(text.strip()) < 10:
157
+ return (
158
+ text,
159
+ "Error: Please enter a valid medical report text (at least 10 characters).",
160
+ None,
161
+ )
162
+
163
+ # Normalize text
164
+ normalized_text = normalize_report_text(text)
165
+
166
+ # Run text analysis
167
+ self.logger.info("Analyzing medical report text")
168
+ results = self.text_model.analyze(normalized_text)
169
+
170
+ # Get entities and create visualization
171
+ entities = results.get("entities", {})
172
+ fig = plot_report_entities(normalized_text, entities)
173
+
174
+ # Convert to HTML for display
175
+ entities_plot_html = self.fig_to_html(fig)
176
+
177
+ # Format results as HTML
178
+ html_result = f"""
179
+ <h2>Medical Report Analysis Results</h2>
180
+ <p><strong>Severity Level:</strong> {results.get("severity", {}).get("level", "Unknown")}</p>
181
+ <p><strong>Severity Score:</strong> {results.get("severity", {}).get("score", 0)}/4</p>
182
+ <p><strong>Confidence:</strong> {results.get("severity", {}).get("confidence", 0):.1%}</p>
183
+
184
+ <h3>Key Findings:</h3>
185
+ <ul>
186
+ """
187
+
188
+ # Add findings
189
+ findings = results.get("findings", [])
190
+ if findings:
191
+ for finding in findings:
192
+ html_result += f"<li>{finding}</li>"
193
+ else:
194
+ html_result += "<li>No specific findings detailed.</li>"
195
+
196
+ html_result += "</ul>"
197
+
198
+ # Add entities
199
+ html_result += "<h3>Extracted Medical Entities:</h3>"
200
+
201
+ for category, items in entities.items():
202
+ if items:
203
+ html_result += f"<p><strong>{category.capitalize()}:</strong> {', '.join(items)}</p>"
204
+
205
+ # Add follow-up recommendations
206
+ html_result += "<h3>Follow-up Recommendations:</h3><ul>"
207
+ followups = results.get("followup_recommendations", [])
208
+
209
+ if followups:
210
+ for rec in followups:
211
+ html_result += f"<li>{rec}</li>"
212
+ else:
213
+ html_result += "<li>No specific follow-up recommendations.</li>"
214
+
215
+ html_result += "</ul>"
216
+
217
+ return text, html_result, entities_plot_html
218
+
219
+ except Exception as e:
220
+ self.logger.error(f"Error in text analysis: {e}")
221
+ return text, f"Error analyzing text: {str(e)}", None
222
+
223
+ def analyze_multimodal(self, image, text):
224
+ """
225
+ Perform multimodal analysis of image and text.
226
+
227
+ Args:
228
+ image: Image file uploaded through Gradio
229
+ text: Report text input through Gradio
230
+
231
+ Returns:
232
+ tuple: (results_html, multimodal_plot_html)
233
+ """
234
+ try:
235
+ # Ensure models are loaded
236
+ if not self.load_models() or self.fusion_model is None:
237
+ return "Error: Models not loaded properly.", None
238
+
239
+ # Check for empty inputs
240
+ if image is None:
241
+ return "Error: Please upload an X-ray image for analysis.", None
242
+
243
+ if not text or len(text.strip()) < 10:
244
+ return (
245
+ "Error: Please enter a valid medical report text (at least 10 characters).",
246
+ None,
247
+ )
248
+
249
+ # Save uploaded image to a temporary file
250
+ temp_dir = tempfile.mkdtemp()
251
+ temp_path = os.path.join(temp_dir, "upload.png")
252
+
253
+ if isinstance(image, str):
254
+ # Copy the file if it's a path
255
+ from shutil import copyfile
256
+
257
+ copyfile(image, temp_path)
258
+ else:
259
+ # Save if it's a Gradio UploadButton image
260
+ image.save(temp_path)
261
+
262
+ # Normalize text
263
+ normalized_text = normalize_report_text(text)
264
+
265
+ # Run multimodal analysis
266
+ self.logger.info("Performing multimodal analysis")
267
+ results = self.fusion_model.analyze(temp_path, normalized_text)
268
+
269
+ # Create visualization
270
+ fig = plot_multimodal_results(results, image, text)
271
+
272
+ # Convert to HTML for display
273
+ plot_html = self.fig_to_html(fig)
274
+
275
+ # Generate explanation
276
+ explanation = self.fusion_model.get_explanation(results)
277
+
278
+ # Format results as HTML
279
+ html_result = f"""
280
+ <h2>Multimodal Medical Analysis Results</h2>
281
+
282
+ <h3>Overview</h3>
283
+ <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
284
+ <p><strong>Severity Level:</strong> {results.get("severity", {}).get("level", "Unknown")}</p>
285
+ <p><strong>Severity Score:</strong> {results.get("severity", {}).get("score", 0)}/4</p>
286
+ <p><strong>Agreement Score:</strong> {results.get("agreement_score", 0):.0%}</p>
287
+
288
+ <h3>Detailed Findings</h3>
289
+ <ul>
290
+ """
291
+
292
+ # Add findings
293
+ findings = results.get("findings", [])
294
+ if findings:
295
+ for finding in findings:
296
+ html_result += f"<li>{finding}</li>"
297
+ else:
298
+ html_result += "<li>No specific findings detailed.</li>"
299
+
300
+ html_result += "</ul>"
301
+
302
+ # Add follow-up recommendations
303
+ html_result += "<h3>Recommended Follow-up</h3><ul>"
304
+ followups = results.get("followup_recommendations", [])
305
+
306
+ if followups:
307
+ for rec in followups:
308
+ html_result += f"<li>{rec}</li>"
309
+ else:
310
+ html_result += (
311
+ "<li>No specific follow-up recommendations provided.</li>"
312
+ )
313
+
314
+ html_result += "</ul>"
315
+
316
+ # Add confidence note
317
+ confidence = results.get("severity", {}).get("confidence", 0)
318
+ html_result += f"""
319
+ <p><em>Note: This analysis has a confidence level of {confidence:.0%}.
320
+ Please consult with healthcare professionals for official diagnosis.</em></p>
321
+ """
322
+
323
+ return html_result, plot_html
324
+
325
+ except Exception as e:
326
+ self.logger.error(f"Error in multimodal analysis: {e}")
327
+ return f"Error in multimodal analysis: {str(e)}", None
328
+
329
+ def enhance_image(self, image):
330
+ """
331
+ Enhance X-ray image contrast.
332
+
333
+ Args:
334
+ image: Image file uploaded through Gradio
335
+
336
+ Returns:
337
+ PIL.Image: Enhanced image
338
+ """
339
+ try:
340
+ if image is None:
341
+ return None
342
+
343
+ # Save uploaded image to a temporary file
344
+ temp_dir = tempfile.mkdtemp()
345
+ temp_path = os.path.join(temp_dir, "upload.png")
346
+
347
+ if isinstance(image, str):
348
+ # Copy the file if it's a path
349
+ from shutil import copyfile
350
+
351
+ copyfile(image, temp_path)
352
+ else:
353
+ # Save if it's a Gradio UploadButton image
354
+ image.save(temp_path)
355
+
356
+ # Enhance image
357
+ self.logger.info(f"Enhancing image: {temp_path}")
358
+ output_path = os.path.join(temp_dir, "enhanced.png")
359
+ enhance_xray_image(temp_path, output_path)
360
+
361
+ # Load enhanced image
362
+ enhanced = Image.open(output_path)
363
+ return enhanced
364
+
365
+ except Exception as e:
366
+ self.logger.error(f"Error enhancing image: {e}")
367
+ return image # Return original image on error
368
+
369
+ def fig_to_html(self, fig):
370
+ """Convert matplotlib figure to HTML for display in Gradio."""
371
+ try:
372
+ import base64
373
+ import io
374
+
375
+ buf = io.BytesIO()
376
+ fig.savefig(buf, format="png", bbox_inches="tight")
377
+ buf.seek(0)
378
+ img_str = base64.b64encode(buf.read()).decode("utf-8")
379
+ plt.close(fig)
380
+
381
+ return f'<img src="data:image/png;base64,{img_str}" alt="Analysis Plot">'
382
+
383
+ except Exception as e:
384
+ self.logger.error(f"Error converting figure to HTML: {e}")
385
+ return "<p>Error displaying visualization.</p>"
386
+
387
+
388
+ def create_interface():
389
+ """Create and launch the Gradio interface."""
390
+
391
+ app = MediSyncApp()
392
+
393
+ # Example medical report for demo
394
+ example_report = """
395
+ CHEST X-RAY EXAMINATION
396
+
397
+ CLINICAL HISTORY: 55-year-old male with cough and fever.
398
+
399
+ FINDINGS: The heart size is at the upper limits of normal. The lungs are clear without focal consolidation,
400
+ effusion, or pneumothorax. There is mild prominence of the pulmonary vasculature. No pleural effusion is seen.
401
+ There is a small nodular opacity noted in the right lower lobe measuring approximately 8mm, which is suspicious
402
+ and warrants further investigation. The mediastinum is unremarkable. The visualized bony structures show no acute abnormalities.
403
+
404
+ IMPRESSION:
405
+ 1. Mild cardiomegaly.
406
+ 2. 8mm nodular opacity in the right lower lobe, recommend follow-up CT for further evaluation.
407
+ 3. No acute pulmonary parenchymal abnormality.
408
+
409
+ RECOMMENDATIONS: Follow-up chest CT to further characterize the nodular opacity in the right lower lobe.
410
+ """
411
+
412
+ # Get sample image path if available
413
+ sample_images_dir = Path(parent_dir) / "data" / "sample"
414
+ sample_images = list(sample_images_dir.glob("*.png")) + list(
415
+ sample_images_dir.glob("*.jpg")
416
+ )
417
+
418
+ sample_image_path = None
419
+ if sample_images:
420
+ sample_image_path = str(sample_images[0])
421
+
422
+ # Define interface
423
+ with gr.Blocks(
424
+ title="MediSync: Multi-Modal Medical Analysis System",
425
+ theme=gr.themes.Soft()
426
+ ) as interface:
427
+ # Get appointment ID from URL parameters if present
428
+ appointment_id = gr.Textbox(
429
+ visible=False,
430
+ value=gr.Request.query_params.get("appointment_id", "")
431
+ )
432
+
433
+ gr.Markdown("""
434
+ # MediSync: Multi-Modal Medical Analysis System
435
+
436
+ This AI-powered healthcare solution combines X-ray image analysis with patient report text processing
437
+ to provide comprehensive medical insights.
438
+
439
+ ## How to Use
440
+ 1. Upload a chest X-ray image
441
+ 2. Enter the corresponding medical report text
442
+ 3. Choose the analysis type: image-only, text-only, or multimodal (combined)
443
+ 4. Click "End Consultation" when finished to complete your appointment
444
+ """)
445
+
446
+ with gr.Tab("Multimodal Analysis"):
447
+ with gr.Row():
448
+ with gr.Column():
449
+ multi_img_input = gr.Image(label="Upload X-ray Image", type="pil")
450
+ multi_img_enhance = gr.Button("Enhance Image")
451
+
452
+ multi_text_input = gr.Textbox(
453
+ label="Enter Medical Report Text",
454
+ placeholder="Enter the radiologist's report text here...",
455
+ lines=10,
456
+ value=example_report if sample_image_path is None else None,
457
+ )
458
+
459
+ multi_analyze_btn = gr.Button(
460
+ "Analyze Image & Text", variant="primary"
461
+ )
462
+
463
+ with gr.Column():
464
+ multi_results = gr.HTML(label="Analysis Results")
465
+ multi_plot = gr.HTML(label="Visualization")
466
+
467
+ # Set up examples if sample image exists
468
+ if sample_image_path:
469
+ gr.Examples(
470
+ examples=[[sample_image_path, example_report]],
471
+ inputs=[multi_img_input, multi_text_input],
472
+ label="Example X-ray and Report",
473
+ )
474
+
475
+ with gr.Tab("Image Analysis"):
476
+ with gr.Row():
477
+ with gr.Column():
478
+ img_input = gr.Image(label="Upload X-ray Image", type="pil")
479
+ img_enhance = gr.Button("Enhance Image")
480
+ img_analyze_btn = gr.Button("Analyze Image", variant="primary")
481
+
482
+ with gr.Column():
483
+ img_output = gr.Image(label="Processed Image")
484
+ img_results = gr.HTML(label="Analysis Results")
485
+ img_plot = gr.HTML(label="Visualization")
486
+
487
+ # Set up example if sample image exists
488
+ if sample_image_path:
489
+ gr.Examples(
490
+ examples=[[sample_image_path]],
491
+ inputs=[img_input],
492
+ label="Example X-ray Image",
493
+ )
494
+
495
+ with gr.Tab("Text Analysis"):
496
+ with gr.Row():
497
+ with gr.Column():
498
+ text_input = gr.Textbox(
499
+ label="Enter Medical Report Text",
500
+ placeholder="Enter the radiologist's report text here...",
501
+ lines=10,
502
+ value=example_report,
503
+ )
504
+ text_analyze_btn = gr.Button("Analyze Text", variant="primary")
505
+
506
+ with gr.Column():
507
+ text_output = gr.Textbox(label="Processed Text")
508
+ text_results = gr.HTML(label="Analysis Results")
509
+ text_plot = gr.HTML(label="Entity Visualization")
510
+
511
+ # Set up example
512
+ gr.Examples(
513
+ examples=[[example_report]],
514
+ inputs=[text_input],
515
+ label="Example Medical Report",
516
+ )
517
+
518
+ with gr.Tab("About"):
519
+ gr.Markdown("""
520
+ ## About MediSync
521
+
522
+ MediSync is an AI-powered healthcare solution that uses multi-modal analysis to provide comprehensive insights from medical images and reports.
523
+
524
+ ### Key Features
525
+
526
+ - **X-ray Image Analysis**: Detects abnormalities in chest X-rays using pre-trained vision models
527
+ - **Medical Report Processing**: Extracts key information from patient reports using NLP models
528
+ - **Multi-modal Integration**: Combines insights from both image and text data for more accurate analysis
529
+
530
+ ### Models Used
531
+
532
+ - **X-ray Analysis**: facebook/deit-base-patch16-224-medical-cxr
533
+ - **Medical Text Analysis**: medicalai/ClinicalBERT
534
+
535
+ ### Important Disclaimer
536
+
537
+ This tool is for educational and research purposes only. It is not intended to provide medical advice or replace professional healthcare. Always consult with qualified healthcare providers for medical decisions.
538
+ """)
539
+
540
+ # Consultation completion section
541
+ with gr.Row():
542
+ with gr.Column():
543
+ end_consultation_btn = gr.Button(
544
+ "End Consultation",
545
+ variant="stop",
546
+ size="lg"
547
+ )
548
+ completion_status = gr.HTML()
549
+
550
+ # Set up event handlers
551
+ multi_img_enhance.click(
552
+ app.enhance_image, inputs=multi_img_input, outputs=multi_img_input
553
+ )
554
+ multi_analyze_btn.click(
555
+ app.analyze_multimodal,
556
+ inputs=[multi_img_input, multi_text_input],
557
+ outputs=[multi_results, multi_plot],
558
+ )
559
+
560
+ img_enhance.click(app.enhance_image, inputs=img_input, outputs=img_output)
561
+ img_analyze_btn.click(
562
+ app.analyze_image,
563
+ inputs=img_input,
564
+ outputs=[img_output, img_results, img_plot],
565
+ )
566
+
567
+ text_analyze_btn.click(
568
+ app.analyze_text,
569
+ inputs=text_input,
570
+ outputs=[text_output, text_results, text_plot],
571
+ )
572
+
573
+ # Handle consultation completion
574
+ end_consultation_btn.click(
575
+ fn=complete_consultation,
576
+ inputs=[appointment_id],
577
+ outputs=completion_status
578
+ )
579
+
580
+ # Run the interface
581
+ interface.launch()
582
+
583
+ def complete_consultation(appointment_id):
584
+ """Handle consultation completion by notifying the Flask app."""
585
+ if not appointment_id:
586
+ return "<div class='alert alert-error'>No appointment ID found. Please contact support.</div>"
587
+
588
+ try:
589
+ # Call your Flask app's completion endpoint
590
+ # Replace with your actual Flask app URL
591
+ flask_app_url = "http://127.0.0.1:600/complete_consultation"
592
+
593
+ response = requests.post(
594
+ flask_app_url,
595
+ json={"appointment_id": appointment_id},
596
+ timeout=10
597
+ )
598
+
599
+ if response.status_code == 200:
600
+ # Return JavaScript to redirect back to the doctors page
601
+ return """
602
+ <div class='alert alert-success'>
603
+ Consultation completed successfully. Redirecting...
604
+ <script>
605
+ setTimeout(function() {
606
+ window.location.href = "http://127.0.0.1:600/doctors";
607
+ }, 2000);
608
+ </script>
609
+ </div>
610
+ """
611
+ else:
612
+ return f"""
613
+ <div class='alert alert-error'>
614
+ Error completing appointment (Status: {response.status_code}).
615
+ Please contact support.
616
+ </div>
617
+ """
618
+
619
+ except Exception as e:
620
+ return f"""
621
+ <div class='alert alert-error'>
622
+ Error: {str(e)}
623
+ </div>
624
+ """
625
+
626
+ if __name__ == "__main__":
627
+ create_interface()