blackshadow1 commited on
Commit
d686388
·
verified ·
1 Parent(s): c9365e2

updated the application UI ✅✅

Browse files
Files changed (1) hide show
  1. mediSync/app.py +234 -176
mediSync/app.py CHANGED
@@ -386,14 +386,14 @@ os.makedirs(os.path.join(parent_dir, "data", "sample"), exist_ok=True)
386
  import logging
387
  import os
388
  import sys
 
389
  from pathlib import Path
390
  import requests
391
  import gradio as gr
392
  import matplotlib.pyplot as plt
393
  from PIL import Image
394
- import json
395
 
396
- # Import configuration
397
  try:
398
  from .config import get_flask_urls, get_doctors_page_urls, TIMEOUT_SETTINGS
399
  except ImportError:
@@ -411,9 +411,20 @@ except ImportError:
411
  }
412
  TIMEOUT_SETTINGS = {"connection_timeout": 5, "request_timeout": 10}
413
 
 
414
  parent_dir = os.path.dirname(os.path.abspath(__file__))
415
  sys.path.append(parent_dir)
416
 
 
 
 
 
 
 
 
 
 
 
417
  logging.basicConfig(
418
  level=logging.INFO,
419
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
@@ -421,224 +432,271 @@ logging.basicConfig(
421
  )
422
  logger = logging.getLogger(__name__)
423
 
 
 
 
424
  class MediSyncApp:
 
 
 
 
425
  def __init__(self):
 
426
  self.logger = logging.getLogger(__name__)
427
  self.logger.info("Initializing MediSync application")
428
- self._temp_files = []
429
  self.fusion_model = None
430
  self.image_model = None
431
  self.text_model = None
432
 
433
- def __del__(self):
434
- self.cleanup_temp_files()
435
-
436
- def cleanup_temp_files(self):
437
- for temp_file in self._temp_files:
438
- try:
439
- if os.path.exists(temp_file):
440
- os.remove(temp_file)
441
- self.logger.debug(f"Cleaned up temporary file: {temp_file}")
442
- except Exception as e:
443
- self.logger.warning(f"Failed to clean up temporary file {temp_file}: {e}")
444
- self._temp_files = []
445
-
446
  def load_models(self):
447
- if self.fusion_model is not None:
448
- return True
 
 
 
 
449
  try:
450
- self.logger.info("Loading models...")
451
- self.logger.info("Models loaded successfully (mock implementation)")
 
 
 
 
452
  return True
453
  except Exception as e:
454
  self.logger.error(f"Error loading models: {e}")
455
  return False
456
 
457
- def enhance_image(self, image):
458
- if image is None:
459
- return None
460
- try:
461
- enhanced_image = image
462
- self.logger.info("Image enhanced successfully")
463
- return enhanced_image
464
- except Exception as e:
465
- self.logger.error(f"Error enhancing image: {e}")
466
- return image
467
-
468
  def analyze_image(self, image):
469
- if image is None:
470
- return None, "Please upload an image first.", None
471
- if not self.load_models():
472
- return image, "Error: Models not loaded properly.", None
 
 
 
 
 
473
  try:
474
- self.logger.info("Analyzing image")
475
- results = {
476
- "primary_finding": "Normal chest X-ray",
477
- "confidence": 0.85,
478
- "has_abnormality": False,
479
- "predictions": [
480
- ("Normal", 0.85),
481
- ("Pneumonia", 0.10),
482
- ("Cardiomegaly", 0.05)
483
- ]
484
- }
485
- fig = self.plot_image_prediction(
 
 
 
 
 
486
  image,
487
  results.get("predictions", []),
488
- f"Primary Finding: {results.get('primary_finding', 'Unknown')}"
489
  )
490
  plot_html = self.fig_to_html(fig)
491
- plt.close(fig)
492
- html_result = self.format_image_results(results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  return image, html_result, plot_html
494
  except Exception as e:
495
  self.logger.error(f"Error in image analysis: {e}")
496
  return image, f"Error analyzing image: {str(e)}", None
497
 
498
  def analyze_text(self, text):
499
- if not text or text.strip() == "":
500
- return "", "Please enter medical report text.", None
501
- if not self.load_models():
502
- return text, "Error: Models not loaded properly.", None
 
 
 
 
 
503
  try:
504
- self.logger.info("Analyzing text")
505
- results = {
506
- "entities": [
507
- {"text": "chest X-ray", "type": "PROCEDURE", "confidence": 0.95},
508
- {"text": "55-year-old male", "type": "PATIENT", "confidence": 0.90},
509
- {"text": "cough and fever", "type": "SYMPTOM", "confidence": 0.88}
510
- ],
511
- "sentiment": "neutral",
512
- "key_findings": ["Normal heart size", "Clear lungs", "8mm nodular opacity"]
513
- }
514
- html_result = self.format_text_results(results)
515
- plot_html = self.create_entity_visualization(results["entities"])
516
- return text, html_result, plot_html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  except Exception as e:
518
  self.logger.error(f"Error in text analysis: {e}")
519
  return text, f"Error analyzing text: {str(e)}", None
520
 
521
  def analyze_multimodal(self, image, text):
522
- if image is None and (not text or text.strip() == ""):
523
- return "Please provide either an image or text for analysis.", None
524
- if not self.load_models():
525
- return "Error: Models not loaded properly.", None
 
 
 
 
 
 
526
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
  self.logger.info("Performing multimodal analysis")
528
- results = {
529
- "combined_finding": "Normal chest X-ray with minor findings",
530
- "confidence": 0.92,
531
- "image_contribution": "Normal cardiac silhouette and clear lung fields",
532
- "text_contribution": "Clinical history supports normal findings",
533
- "recommendations": [
534
- "Follow-up CT for the 8mm nodular opacity",
535
- "Monitor for any changes in symptoms"
536
- ]
537
- }
538
- html_result = self.format_multimodal_results(results)
539
- plot_html = self.create_multimodal_visualization(results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  return html_result, plot_html
541
  except Exception as e:
542
  self.logger.error(f"Error in multimodal analysis: {e}")
543
  return f"Error in multimodal analysis: {str(e)}", None
544
 
545
- def format_image_results(self, results):
546
- html_result = f"""
547
- <div class="medisync-card medisync-card-bg medisync-force-text">
548
- <h2 class="medisync-title medisync-blue">X-ray Analysis Results</h2>
549
- <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
550
- <p><strong>Confidence:</strong> {results.get("confidence", 0):.1%}</p>
551
- <p><strong>Abnormality Detected:</strong> {"Yes" if results.get("has_abnormality", False) else "No"}</p>
552
- <h3>Top Predictions:</h3>
553
- <ul>
554
- """
555
- for label, prob in results.get("predictions", [])[:5]:
556
- html_result += f"<li>{label}: {prob:.1%}</li>"
557
- html_result += "</ul></div>"
558
- return html_result
559
-
560
- def format_text_results(self, results):
561
- html_result = f"""
562
- <div class="medisync-card medisync-card-bg medisync-force-text">
563
- <h2 class="medisync-title medisync-green">Text Analysis Results</h2>
564
- <p><strong>Sentiment:</strong> {results.get("sentiment", "Unknown").title()}</p>
565
- <h3>Key Findings:</h3>
566
- <ul>
567
  """
568
- for finding in results.get("key_findings", []):
569
- html_result += f"<li>{finding}</li>"
570
- html_result += "</ul>"
571
- html_result += "<h3>Extracted Entities:</h3><ul>"
572
- for entity in results.get("entities", [])[:5]:
573
- html_result += f"<li><strong>{entity['text']}</strong> ({entity['type']}) - {entity['confidence']:.1%}</li>"
574
- html_result += "</ul></div>"
575
- return html_result
576
-
577
- def format_multimodal_results(self, results):
578
- html_result = f"""
579
- <div class="medisync-card medisync-card-bg medisync-force-text">
580
- <h2 class="medisync-title medisync-purple">Multimodal Analysis Results</h2>
581
- <p><strong>Combined Finding:</strong> {results.get("combined_finding", "Unknown")}</p>
582
- <p><strong>Overall Confidence:</strong> {results.get("confidence", 0):.1%}</p>
583
- <h3>Image Contribution:</h3>
584
- <p>{results.get("image_contribution", "No image analysis available")}</p>
585
- <h3>Text Contribution:</h3>
586
- <p>{results.get("text_contribution", "No text analysis available")}</p>
587
- <h3>Recommendations:</h3>
588
- <ul>
589
  """
590
- for rec in results.get("recommendations", []):
591
- html_result += f"<li>{rec}</li>"
592
- html_result += "</ul></div>"
593
- return html_result
594
-
595
- def plot_image_prediction(self, image, predictions, title):
596
- fig, ax = plt.subplots(figsize=(10, 6))
597
- ax.imshow(image)
598
- ax.set_title(title, fontsize=14, fontweight='bold', color='#007bff')
599
- ax.axis('off')
600
- return fig
601
-
602
- def create_entity_visualization(self, entities):
603
- if not entities:
604
- return "<p>No entities found in text.</p>"
605
- fig, ax = plt.subplots(figsize=(10, 6))
606
- entity_types = {}
607
- for entity in entities:
608
- entity_type = entity['type']
609
- if entity_type not in entity_types:
610
- entity_types[entity_type] = 0
611
- entity_types[entity_type] += 1
612
- if entity_types:
613
- ax.bar(entity_types.keys(), entity_types.values(), color='#00bfae')
614
- ax.set_title('Entity Types Found in Text', fontsize=14, fontweight='bold', color='#00bfae')
615
- ax.set_ylabel('Count', color='#00bfae')
616
- plt.xticks(rotation=45, color='#222')
617
- plt.yticks(color='#222')
618
- return self.fig_to_html(fig)
619
-
620
- def create_multimodal_visualization(self, results):
621
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
622
- confidence = results.get("confidence", 0)
623
- ax1.pie([confidence, 1-confidence], labels=['Confidence', 'Uncertainty'],
624
- colors=['#00bfae', '#ff7675'], autopct='%1.1f%%', textprops={'color': '#222'})
625
- ax1.set_title('Analysis Confidence', fontweight='bold', color='#00bfae')
626
- recommendations = results.get("recommendations", [])
627
- ax2.bar(['Recommendations'], [len(recommendations)], color='#6c63ff')
628
- ax2.set_title('Number of Recommendations', fontweight='bold', color='#6c63ff')
629
- ax2.set_ylabel('Count', color='#6c63ff')
630
- plt.tight_layout()
631
- return self.fig_to_html(fig)
632
 
633
  def fig_to_html(self, fig):
634
- import io
635
- import base64
636
- buf = io.BytesIO()
637
- fig.savefig(buf, format='png', bbox_inches='tight', dpi=100, facecolor=fig.get_facecolor())
638
- buf.seek(0)
639
- img_str = base64.b64encode(buf.read()).decode()
640
- buf.close()
641
- return f'<img src="data:image/png;base64,{img_str}" style="max-width: 100%; height: auto; background: transparent;"/>'
 
 
 
 
 
642
 
643
  def complete_appointment(appointment_id):
644
  try:
 
386
  import logging
387
  import os
388
  import sys
389
+ import tempfile
390
  from pathlib import Path
391
  import requests
392
  import gradio as gr
393
  import matplotlib.pyplot as plt
394
  from PIL import Image
 
395
 
396
+ # Import configuration for end consultation logic
397
  try:
398
  from .config import get_flask_urls, get_doctors_page_urls, TIMEOUT_SETTINGS
399
  except ImportError:
 
411
  }
412
  TIMEOUT_SETTINGS = {"connection_timeout": 5, "request_timeout": 10}
413
 
414
+ # Add parent directory to path
415
  parent_dir = os.path.dirname(os.path.abspath(__file__))
416
  sys.path.append(parent_dir)
417
 
418
+ # Import our modules for model and utility logic
419
+ from models.multimodal_fusion import MultimodalFusion
420
+ from utils.preprocessing import enhance_xray_image, normalize_report_text
421
+ from utils.visualization import (
422
+ plot_image_prediction,
423
+ plot_multimodal_results,
424
+ plot_report_entities,
425
+ )
426
+
427
+ # Set up logging
428
  logging.basicConfig(
429
  level=logging.INFO,
430
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 
432
  )
433
  logger = logging.getLogger(__name__)
434
 
435
+ # Ensure sample data directory exists
436
+ os.makedirs(os.path.join(parent_dir, "data", "sample"), exist_ok=True)
437
+
438
  class MediSyncApp:
439
+ """
440
+ Main application class for the MediSync multi-modal medical analysis system.
441
+ """
442
+
443
  def __init__(self):
444
+ """Initialize the application and load models."""
445
  self.logger = logging.getLogger(__name__)
446
  self.logger.info("Initializing MediSync application")
 
447
  self.fusion_model = None
448
  self.image_model = None
449
  self.text_model = None
450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  def load_models(self):
452
+ """
453
+ Load models if not already loaded.
454
+
455
+ Returns:
456
+ bool: True if models loaded successfully, False otherwise
457
+ """
458
  try:
459
+ if self.fusion_model is None:
460
+ self.logger.info("Loading models...")
461
+ self.fusion_model = MultimodalFusion()
462
+ self.image_model = self.fusion_model.image_analyzer
463
+ self.text_model = self.fusion_model.text_analyzer
464
+ self.logger.info("Models loaded successfully")
465
  return True
466
  except Exception as e:
467
  self.logger.error(f"Error loading models: {e}")
468
  return False
469
 
 
 
 
 
 
 
 
 
 
 
 
470
  def analyze_image(self, image):
471
+ """
472
+ Analyze a medical image.
473
+
474
+ Args:
475
+ image: Image file uploaded through Gradio
476
+
477
+ Returns:
478
+ tuple: (image, image_results_html, plot_as_html)
479
+ """
480
  try:
481
+ if image is None:
482
+ return None, "Please upload an image first.", None
483
+ if not self.load_models() or self.image_model is None:
484
+ return image, "Error: Models not loaded properly.", None
485
+
486
+ temp_dir = tempfile.mkdtemp()
487
+ temp_path = os.path.join(temp_dir, "upload.png")
488
+ if isinstance(image, str):
489
+ from shutil import copyfile
490
+ copyfile(image, temp_path)
491
+ else:
492
+ image.save(temp_path)
493
+
494
+ self.logger.info(f"Analyzing image: {temp_path}")
495
+ results = self.image_model.analyze(temp_path)
496
+
497
+ fig = plot_image_prediction(
498
  image,
499
  results.get("predictions", []),
500
+ f"Primary Finding: {results.get('primary_finding', 'Unknown')}",
501
  )
502
  plot_html = self.fig_to_html(fig)
503
+
504
+ html_result = f"""
505
+ <div class="medisync-card medisync-card-bg medisync-force-text">
506
+ <h2 class="medisync-title medisync-blue">X-ray Analysis Results</h2>
507
+ <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
508
+ <p><strong>Confidence:</strong> {results.get("confidence", 0):.1%}</p>
509
+ <p><strong>Abnormality Detected:</strong> {"Yes" if results.get("has_abnormality", False) else "No"}</p>
510
+ <h3>Top Predictions:</h3>
511
+ <ul>
512
+ """
513
+ for label, prob in results.get("predictions", [])[:5]:
514
+ html_result += f"<li>{label}: {prob:.1%}</li>"
515
+ html_result += "</ul>"
516
+ explanation = self.image_model.get_explanation(results)
517
+ html_result += f"<h3>Analysis Explanation:</h3><p>{explanation}</p>"
518
+ html_result += "</div>"
519
  return image, html_result, plot_html
520
  except Exception as e:
521
  self.logger.error(f"Error in image analysis: {e}")
522
  return image, f"Error analyzing image: {str(e)}", None
523
 
524
  def analyze_text(self, text):
525
+ """
526
+ Analyze a medical report text.
527
+
528
+ Args:
529
+ text: Report text input through Gradio
530
+
531
+ Returns:
532
+ tuple: (text, text_results_html, entities_plot_html)
533
+ """
534
  try:
535
+ if not text or text.strip() == "":
536
+ return "", "Please enter medical report text.", None
537
+ if not self.load_models() or self.text_model is None:
538
+ return text, "Error: Models not loaded properly.", None
539
+ if not text or len(text.strip()) < 10:
540
+ return (
541
+ text,
542
+ "Error: Please enter a valid medical report text (at least 10 characters).",
543
+ None,
544
+ )
545
+ normalized_text = normalize_report_text(text)
546
+ self.logger.info("Analyzing medical report text")
547
+ results = self.text_model.analyze(normalized_text)
548
+ entities = results.get("entities", {})
549
+ fig = plot_report_entities(normalized_text, entities)
550
+ entities_plot_html = self.fig_to_html(fig)
551
+ html_result = f"""
552
+ <div class="medisync-card medisync-card-bg medisync-force-text">
553
+ <h2 class="medisync-title medisync-green">Text Analysis Results</h2>
554
+ <p><strong>Severity Level:</strong> {results.get("severity", {}).get("level", "Unknown")}</p>
555
+ <p><strong>Severity Score:</strong> {results.get("severity", {}).get("score", 0)}/4</p>
556
+ <p><strong>Confidence:</strong> {results.get("severity", {}).get("confidence", 0):.1%}</p>
557
+ <h3>Key Findings:</h3>
558
+ <ul>
559
+ """
560
+ findings = results.get("findings", [])
561
+ if findings:
562
+ for finding in findings:
563
+ html_result += f"<li>{finding}</li>"
564
+ else:
565
+ html_result += "<li>No specific findings detailed.</li>"
566
+ html_result += "</ul>"
567
+ html_result += "<h3>Extracted Medical Entities:</h3>"
568
+ for category, items in entities.items():
569
+ if items:
570
+ html_result += f"<p><strong>{category.capitalize()}:</strong> {', '.join(items)}</p>"
571
+ html_result += "<h3>Follow-up Recommendations:</h3><ul>"
572
+ followups = results.get("followup_recommendations", [])
573
+ if followups:
574
+ for rec in followups:
575
+ html_result += f"<li>{rec}</li>"
576
+ else:
577
+ html_result += "<li>No specific follow-up recommendations.</li>"
578
+ html_result += "</ul></div>"
579
+ return text, html_result, entities_plot_html
580
  except Exception as e:
581
  self.logger.error(f"Error in text analysis: {e}")
582
  return text, f"Error analyzing text: {str(e)}", None
583
 
584
  def analyze_multimodal(self, image, text):
585
+ """
586
+ Perform multimodal analysis of image and text.
587
+
588
+ Args:
589
+ image: Image file uploaded through Gradio
590
+ text: Report text input through Gradio
591
+
592
+ Returns:
593
+ tuple: (results_html, multimodal_plot_html)
594
+ """
595
  try:
596
+ if not self.load_models() or self.fusion_model is None:
597
+ return "Error: Models not loaded properly.", None
598
+ if image is None:
599
+ return "Error: Please upload an X-ray image for analysis.", None
600
+ if not text or len(text.strip()) < 10:
601
+ return (
602
+ "Error: Please enter a valid medical report text (at least 10 characters).",
603
+ None,
604
+ )
605
+ temp_dir = tempfile.mkdtemp()
606
+ temp_path = os.path.join(temp_dir, "upload.png")
607
+ if isinstance(image, str):
608
+ from shutil import copyfile
609
+ copyfile(image, temp_path)
610
+ else:
611
+ image.save(temp_path)
612
+ normalized_text = normalize_report_text(text)
613
  self.logger.info("Performing multimodal analysis")
614
+ results = self.fusion_model.analyze(temp_path, normalized_text)
615
+ fig = plot_multimodal_results(results, image, text)
616
+ plot_html = self.fig_to_html(fig)
617
+ explanation = self.fusion_model.get_explanation(results)
618
+ html_result = f"""
619
+ <div class="medisync-card medisync-card-bg medisync-force-text">
620
+ <h2 class="medisync-title medisync-purple">Multimodal Analysis Results</h2>
621
+ <h3>Overview</h3>
622
+ <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
623
+ <p><strong>Severity Level:</strong> {results.get("severity", {}).get("level", "Unknown")}</p>
624
+ <p><strong>Severity Score:</strong> {results.get("severity", {}).get("score", 0)}/4</p>
625
+ <p><strong>Agreement Score:</strong> {results.get("agreement_score", 0):.0%}</p>
626
+ <h3>Detailed Findings</h3>
627
+ <ul>
628
+ """
629
+ findings = results.get("findings", [])
630
+ if findings:
631
+ for finding in findings:
632
+ html_result += f"<li>{finding}</li>"
633
+ else:
634
+ html_result += "<li>No specific findings detailed.</li>"
635
+ html_result += "</ul>"
636
+ html_result += "<h3>Recommended Follow-up</h3><ul>"
637
+ followups = results.get("followup_recommendations", [])
638
+ if followups:
639
+ for rec in followups:
640
+ html_result += f"<li>{rec}</li>"
641
+ else:
642
+ html_result += "<li>No specific follow-up recommendations provided.</li>"
643
+ html_result += "</ul>"
644
+ confidence = results.get("severity", {}).get("confidence", 0)
645
+ html_result += f"""
646
+ <p><em>Note: This analysis has a confidence level of {confidence:.0%}.
647
+ Please consult with healthcare professionals for official diagnosis.</em></p>
648
+ <h3>Analysis Explanation:</h3>
649
+ <p>{explanation}</p>
650
+ </div>
651
+ """
652
  return html_result, plot_html
653
  except Exception as e:
654
  self.logger.error(f"Error in multimodal analysis: {e}")
655
  return f"Error in multimodal analysis: {str(e)}", None
656
 
657
+ def enhance_image(self, image):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
658
  """
659
+ Enhance X-ray image contrast.
660
+
661
+ Args:
662
+ image: Image file uploaded through Gradio
663
+
664
+ Returns:
665
+ PIL.Image: Enhanced image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  """
667
+ try:
668
+ if image is None:
669
+ return None
670
+ temp_dir = tempfile.mkdtemp()
671
+ temp_path = os.path.join(temp_dir, "upload.png")
672
+ if isinstance(image, str):
673
+ from shutil import copyfile
674
+ copyfile(image, temp_path)
675
+ else:
676
+ image.save(temp_path)
677
+ self.logger.info(f"Enhancing image: {temp_path}")
678
+ output_path = os.path.join(temp_dir, "enhanced.png")
679
+ enhance_xray_image(temp_path, output_path)
680
+ enhanced = Image.open(output_path)
681
+ return enhanced
682
+ except Exception as e:
683
+ self.logger.error(f"Error enhancing image: {e}")
684
+ return image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
 
686
  def fig_to_html(self, fig):
687
+ """Convert matplotlib figure to HTML for display in Gradio."""
688
+ try:
689
+ import base64
690
+ import io
691
+ buf = io.BytesIO()
692
+ fig.savefig(buf, format="png", bbox_inches="tight", dpi=100, facecolor=fig.get_facecolor())
693
+ buf.seek(0)
694
+ img_str = base64.b64encode(buf.read()).decode("utf-8")
695
+ plt.close(fig)
696
+ return f'<img src="data:image/png;base64,{img_str}" style="max-width: 100%; height: auto; background: transparent;"/>'
697
+ except Exception as e:
698
+ self.logger.error(f"Error converting figure to HTML: {e}")
699
+ return "<p>Error displaying visualization.</p>"
700
 
701
  def complete_appointment(appointment_id):
702
  try: