blackshadow1 commited on
Commit
deca3e2
·
verified ·
1 Parent(s): 9cd286b

updated old UI ✅✅

Browse files
Files changed (1) hide show
  1. mediSync/app.py +416 -194
mediSync/app.py CHANGED
@@ -383,11 +383,10 @@ os.makedirs(os.path.join(parent_dir, "data", "sample"), exist_ok=True)
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
  import logging
389
  import os
390
  import sys
 
391
  from pathlib import Path
392
  import requests
393
  import gradio as gr
@@ -399,6 +398,7 @@ import json
399
  try:
400
  from .config import get_flask_urls, get_doctors_page_urls, TIMEOUT_SETTINGS
401
  except ImportError:
 
402
  def get_flask_urls():
403
  return [
404
  "http://127.0.0.1:600/complete_appointment",
@@ -406,16 +406,20 @@ except ImportError:
406
  "https://your-flask-app-domain.com/complete_appointment",
407
  "http://your-flask-app-ip:600/complete_appointment"
408
  ]
 
409
  def get_doctors_page_urls():
410
  return {
411
  "local": "http://127.0.0.1:600/doctors",
412
  "production": "https://your-flask-app-domain.com/doctors"
413
  }
 
414
  TIMEOUT_SETTINGS = {"connection_timeout": 5, "request_timeout": 10}
415
 
 
416
  parent_dir = os.path.dirname(os.path.abspath(__file__))
417
  sys.path.append(parent_dir)
418
 
 
419
  logging.basicConfig(
420
  level=logging.INFO,
421
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
@@ -424,18 +428,25 @@ logging.basicConfig(
424
  logger = logging.getLogger(__name__)
425
 
426
  class MediSyncApp:
 
 
 
 
427
  def __init__(self):
 
428
  self.logger = logging.getLogger(__name__)
429
  self.logger.info("Initializing MediSync application")
430
- self._temp_files = []
431
  self.fusion_model = None
432
  self.image_model = None
433
  self.text_model = None
434
 
435
  def __del__(self):
 
436
  self.cleanup_temp_files()
437
 
438
  def cleanup_temp_files(self):
 
439
  for temp_file in self._temp_files:
440
  try:
441
  if os.path.exists(temp_file):
@@ -446,10 +457,19 @@ class MediSyncApp:
446
  self._temp_files = []
447
 
448
  def load_models(self):
 
 
 
 
 
 
449
  if self.fusion_model is not None:
450
  return True
 
451
  try:
452
  self.logger.info("Loading models...")
 
 
453
  self.logger.info("Models loaded successfully (mock implementation)")
454
  return True
455
  except Exception as e:
@@ -457,9 +477,12 @@ class MediSyncApp:
457
  return False
458
 
459
  def enhance_image(self, image):
 
460
  if image is None:
461
  return None
 
462
  try:
 
463
  enhanced_image = image
464
  self.logger.info("Image enhanced successfully")
465
  return enhanced_image
@@ -468,12 +491,25 @@ class MediSyncApp:
468
  return image
469
 
470
  def analyze_image(self, image):
 
 
 
 
 
 
 
 
 
471
  if image is None:
472
  return None, "Please upload an image first.", None
 
473
  if not self.load_models():
474
  return image, "Error: Models not loaded properly.", None
 
475
  try:
476
  self.logger.info("Analyzing image")
 
 
477
  results = {
478
  "primary_finding": "Normal chest X-ray",
479
  "confidence": 0.85,
@@ -484,26 +520,47 @@ class MediSyncApp:
484
  ("Cardiomegaly", 0.05)
485
  ]
486
  }
 
 
487
  fig = self.plot_image_prediction(
488
  image,
489
  results.get("predictions", []),
490
  f"Primary Finding: {results.get('primary_finding', 'Unknown')}"
491
  )
 
 
492
  plot_html = self.fig_to_html(fig)
493
- plt.close(fig)
 
 
494
  html_result = self.format_image_results(results)
 
495
  return image, html_result, plot_html
 
496
  except Exception as e:
497
  self.logger.error(f"Error in image analysis: {e}")
498
  return image, f"Error analyzing image: {str(e)}", None
499
 
500
  def analyze_text(self, text):
 
 
 
 
 
 
 
 
 
501
  if not text or text.strip() == "":
502
  return "", "Please enter medical report text.", None
 
503
  if not self.load_models():
504
  return text, "Error: Models not loaded properly.", None
 
505
  try:
506
  self.logger.info("Analyzing text")
 
 
507
  results = {
508
  "entities": [
509
  {"text": "chest X-ray", "type": "PROCEDURE", "confidence": 0.95},
@@ -513,20 +570,40 @@ class MediSyncApp:
513
  "sentiment": "neutral",
514
  "key_findings": ["Normal heart size", "Clear lungs", "8mm nodular opacity"]
515
  }
 
 
516
  html_result = self.format_text_results(results)
 
 
517
  plot_html = self.create_entity_visualization(results["entities"])
 
518
  return text, html_result, plot_html
 
519
  except Exception as e:
520
  self.logger.error(f"Error in text analysis: {e}")
521
  return text, f"Error analyzing text: {str(e)}", None
522
 
523
  def analyze_multimodal(self, image, text):
 
 
 
 
 
 
 
 
 
 
524
  if image is None and (not text or text.strip() == ""):
525
  return "Please provide either an image or text for analysis.", None
 
526
  if not self.load_models():
527
  return "Error: Models not loaded properly.", None
 
528
  try:
529
  self.logger.info("Performing multimodal analysis")
 
 
530
  results = {
531
  "combined_finding": "Normal chest X-ray with minor findings",
532
  "confidence": 0.92,
@@ -537,119 +614,168 @@ class MediSyncApp:
537
  "Monitor for any changes in symptoms"
538
  ]
539
  }
 
 
540
  html_result = self.format_multimodal_results(results)
 
 
541
  plot_html = self.create_multimodal_visualization(results)
 
542
  return html_result, plot_html
 
543
  except Exception as e:
544
  self.logger.error(f"Error in multimodal analysis: {e}")
545
  return f"Error in multimodal analysis: {str(e)}", None
546
 
547
  def format_image_results(self, results):
 
548
  html_result = f"""
549
- <div class="medisync-card medisync-card-bg">
550
- <h2 class="medisync-title medisync-blue">X-ray Analysis Results</h2>
551
  <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
552
  <p><strong>Confidence:</strong> {results.get("confidence", 0):.1%}</p>
553
  <p><strong>Abnormality Detected:</strong> {"Yes" if results.get("has_abnormality", False) else "No"}</p>
 
554
  <h3>Top Predictions:</h3>
555
  <ul>
556
  """
 
557
  for label, prob in results.get("predictions", [])[:5]:
558
  html_result += f"<li>{label}: {prob:.1%}</li>"
 
559
  html_result += "</ul></div>"
560
  return html_result
561
 
562
  def format_text_results(self, results):
 
563
  html_result = f"""
564
- <div class="medisync-card medisync-card-bg">
565
- <h2 class="medisync-title medisync-green">Text Analysis Results</h2>
566
  <p><strong>Sentiment:</strong> {results.get("sentiment", "Unknown").title()}</p>
 
567
  <h3>Key Findings:</h3>
568
  <ul>
569
  """
 
570
  for finding in results.get("key_findings", []):
571
  html_result += f"<li>{finding}</li>"
 
572
  html_result += "</ul>"
 
573
  html_result += "<h3>Extracted Entities:</h3><ul>"
574
  for entity in results.get("entities", [])[:5]:
575
  html_result += f"<li><strong>{entity['text']}</strong> ({entity['type']}) - {entity['confidence']:.1%}</li>"
 
576
  html_result += "</ul></div>"
577
  return html_result
578
 
579
  def format_multimodal_results(self, results):
 
580
  html_result = f"""
581
- <div class="medisync-card medisync-card-bg">
582
- <h2 class="medisync-title medisync-purple">Multimodal Analysis Results</h2>
583
  <p><strong>Combined Finding:</strong> {results.get("combined_finding", "Unknown")}</p>
584
  <p><strong>Overall Confidence:</strong> {results.get("confidence", 0):.1%}</p>
 
585
  <h3>Image Contribution:</h3>
586
  <p>{results.get("image_contribution", "No image analysis available")}</p>
 
587
  <h3>Text Contribution:</h3>
588
  <p>{results.get("text_contribution", "No text analysis available")}</p>
 
589
  <h3>Recommendations:</h3>
590
  <ul>
591
  """
 
592
  for rec in results.get("recommendations", []):
593
  html_result += f"<li>{rec}</li>"
 
594
  html_result += "</ul></div>"
595
  return html_result
596
 
597
  def plot_image_prediction(self, image, predictions, title):
 
598
  fig, ax = plt.subplots(figsize=(10, 6))
599
  ax.imshow(image)
600
- ax.set_title(title, fontsize=14, fontweight='bold', color='#007bff')
601
  ax.axis('off')
602
  return fig
603
 
604
  def create_entity_visualization(self, entities):
 
605
  if not entities:
606
  return "<p>No entities found in text.</p>"
 
607
  fig, ax = plt.subplots(figsize=(10, 6))
 
608
  entity_types = {}
609
  for entity in entities:
610
  entity_type = entity['type']
611
  if entity_type not in entity_types:
612
  entity_types[entity_type] = 0
613
  entity_types[entity_type] += 1
 
614
  if entity_types:
615
- ax.bar(entity_types.keys(), entity_types.values(), color='#00bfae')
616
- ax.set_title('Entity Types Found in Text', fontsize=14, fontweight='bold', color='#00bfae')
617
- ax.set_ylabel('Count', color='#00bfae')
618
- plt.xticks(rotation=45, color='#222')
619
- plt.yticks(color='#222')
620
  return self.fig_to_html(fig)
621
 
622
  def create_multimodal_visualization(self, results):
 
623
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
 
 
624
  confidence = results.get("confidence", 0)
625
- ax1.pie([confidence, 1-confidence], labels=['Confidence', 'Uncertainty'],
626
- colors=['#00bfae', '#ff7675'], autopct='%1.1f%%', textprops={'color': '#222'})
627
- ax1.set_title('Analysis Confidence', fontweight='bold', color='#00bfae')
 
 
628
  recommendations = results.get("recommendations", [])
629
- ax2.bar(['Recommendations'], [len(recommendations)], color='#6c63ff')
630
- ax2.set_title('Number of Recommendations', fontweight='bold', color='#6c63ff')
631
- ax2.set_ylabel('Count', color='#6c63ff')
 
632
  plt.tight_layout()
633
  return self.fig_to_html(fig)
634
 
635
  def fig_to_html(self, fig):
 
636
  import io
637
  import base64
 
638
  buf = io.BytesIO()
639
- fig.savefig(buf, format='png', bbox_inches='tight', dpi=100, facecolor=fig.get_facecolor())
640
  buf.seek(0)
641
  img_str = base64.b64encode(buf.read()).decode()
642
  buf.close()
643
- return f'<img src="data:image/png;base64,{img_str}" style="max-width: 100%; height: auto; background: transparent;"/>'
 
644
 
645
  def complete_appointment(appointment_id):
 
 
 
 
 
 
 
 
 
646
  try:
 
647
  flask_urls = get_flask_urls()
 
648
  payload = {"appointment_id": appointment_id}
 
649
  for flask_api_url in flask_urls:
650
  try:
651
  logger.info(f"Trying to connect to: {flask_api_url}")
652
  response = requests.post(flask_api_url, json=payload, timeout=TIMEOUT_SETTINGS["connection_timeout"])
 
653
  if response.status_code == 200:
654
  return {"status": "success", "message": "Appointment completed successfully"}
655
  elif response.status_code == 404:
@@ -657,6 +783,7 @@ def complete_appointment(appointment_id):
657
  else:
658
  logger.warning(f"Unexpected response from {flask_api_url}: {response.status_code}")
659
  continue
 
660
  except requests.exceptions.ConnectionError:
661
  logger.warning(f"Connection failed to {flask_api_url}")
662
  continue
@@ -666,168 +793,114 @@ def complete_appointment(appointment_id):
666
  except Exception as e:
667
  logger.warning(f"Error with {flask_api_url}: {e}")
668
  continue
 
 
669
  return {
670
- "status": "error",
671
  "message": "Cannot connect to Flask app. Please ensure the Flask app is running and accessible."
672
  }
 
673
  except Exception as e:
674
  logger.error(f"Error completing appointment: {e}")
675
  return {"status": "error", "message": f"Error: {str(e)}"}
676
 
677
  def create_interface():
 
 
678
  app = MediSyncApp()
 
 
679
  example_report = """
680
  CHEST X-RAY EXAMINATION
681
-
682
  CLINICAL HISTORY: 55-year-old male with cough and fever.
683
-
684
  FINDINGS: The heart size is at the upper limits of normal. The lungs are clear without focal consolidation,
685
  effusion, or pneumothorax. There is mild prominence of the pulmonary vasculature. No pleural effusion is seen.
686
  There is a small nodular opacity noted in the right lower lobe measuring approximately 8mm, which is suspicious
687
  and warrants further investigation. The mediastinum is unremarkable. The visualized bony structures show no acute abnormalities.
688
-
689
  IMPRESSION:
690
  1. Mild cardiomegaly.
691
  2. 8mm nodular opacity in the right lower lobe, recommend follow-up CT for further evaluation.
692
  3. No acute pulmonary parenchymal abnormality.
693
-
694
  RECOMMENDATIONS: Follow-up chest CT to further characterize the nodular opacity in the right lower lobe.
695
  """
696
 
 
697
  sample_images_dir = Path(parent_dir) / "data" / "sample"
698
- sample_images = list(sample_images_dir.glob("*.png")) + list(sample_images_dir.glob("*.jpg"))
699
- sample_image_path = str(sample_images[0]) if sample_images else None
 
 
 
 
 
700
 
 
701
  with gr.Blocks(
702
- title="MediSync: Multi-Modal Medical Analysis System",
703
- theme=gr.themes.Default(), # Use Default for HuggingFace dark/light support
704
- css="""
705
- /* Modern neumorphic card style for all result containers */
706
- .medisync-card {
707
- border-radius: 18px;
708
- box-shadow: 0 4px 24px 0 rgba(0,0,0,0.10), 0 1.5px 4px 0 rgba(0,191,174,0.08);
709
- margin: 18px 0;
710
- padding: 24px 24px 18px 24px;
711
- font-size: 1.08rem;
712
- transition: background 0.2s, color 0.2s;
713
- }
714
- .medisync-card-bg {
715
- background: var(--background-fill-primary, #f8f9fa);
716
- color: var(--body-text-color, #222);
717
- }
718
- .medisync-title {
719
- font-weight: 700;
720
- margin-bottom: 0.7em;
721
- }
722
- .medisync-blue { color: #00bfae; }
723
- .medisync-green { color: #28a745; }
724
- .medisync-purple { color: #6c63ff; }
725
- .medisync-card ul, .medisync-card ol {
726
- margin-left: 1.2em;
727
- }
728
- .medisync-card li {
729
- margin-bottom: 0.2em;
730
- }
731
- /* Button and input styling for modern look */
732
- .gr-button, .end-consultation-btn {
733
- border-radius: 8px !important;
734
- font-weight: 600 !important;
735
- font-size: 1.08rem !important;
736
- transition: background 0.2s, color 0.2s;
737
- }
738
- .end-consultation-btn {
739
- background: linear-gradient(90deg, #dc3545 60%, #ff7675 100%) !important;
740
- border: none !important;
741
- color: #fff !important;
742
- box-shadow: 0 2px 8px 0 rgba(220,53,69,0.10);
743
- }
744
- .end-consultation-btn:hover {
745
- background: linear-gradient(90deg, #c82333 60%, #ff7675 100%) !important;
746
- }
747
- /* Responsive tweaks */
748
- @media (max-width: 900px) {
749
- .medisync-card { padding: 16px 8px 12px 8px; }
750
- }
751
- /* Ensure text is visible in dark mode */
752
- html[data-theme="dark"] .medisync-card-bg {
753
- background: #23272f !important;
754
- color: #f8fafc !important;
755
- }
756
- html[data-theme="dark"] .medisync-title {
757
- color: #00bfae !important;
758
- }
759
- html[data-theme="dark"] .medisync-blue { color: #00bfae !important; }
760
- html[data-theme="dark"] .medisync-green { color: #00e676 !important; }
761
- html[data-theme="dark"] .medisync-purple { color: #a385ff !important; }
762
- /* Make sure all gradio labels and text are visible */
763
- label, .gr-label, .gr-text, .gr-html, .gr-markdown {
764
- color: var(--body-text-color, #222) !important;
765
- }
766
- html[data-theme="dark"] label, html[data-theme="dark"] .gr-label, html[data-theme="dark"] .gr-text, html[data-theme="dark"] .gr-html, html[data-theme="dark"] .gr-markdown {
767
- color: #f8fafc !important;
768
- }
769
- """
770
  ) as interface:
771
- gr.Markdown(
772
- """
773
- <div style="display: flex; align-items: center; gap: 16px; margin-bottom: 0.5em;">
774
- <img src="https://cdn.jsdelivr.net/gh/saqib-ali-buriro/medivance-assets/medivance_logo.png" alt="Medivance Logo" style="height: 38px; border-radius: 8px; background: #fff; box-shadow: 0 2px 8px 0 rgba(26,115,232,0.10);">
775
- <span style="font-size: 2.1rem; font-weight: 700; color: #00bfae;">MediSync</span>
776
- </div>
777
- <div style="font-size: 1.18rem; margin-bottom: 1.2em;">
778
- <span style="color: var(--body-text-color, #222);">AI-powered Multi-Modal Medical Analysis System</span>
779
- </div>
780
- <div style="font-size: 1.05rem; margin-bottom: 1.2em;">
781
- <span style="color: var(--body-text-color, #222);">Seamlessly analyze X-ray images and medical reports for comprehensive healthcare insights.</span>
782
- </div>
783
- <div style="margin-bottom: 1.2em;">
784
- <ul style="font-size: 1.01rem; color: var(--body-text-color, #222);">
785
- <li>Upload a chest X-ray image</li>
786
- <li>Enter the corresponding medical report text</li>
787
- <li>Choose the analysis type: <b>Image</b>, <b>Text</b>, or <b>Multimodal</b></li>
788
- <li>Click <b>End Consultation</b> to complete your appointment</li>
789
- </ul>
790
- </div>
791
- """,
792
- elem_id="medisync-header"
793
- )
794
 
 
795
  with gr.Row():
 
796
  import urllib.parse
797
  try:
 
798
  url_params = {}
799
  if hasattr(gr, 'get_current_url'):
800
  current_url = gr.get_current_url()
801
  if current_url:
802
  parsed = urllib.parse.urlparse(current_url)
803
  url_params = urllib.parse.parse_qs(parsed.query)
 
804
  default_appointment_id = url_params.get('appointment_id', [''])[0]
805
  except:
806
  default_appointment_id = ""
 
807
  appointment_id_input = gr.Textbox(
808
  label="Appointment ID",
809
  placeholder="Enter your appointment ID here...",
810
  info="This will be automatically populated if you came from the doctors page",
811
- value=default_appointment_id,
812
- elem_id="appointment_id_input"
813
  )
814
 
815
- with gr.Tab("🧬 Multimodal Analysis"):
816
  with gr.Row():
817
  with gr.Column():
818
- multi_img_input = gr.Image(label="Upload X-ray Image", type="pil", elem_id="multi_img_input")
819
- multi_img_enhance = gr.Button("Enhance Image", icon="✨")
 
820
  multi_text_input = gr.Textbox(
821
  label="Enter Medical Report Text",
822
  placeholder="Enter the radiologist's report text here...",
823
  lines=10,
824
  value=example_report if sample_image_path is None else None,
825
- elem_id="multi_text_input"
826
  )
827
- multi_analyze_btn = gr.Button("Analyze Image & Text", variant="primary", icon="🔎")
 
 
 
 
828
  with gr.Column():
829
- multi_results = gr.HTML(label="Analysis Results", elem_id="multi_results")
830
- multi_plot = gr.HTML(label="Visualization", elem_id="multi_plot")
 
 
831
  if sample_image_path:
832
  gr.Examples(
833
  examples=[[sample_image_path, example_report]],
@@ -835,16 +908,19 @@ def create_interface():
835
  label="Example X-ray and Report",
836
  )
837
 
838
- with gr.Tab("🖼️ Image Analysis"):
839
  with gr.Row():
840
  with gr.Column():
841
- img_input = gr.Image(label="Upload X-ray Image", type="pil", elem_id="img_input")
842
- img_enhance = gr.Button("Enhance Image", icon="✨")
843
- img_analyze_btn = gr.Button("Analyze Image", variant="primary", icon="🔎")
 
844
  with gr.Column():
845
- img_output = gr.Image(label="Processed Image", elem_id="img_output")
846
- img_results = gr.HTML(label="Analysis Results", elem_id="img_results")
847
- img_plot = gr.HTML(label="Visualization", elem_id="img_plot")
 
 
848
  if sample_image_path:
849
  gr.Examples(
850
  examples=[[sample_image_path]],
@@ -852,7 +928,7 @@ def create_interface():
852
  label="Example X-ray Image",
853
  )
854
 
855
- with gr.Tab("📝 Text Analysis"):
856
  with gr.Row():
857
  with gr.Column():
858
  text_input = gr.Textbox(
@@ -860,58 +936,55 @@ def create_interface():
860
  placeholder="Enter the radiologist's report text here...",
861
  lines=10,
862
  value=example_report,
863
- elem_id="text_input"
864
  )
865
- text_analyze_btn = gr.Button("Analyze Text", variant="primary", icon="🔎")
 
866
  with gr.Column():
867
- text_output = gr.Textbox(label="Processed Text", elem_id="text_output")
868
- text_results = gr.HTML(label="Analysis Results", elem_id="text_results")
869
- text_plot = gr.HTML(label="Entity Visualization", elem_id="text_plot")
 
 
870
  gr.Examples(
871
  examples=[[example_report]],
872
  inputs=[text_input],
873
  label="Example Medical Report",
874
  )
875
 
 
876
  with gr.Row():
877
  with gr.Column():
878
  end_consultation_btn = gr.Button(
879
- "End Consultation",
880
- variant="stop",
881
  size="lg",
882
- elem_classes=["end-consultation-btn"],
883
- icon="🛑"
884
  )
885
- end_consultation_status = gr.HTML(label="Status", elem_id="end_consultation_status")
886
 
887
- with gr.Tab("ℹ️ About"):
888
- gr.Markdown(
889
- """
890
- <div class="medisync-card medisync-card-bg">
891
- <h2 class="medisync-title medisync-blue">About MediSync</h2>
892
- <p>
893
- <b>MediSync</b> is an AI-powered healthcare solution that uses multi-modal analysis to provide comprehensive insights from medical images and reports.
894
- </p>
895
- <h3>Key Features</h3>
896
- <ul>
897
- <li><b>X-ray Image Analysis</b>: Detects abnormalities in chest X-rays using pre-trained vision models</li>
898
- <li><b>Medical Report Processing</b>: Extracts key information from patient reports using NLP models</li>
899
- <li><b>Multi-modal Integration</b>: Combines insights from both image and text data for more accurate analysis</li>
900
- </ul>
901
- <h3>Models Used</h3>
902
- <ul>
903
- <li><b>X-ray Analysis</b>: facebook/deit-base-patch16-224-medical-cxr</li>
904
- <li><b>Medical Text Analysis</b>: medicalai/ClinicalBERT</li>
905
- </ul>
906
- <h3 style="color:#dc3545;">Important Disclaimer</h3>
907
- <p>
908
- 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.
909
- </p>
910
- </div>
911
- """
912
- )
913
 
914
- # Event handlers
915
  multi_img_enhance.click(
916
  app.enhance_image, inputs=multi_img_input, outputs=multi_img_input
917
  )
@@ -920,43 +993,54 @@ def create_interface():
920
  inputs=[multi_img_input, multi_text_input],
921
  outputs=[multi_results, multi_plot],
922
  )
 
923
  img_enhance.click(app.enhance_image, inputs=img_input, outputs=img_output)
924
  img_analyze_btn.click(
925
  app.analyze_image,
926
  inputs=img_input,
927
  outputs=[img_output, img_results, img_plot],
928
  )
 
929
  text_analyze_btn.click(
930
  app.analyze_text,
931
  inputs=text_input,
932
  outputs=[text_output, text_results, text_plot],
933
  )
934
 
 
935
  def handle_end_consultation(appointment_id):
936
  if not appointment_id or appointment_id.strip() == "":
937
- return "<div style='color: #dc3545; padding: 10px; background-color: #ffe6e6; border-radius: 5px;'>Please enter your appointment ID first.</div>"
 
 
938
  result = complete_appointment(appointment_id.strip())
 
939
  if result["status"] == "success":
 
940
  doctors_urls = get_doctors_page_urls()
 
 
941
  html_response = f"""
942
- <div style='color: #28a745; padding: 15px; background-color: #e6ffe6; border-radius: 5px; margin: 10px 0;'>
943
  <h3>✅ Consultation Completed Successfully!</h3>
944
  <p>{result['message']}</p>
945
  <p>Your appointment has been marked as completed.</p>
946
  <button onclick="window.open('{doctors_urls['local']}', '_blank')"
947
- style="background-color: #00bfae; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px;">
948
  Return to Doctors Page (Local)
949
  </button>
950
  <button onclick="window.open('{doctors_urls['production']}', '_blank')"
951
- style="background-color: #6c63ff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px; margin-left: 10px;">
952
  Return to Doctors Page (Production)
953
  </button>
954
  </div>
955
  """
956
  else:
 
957
  if "Cannot connect to Flask app" in result['message']:
 
958
  html_response = f"""
959
- <div style='color: #ff9800; padding: 15px; background-color: #fff3cd; border-radius: 5px; margin: 10px 0;'>
960
  <h3>⚠️ Consultation Ready to Complete</h3>
961
  <p>Your consultation analysis is complete! However, we cannot automatically mark your appointment as completed because the Flask app is not accessible from this environment.</p>
962
  <p><strong>Appointment ID:</strong> {appointment_id.strip()}</p>
@@ -968,15 +1052,15 @@ def create_interface():
968
  </ol>
969
  <div style="margin-top: 15px;">
970
  <button onclick="window.open('http://127.0.0.1:600/complete_appointment_manual?appointment_id={appointment_id.strip()}', '_blank')"
971
- style="background-color: #00bfae; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px;">
972
  Complete Appointment
973
  </button>
974
  <button onclick="window.open('http://127.0.0.1:600/doctors', '_blank')"
975
- style="background-color: #6c63ff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px;">
976
  Return to Doctors Page
977
  </button>
978
  <button onclick="navigator.clipboard.writeText('{appointment_id.strip()}')"
979
- style="background-color: #23272f; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;">
980
  Copy Appointment ID
981
  </button>
982
  </div>
@@ -984,12 +1068,13 @@ def create_interface():
984
  """
985
  else:
986
  html_response = f"""
987
- <div style='color: #dc3545; padding: 15px; background-color: #ffe6e6; border-radius: 5px; margin: 10px 0;'>
988
  <h3>❌ Error Completing Consultation</h3>
989
  <p>{result['message']}</p>
990
  <p>Please try again or contact support if the problem persists.</p>
991
  </div>
992
  """
 
993
  return html_response
994
 
995
  end_consultation_btn.click(
@@ -998,36 +1083,173 @@ def create_interface():
998
  outputs=[end_consultation_status]
999
  )
1000
 
1001
- # JavaScript for appointment ID auto-population
1002
  gr.HTML("""
 
 
 
 
 
 
 
 
 
 
 
 
 
1003
  <script>
 
1004
  function getUrlParameter(name) {
1005
  name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
1006
  var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
1007
  var results = regex.exec(location.search);
1008
- return results === null ? '' : decodeURIComponent(results[1].replace(/\\+/g, ' '));
1009
  }
 
 
1010
  function populateAppointmentId() {
1011
  var appointmentId = getUrlParameter('appointment_id');
 
 
1012
  if (appointmentId) {
1013
- var input = document.getElementById('appointment_id_input');
1014
- if (input) {
1015
- input.value = appointmentId;
 
 
 
 
1016
  var event = new Event('input', { bubbles: true });
1017
- input.dispatchEvent(event);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1018
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1019
  }
1020
  }
 
 
1021
  document.addEventListener('DOMContentLoaded', function() {
1022
- setTimeout(populateAppointmentId, 800);
 
1023
  });
 
 
1024
  window.addEventListener('load', function() {
1025
- setTimeout(populateAppointmentId, 1200);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1026
  });
1027
  </script>
1028
  """)
1029
 
 
1030
  interface.launch()
1031
 
 
1032
  if __name__ == "__main__":
1033
- create_interface()
 
383
  # except Exception as e:
384
  # self.logger.error(f"Error converting figure to HTML: {e}")
385
  # return "<p>Error displaying visualization.</p>"
 
 
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
 
398
  try:
399
  from .config import get_flask_urls, get_doctors_page_urls, TIMEOUT_SETTINGS
400
  except ImportError:
401
+ # Fallback configuration if config file is not available
402
  def get_flask_urls():
403
  return [
404
  "http://127.0.0.1:600/complete_appointment",
 
406
  "https://your-flask-app-domain.com/complete_appointment",
407
  "http://your-flask-app-ip:600/complete_appointment"
408
  ]
409
+
410
  def get_doctors_page_urls():
411
  return {
412
  "local": "http://127.0.0.1:600/doctors",
413
  "production": "https://your-flask-app-domain.com/doctors"
414
  }
415
+
416
  TIMEOUT_SETTINGS = {"connection_timeout": 5, "request_timeout": 10}
417
 
418
+ # Add parent directory to path
419
  parent_dir = os.path.dirname(os.path.abspath(__file__))
420
  sys.path.append(parent_dir)
421
 
422
+ # Configure logging
423
  logging.basicConfig(
424
  level=logging.INFO,
425
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 
428
  logger = logging.getLogger(__name__)
429
 
430
  class MediSyncApp:
431
+ """
432
+ Main application class for the MediSync multi-modal medical analysis system.
433
+ """
434
+
435
  def __init__(self):
436
+ """Initialize the application and load models."""
437
  self.logger = logging.getLogger(__name__)
438
  self.logger.info("Initializing MediSync application")
439
+ self._temp_files = [] # Track temporary files for cleanup
440
  self.fusion_model = None
441
  self.image_model = None
442
  self.text_model = None
443
 
444
  def __del__(self):
445
+ """Cleanup temporary files on object destruction."""
446
  self.cleanup_temp_files()
447
 
448
  def cleanup_temp_files(self):
449
+ """Clean up temporary files."""
450
  for temp_file in self._temp_files:
451
  try:
452
  if os.path.exists(temp_file):
 
457
  self._temp_files = []
458
 
459
  def load_models(self):
460
+ """
461
+ Load models if not already loaded.
462
+
463
+ Returns:
464
+ bool: True if models loaded successfully, False otherwise
465
+ """
466
  if self.fusion_model is not None:
467
  return True
468
+
469
  try:
470
  self.logger.info("Loading models...")
471
+ # For now, we'll create a simple mock implementation
472
+ # You can replace this with your actual model loading code
473
  self.logger.info("Models loaded successfully (mock implementation)")
474
  return True
475
  except Exception as e:
 
477
  return False
478
 
479
  def enhance_image(self, image):
480
+ """Enhance the uploaded image."""
481
  if image is None:
482
  return None
483
+
484
  try:
485
+ # Simple image enhancement (you can replace with actual enhancement logic)
486
  enhanced_image = image
487
  self.logger.info("Image enhanced successfully")
488
  return enhanced_image
 
491
  return image
492
 
493
  def analyze_image(self, image):
494
+ """
495
+ Analyze a medical image.
496
+
497
+ Args:
498
+ image: Image file uploaded through Gradio
499
+
500
+ Returns:
501
+ tuple: (image, image_results_html, plot_as_html)
502
+ """
503
  if image is None:
504
  return None, "Please upload an image first.", None
505
+
506
  if not self.load_models():
507
  return image, "Error: Models not loaded properly.", None
508
+
509
  try:
510
  self.logger.info("Analyzing image")
511
+
512
+ # Mock analysis results (replace with actual model inference)
513
  results = {
514
  "primary_finding": "Normal chest X-ray",
515
  "confidence": 0.85,
 
520
  ("Cardiomegaly", 0.05)
521
  ]
522
  }
523
+
524
+ # Create visualization
525
  fig = self.plot_image_prediction(
526
  image,
527
  results.get("predictions", []),
528
  f"Primary Finding: {results.get('primary_finding', 'Unknown')}"
529
  )
530
+
531
+ # Convert to HTML for display
532
  plot_html = self.fig_to_html(fig)
533
+ plt.close(fig) # Clean up matplotlib figure
534
+
535
+ # Format results as HTML
536
  html_result = self.format_image_results(results)
537
+
538
  return image, html_result, plot_html
539
+
540
  except Exception as e:
541
  self.logger.error(f"Error in image analysis: {e}")
542
  return image, f"Error analyzing image: {str(e)}", None
543
 
544
  def analyze_text(self, text):
545
+ """
546
+ Analyze medical report text.
547
+
548
+ Args:
549
+ text: Medical report text
550
+
551
+ Returns:
552
+ tuple: (processed_text, text_results_html, plot_as_html)
553
+ """
554
  if not text or text.strip() == "":
555
  return "", "Please enter medical report text.", None
556
+
557
  if not self.load_models():
558
  return text, "Error: Models not loaded properly.", None
559
+
560
  try:
561
  self.logger.info("Analyzing text")
562
+
563
+ # Mock text analysis results (replace with actual model inference)
564
  results = {
565
  "entities": [
566
  {"text": "chest X-ray", "type": "PROCEDURE", "confidence": 0.95},
 
570
  "sentiment": "neutral",
571
  "key_findings": ["Normal heart size", "Clear lungs", "8mm nodular opacity"]
572
  }
573
+
574
+ # Format results as HTML
575
  html_result = self.format_text_results(results)
576
+
577
+ # Create entity visualization
578
  plot_html = self.create_entity_visualization(results["entities"])
579
+
580
  return text, html_result, plot_html
581
+
582
  except Exception as e:
583
  self.logger.error(f"Error in text analysis: {e}")
584
  return text, f"Error analyzing text: {str(e)}", None
585
 
586
  def analyze_multimodal(self, image, text):
587
+ """
588
+ Analyze both image and text together.
589
+
590
+ Args:
591
+ image: Medical image
592
+ text: Medical report text
593
+
594
+ Returns:
595
+ tuple: (results_html, plot_as_html)
596
+ """
597
  if image is None and (not text or text.strip() == ""):
598
  return "Please provide either an image or text for analysis.", None
599
+
600
  if not self.load_models():
601
  return "Error: Models not loaded properly.", None
602
+
603
  try:
604
  self.logger.info("Performing multimodal analysis")
605
+
606
+ # Mock multimodal analysis results (replace with actual model inference)
607
  results = {
608
  "combined_finding": "Normal chest X-ray with minor findings",
609
  "confidence": 0.92,
 
614
  "Monitor for any changes in symptoms"
615
  ]
616
  }
617
+
618
+ # Format results as HTML
619
  html_result = self.format_multimodal_results(results)
620
+
621
+ # Create combined visualization
622
  plot_html = self.create_multimodal_visualization(results)
623
+
624
  return html_result, plot_html
625
+
626
  except Exception as e:
627
  self.logger.error(f"Error in multimodal analysis: {e}")
628
  return f"Error in multimodal analysis: {str(e)}", None
629
 
630
  def format_image_results(self, results):
631
+ """Format image analysis results as HTML."""
632
  html_result = f"""
633
+ <div style="background-color: #f8f9fa; padding: 20px; border-radius: 10px; margin: 10px 0;">
634
+ <h2 style="color: #007bff;">X-ray Analysis Results</h2>
635
  <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
636
  <p><strong>Confidence:</strong> {results.get("confidence", 0):.1%}</p>
637
  <p><strong>Abnormality Detected:</strong> {"Yes" if results.get("has_abnormality", False) else "No"}</p>
638
+
639
  <h3>Top Predictions:</h3>
640
  <ul>
641
  """
642
+
643
  for label, prob in results.get("predictions", [])[:5]:
644
  html_result += f"<li>{label}: {prob:.1%}</li>"
645
+
646
  html_result += "</ul></div>"
647
  return html_result
648
 
649
  def format_text_results(self, results):
650
+ """Format text analysis results as HTML."""
651
  html_result = f"""
652
+ <div style="background-color: #f8f9fa; padding: 20px; border-radius: 10px; margin: 10px 0;">
653
+ <h2 style="color: #28a745;">Text Analysis Results</h2>
654
  <p><strong>Sentiment:</strong> {results.get("sentiment", "Unknown").title()}</p>
655
+
656
  <h3>Key Findings:</h3>
657
  <ul>
658
  """
659
+
660
  for finding in results.get("key_findings", []):
661
  html_result += f"<li>{finding}</li>"
662
+
663
  html_result += "</ul>"
664
+
665
  html_result += "<h3>Extracted Entities:</h3><ul>"
666
  for entity in results.get("entities", [])[:5]:
667
  html_result += f"<li><strong>{entity['text']}</strong> ({entity['type']}) - {entity['confidence']:.1%}</li>"
668
+
669
  html_result += "</ul></div>"
670
  return html_result
671
 
672
  def format_multimodal_results(self, results):
673
+ """Format multimodal analysis results as HTML."""
674
  html_result = f"""
675
+ <div style="background-color: #f8f9fa; padding: 20px; border-radius: 10px; margin: 10px 0;">
676
+ <h2 style="color: #6f42c1;">Multimodal Analysis Results</h2>
677
  <p><strong>Combined Finding:</strong> {results.get("combined_finding", "Unknown")}</p>
678
  <p><strong>Overall Confidence:</strong> {results.get("confidence", 0):.1%}</p>
679
+
680
  <h3>Image Contribution:</h3>
681
  <p>{results.get("image_contribution", "No image analysis available")}</p>
682
+
683
  <h3>Text Contribution:</h3>
684
  <p>{results.get("text_contribution", "No text analysis available")}</p>
685
+
686
  <h3>Recommendations:</h3>
687
  <ul>
688
  """
689
+
690
  for rec in results.get("recommendations", []):
691
  html_result += f"<li>{rec}</li>"
692
+
693
  html_result += "</ul></div>"
694
  return html_result
695
 
696
  def plot_image_prediction(self, image, predictions, title):
697
+ """Create visualization for image predictions."""
698
  fig, ax = plt.subplots(figsize=(10, 6))
699
  ax.imshow(image)
700
+ ax.set_title(title, fontsize=14, fontweight='bold')
701
  ax.axis('off')
702
  return fig
703
 
704
  def create_entity_visualization(self, entities):
705
+ """Create visualization for text entities."""
706
  if not entities:
707
  return "<p>No entities found in text.</p>"
708
+
709
  fig, ax = plt.subplots(figsize=(10, 6))
710
+
711
  entity_types = {}
712
  for entity in entities:
713
  entity_type = entity['type']
714
  if entity_type not in entity_types:
715
  entity_types[entity_type] = 0
716
  entity_types[entity_type] += 1
717
+
718
  if entity_types:
719
+ ax.bar(entity_types.keys(), entity_types.values(), color='skyblue')
720
+ ax.set_title('Entity Types Found in Text', fontsize=14, fontweight='bold')
721
+ ax.set_ylabel('Count')
722
+ plt.xticks(rotation=45)
723
+
724
  return self.fig_to_html(fig)
725
 
726
  def create_multimodal_visualization(self, results):
727
+ """Create visualization for multimodal results."""
728
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
729
+
730
+ # Confidence visualization
731
  confidence = results.get("confidence", 0)
732
+ ax1.pie([confidence, 1-confidence], labels=['Confidence', 'Uncertainty'],
733
+ colors=['lightgreen', 'lightcoral'], autopct='%1.1f%%')
734
+ ax1.set_title('Analysis Confidence', fontweight='bold')
735
+
736
+ # Recommendations count
737
  recommendations = results.get("recommendations", [])
738
+ ax2.bar(['Recommendations'], [len(recommendations)], color='lightblue')
739
+ ax2.set_title('Number of Recommendations', fontweight='bold')
740
+ ax2.set_ylabel('Count')
741
+
742
  plt.tight_layout()
743
  return self.fig_to_html(fig)
744
 
745
  def fig_to_html(self, fig):
746
+ """Convert matplotlib figure to HTML."""
747
  import io
748
  import base64
749
+
750
  buf = io.BytesIO()
751
+ fig.savefig(buf, format='png', bbox_inches='tight', dpi=100)
752
  buf.seek(0)
753
  img_str = base64.b64encode(buf.read()).decode()
754
  buf.close()
755
+
756
+ return f'<img src="data:image/png;base64,{img_str}" style="max-width: 100%; height: auto;"/>'
757
 
758
  def complete_appointment(appointment_id):
759
+ """
760
+ Complete an appointment by calling the Flask API.
761
+
762
+ Args:
763
+ appointment_id: The appointment ID to complete
764
+
765
+ Returns:
766
+ dict: Response from the API
767
+ """
768
  try:
769
+ # Get Flask URLs from configuration
770
  flask_urls = get_flask_urls()
771
+
772
  payload = {"appointment_id": appointment_id}
773
+
774
  for flask_api_url in flask_urls:
775
  try:
776
  logger.info(f"Trying to connect to: {flask_api_url}")
777
  response = requests.post(flask_api_url, json=payload, timeout=TIMEOUT_SETTINGS["connection_timeout"])
778
+
779
  if response.status_code == 200:
780
  return {"status": "success", "message": "Appointment completed successfully"}
781
  elif response.status_code == 404:
 
783
  else:
784
  logger.warning(f"Unexpected response from {flask_api_url}: {response.status_code}")
785
  continue
786
+
787
  except requests.exceptions.ConnectionError:
788
  logger.warning(f"Connection failed to {flask_api_url}")
789
  continue
 
793
  except Exception as e:
794
  logger.warning(f"Error with {flask_api_url}: {e}")
795
  continue
796
+
797
+ # If all URLs fail, return a helpful error message
798
  return {
799
+ "status": "error",
800
  "message": "Cannot connect to Flask app. Please ensure the Flask app is running and accessible."
801
  }
802
+
803
  except Exception as e:
804
  logger.error(f"Error completing appointment: {e}")
805
  return {"status": "error", "message": f"Error: {str(e)}"}
806
 
807
  def create_interface():
808
+ """Create and launch the Gradio interface."""
809
+
810
  app = MediSyncApp()
811
+
812
+ # Example medical report for demo
813
  example_report = """
814
  CHEST X-RAY EXAMINATION
815
+
816
  CLINICAL HISTORY: 55-year-old male with cough and fever.
817
+
818
  FINDINGS: The heart size is at the upper limits of normal. The lungs are clear without focal consolidation,
819
  effusion, or pneumothorax. There is mild prominence of the pulmonary vasculature. No pleural effusion is seen.
820
  There is a small nodular opacity noted in the right lower lobe measuring approximately 8mm, which is suspicious
821
  and warrants further investigation. The mediastinum is unremarkable. The visualized bony structures show no acute abnormalities.
822
+
823
  IMPRESSION:
824
  1. Mild cardiomegaly.
825
  2. 8mm nodular opacity in the right lower lobe, recommend follow-up CT for further evaluation.
826
  3. No acute pulmonary parenchymal abnormality.
827
+
828
  RECOMMENDATIONS: Follow-up chest CT to further characterize the nodular opacity in the right lower lobe.
829
  """
830
 
831
+ # Get sample image path if available
832
  sample_images_dir = Path(parent_dir) / "data" / "sample"
833
+ sample_images = list(sample_images_dir.glob("*.png")) + list(
834
+ sample_images_dir.glob("*.jpg")
835
+ )
836
+
837
+ sample_image_path = None
838
+ if sample_images:
839
+ sample_image_path = str(sample_images[0])
840
 
841
+ # Define interface
842
  with gr.Blocks(
843
+ title="MediSync: Multi-Modal Medical Analysis System", theme=gr.themes.Soft()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
844
  ) as interface:
845
+ gr.Markdown("""
846
+ # MediSync: Multi-Modal Medical Analysis System
847
+
848
+ This AI-powered healthcare solution combines X-ray image analysis with patient report text processing
849
+ to provide comprehensive medical insights.
850
+
851
+ ## How to Use
852
+ 1. Upload a chest X-ray image
853
+ 2. Enter the corresponding medical report text
854
+ 3. Choose the analysis type: image-only, text-only, or multimodal (combined)
855
+ 4. Click "End Consultation" when finished to complete your appointment
856
+ """)
 
 
 
 
 
 
 
 
 
 
 
857
 
858
+ # Add appointment ID input with Python-based population
859
  with gr.Row():
860
+ # Get appointment ID from URL parameters if available
861
  import urllib.parse
862
  try:
863
+ # This will be set by JavaScript, but we can also try to get it server-side
864
  url_params = {}
865
  if hasattr(gr, 'get_current_url'):
866
  current_url = gr.get_current_url()
867
  if current_url:
868
  parsed = urllib.parse.urlparse(current_url)
869
  url_params = urllib.parse.parse_qs(parsed.query)
870
+
871
  default_appointment_id = url_params.get('appointment_id', [''])[0]
872
  except:
873
  default_appointment_id = ""
874
+
875
  appointment_id_input = gr.Textbox(
876
  label="Appointment ID",
877
  placeholder="Enter your appointment ID here...",
878
  info="This will be automatically populated if you came from the doctors page",
879
+ value=default_appointment_id
 
880
  )
881
 
882
+ with gr.Tab("Multimodal Analysis"):
883
  with gr.Row():
884
  with gr.Column():
885
+ multi_img_input = gr.Image(label="Upload X-ray Image", type="pil")
886
+ multi_img_enhance = gr.Button("Enhance Image")
887
+
888
  multi_text_input = gr.Textbox(
889
  label="Enter Medical Report Text",
890
  placeholder="Enter the radiologist's report text here...",
891
  lines=10,
892
  value=example_report if sample_image_path is None else None,
 
893
  )
894
+
895
+ multi_analyze_btn = gr.Button(
896
+ "Analyze Image & Text", variant="primary"
897
+ )
898
+
899
  with gr.Column():
900
+ multi_results = gr.HTML(label="Analysis Results")
901
+ multi_plot = gr.HTML(label="Visualization")
902
+
903
+ # Set up examples if sample image exists
904
  if sample_image_path:
905
  gr.Examples(
906
  examples=[[sample_image_path, example_report]],
 
908
  label="Example X-ray and Report",
909
  )
910
 
911
+ with gr.Tab("Image Analysis"):
912
  with gr.Row():
913
  with gr.Column():
914
+ img_input = gr.Image(label="Upload X-ray Image", type="pil")
915
+ img_enhance = gr.Button("Enhance Image")
916
+ img_analyze_btn = gr.Button("Analyze Image", variant="primary")
917
+
918
  with gr.Column():
919
+ img_output = gr.Image(label="Processed Image")
920
+ img_results = gr.HTML(label="Analysis Results")
921
+ img_plot = gr.HTML(label="Visualization")
922
+
923
+ # Set up example if sample image exists
924
  if sample_image_path:
925
  gr.Examples(
926
  examples=[[sample_image_path]],
 
928
  label="Example X-ray Image",
929
  )
930
 
931
+ with gr.Tab("Text Analysis"):
932
  with gr.Row():
933
  with gr.Column():
934
  text_input = gr.Textbox(
 
936
  placeholder="Enter the radiologist's report text here...",
937
  lines=10,
938
  value=example_report,
 
939
  )
940
+ text_analyze_btn = gr.Button("Analyze Text", variant="primary")
941
+
942
  with gr.Column():
943
+ text_output = gr.Textbox(label="Processed Text")
944
+ text_results = gr.HTML(label="Analysis Results")
945
+ text_plot = gr.HTML(label="Entity Visualization")
946
+
947
+ # Set up example
948
  gr.Examples(
949
  examples=[[example_report]],
950
  inputs=[text_input],
951
  label="Example Medical Report",
952
  )
953
 
954
+ # End Consultation Section
955
  with gr.Row():
956
  with gr.Column():
957
  end_consultation_btn = gr.Button(
958
+ "End Consultation",
959
+ variant="stop",
960
  size="lg",
961
+ elem_classes=["end-consultation-btn"]
 
962
  )
963
+ end_consultation_status = gr.HTML(label="Status")
964
 
965
+ with gr.Tab("About"):
966
+ gr.Markdown("""
967
+ ## About MediSync
968
+
969
+ MediSync is an AI-powered healthcare solution that uses multi-modal analysis to provide comprehensive insights from medical images and reports.
970
+
971
+ ### Key Features
972
+
973
+ - **X-ray Image Analysis**: Detects abnormalities in chest X-rays using pre-trained vision models
974
+ - **Medical Report Processing**: Extracts key information from patient reports using NLP models
975
+ - **Multi-modal Integration**: Combines insights from both image and text data for more accurate analysis
976
+
977
+ ### Models Used
978
+
979
+ - **X-ray Analysis**: facebook/deit-base-patch16-224-medical-cxr
980
+ - **Medical Text Analysis**: medicalai/ClinicalBERT
981
+
982
+ ### Important Disclaimer
983
+
984
+ 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.
985
+ """)
 
 
 
 
 
986
 
987
+ # Set up event handlers
988
  multi_img_enhance.click(
989
  app.enhance_image, inputs=multi_img_input, outputs=multi_img_input
990
  )
 
993
  inputs=[multi_img_input, multi_text_input],
994
  outputs=[multi_results, multi_plot],
995
  )
996
+
997
  img_enhance.click(app.enhance_image, inputs=img_input, outputs=img_output)
998
  img_analyze_btn.click(
999
  app.analyze_image,
1000
  inputs=img_input,
1001
  outputs=[img_output, img_results, img_plot],
1002
  )
1003
+
1004
  text_analyze_btn.click(
1005
  app.analyze_text,
1006
  inputs=text_input,
1007
  outputs=[text_output, text_results, text_plot],
1008
  )
1009
 
1010
+ # End consultation handler
1011
  def handle_end_consultation(appointment_id):
1012
  if not appointment_id or appointment_id.strip() == "":
1013
+ return "<div style='color: red; padding: 10px; background-color: #ffe6e6; border-radius: 5px;'>Please enter your appointment ID first.</div>"
1014
+
1015
+ # Try to complete the appointment
1016
  result = complete_appointment(appointment_id.strip())
1017
+
1018
  if result["status"] == "success":
1019
+ # Get doctors page URLs from configuration
1020
  doctors_urls = get_doctors_page_urls()
1021
+
1022
+ # Create success message with redirect button
1023
  html_response = f"""
1024
+ <div style='color: green; padding: 15px; background-color: #e6ffe6; border-radius: 5px; margin: 10px 0;'>
1025
  <h3>✅ Consultation Completed Successfully!</h3>
1026
  <p>{result['message']}</p>
1027
  <p>Your appointment has been marked as completed.</p>
1028
  <button onclick="window.open('{doctors_urls['local']}', '_blank')"
1029
+ style="background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px;">
1030
  Return to Doctors Page (Local)
1031
  </button>
1032
  <button onclick="window.open('{doctors_urls['production']}', '_blank')"
1033
+ style="background-color: #28a745; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px; margin-left: 10px;">
1034
  Return to Doctors Page (Production)
1035
  </button>
1036
  </div>
1037
  """
1038
  else:
1039
+ # Handle connection failure gracefully
1040
  if "Cannot connect to Flask app" in result['message']:
1041
+ # Show a helpful message with manual completion instructions
1042
  html_response = f"""
1043
+ <div style='color: orange; padding: 15px; background-color: #fff3cd; border-radius: 5px; margin: 10px 0;'>
1044
  <h3>⚠️ Consultation Ready to Complete</h3>
1045
  <p>Your consultation analysis is complete! However, we cannot automatically mark your appointment as completed because the Flask app is not accessible from this environment.</p>
1046
  <p><strong>Appointment ID:</strong> {appointment_id.strip()}</p>
 
1052
  </ol>
1053
  <div style="margin-top: 15px;">
1054
  <button onclick="window.open('http://127.0.0.1:600/complete_appointment_manual?appointment_id={appointment_id.strip()}', '_blank')"
1055
+ style="background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px;">
1056
  Complete Appointment
1057
  </button>
1058
  <button onclick="window.open('http://127.0.0.1:600/doctors', '_blank')"
1059
+ style="background-color: #28a745; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px;">
1060
  Return to Doctors Page
1061
  </button>
1062
  <button onclick="navigator.clipboard.writeText('{appointment_id.strip()}')"
1063
+ style="background-color: #6c757d; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;">
1064
  Copy Appointment ID
1065
  </button>
1066
  </div>
 
1068
  """
1069
  else:
1070
  html_response = f"""
1071
+ <div style='color: red; padding: 15px; background-color: #ffe6e6; border-radius: 5px; margin: 10px 0;'>
1072
  <h3>❌ Error Completing Consultation</h3>
1073
  <p>{result['message']}</p>
1074
  <p>Please try again or contact support if the problem persists.</p>
1075
  </div>
1076
  """
1077
+
1078
  return html_response
1079
 
1080
  end_consultation_btn.click(
 
1083
  outputs=[end_consultation_status]
1084
  )
1085
 
1086
+ # Add custom CSS and JavaScript for better styling and functionality
1087
  gr.HTML("""
1088
+ <style>
1089
+ .end-consultation-btn {
1090
+ background-color: #dc3545 !important;
1091
+ border-color: #dc3545 !important;
1092
+ color: white !important;
1093
+ font-weight: bold !important;
1094
+ }
1095
+ .end-consultation-btn:hover {
1096
+ background-color: #c82333 !important;
1097
+ border-color: #bd2130 !important;
1098
+ }
1099
+ </style>
1100
+
1101
  <script>
1102
+ // Function to get URL parameters
1103
  function getUrlParameter(name) {
1104
  name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
1105
  var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
1106
  var results = regex.exec(location.search);
1107
+ return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
1108
  }
1109
+
1110
+ // Function to populate appointment ID from URL
1111
  function populateAppointmentId() {
1112
  var appointmentId = getUrlParameter('appointment_id');
1113
+ console.log('Found appointment ID:', appointmentId);
1114
+
1115
  if (appointmentId) {
1116
+ // Try multiple methods to find and populate the appointment ID input
1117
+ var success = false;
1118
+
1119
+ // Method 1: Try by specific element ID
1120
+ var elementById = document.getElementById('appointment_id_input');
1121
+ if (elementById) {
1122
+ elementById.value = appointmentId;
1123
  var event = new Event('input', { bubbles: true });
1124
+ elementById.dispatchEvent(event);
1125
+ console.log('Set appointment ID by ID to:', appointmentId);
1126
+ success = true;
1127
+ }
1128
+
1129
+ // Method 2: Try by placeholder text
1130
+ if (!success) {
1131
+ var selectors = [
1132
+ 'input[placeholder*="appointment ID"]',
1133
+ 'input[placeholder*="appointment_id"]',
1134
+ 'input[placeholder*="Appointment ID"]',
1135
+ 'textarea[placeholder*="appointment ID"]',
1136
+ 'textarea[placeholder*="appointment_id"]',
1137
+ 'textarea[placeholder*="Appointment ID"]'
1138
+ ];
1139
+
1140
+ for (var selector of selectors) {
1141
+ var elements = document.querySelectorAll(selector);
1142
+ for (var element of elements) {
1143
+ console.log('Found element by placeholder:', element);
1144
+ element.value = appointmentId;
1145
+ var event = new Event('input', { bubbles: true });
1146
+ element.dispatchEvent(event);
1147
+ console.log('Set appointment ID by placeholder to:', appointmentId);
1148
+ success = true;
1149
+ break;
1150
+ }
1151
+ if (success) break;
1152
+ }
1153
+ }
1154
+
1155
+ // Method 3: Try by label text
1156
+ if (!success) {
1157
+ var labels = document.querySelectorAll('label');
1158
+ for (var label of labels) {
1159
+ if (label.textContent && label.textContent.toLowerCase().includes('appointment id')) {
1160
+ var input = label.nextElementSibling;
1161
+ if (input && (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA')) {
1162
+ input.value = appointmentId;
1163
+ var event = new Event('input', { bubbles: true });
1164
+ input.dispatchEvent(event);
1165
+ console.log('Set appointment ID by label to:', appointmentId);
1166
+ success = true;
1167
+ break;
1168
+ }
1169
+ }
1170
+ }
1171
+ }
1172
+
1173
+ // Method 4: Try by Gradio component attributes
1174
+ if (!success) {
1175
+ var gradioInputs = document.querySelectorAll('[data-testid="textbox"]');
1176
+ for (var input of gradioInputs) {
1177
+ var label = input.closest('.form').querySelector('label');
1178
+ if (label && label.textContent.toLowerCase().includes('appointment id')) {
1179
+ input.value = appointmentId;
1180
+ var event = new Event('input', { bubbles: true });
1181
+ input.dispatchEvent(event);
1182
+ console.log('Set appointment ID by Gradio component to:', appointmentId);
1183
+ success = true;
1184
+ break;
1185
+ }
1186
+ }
1187
  }
1188
+
1189
+ if (!success) {
1190
+ console.log('Could not find appointment ID input field');
1191
+ // Log all input elements for debugging
1192
+ var allInputs = document.querySelectorAll('input, textarea');
1193
+ console.log('All input elements found:', allInputs.length);
1194
+ for (var i = 0; i < allInputs.length; i++) {
1195
+ console.log('Input', i, ':', allInputs[i].placeholder, allInputs[i].id, allInputs[i].className);
1196
+ }
1197
+ }
1198
+ } else {
1199
+ console.log('No appointment ID found in URL');
1200
+ }
1201
+ return success;
1202
+ }
1203
+
1204
+ // Function to wait for Gradio to be ready
1205
+ function waitForGradio() {
1206
+ if (typeof gradio !== 'undefined' && gradio) {
1207
+ console.log('Gradio detected, waiting for load...');
1208
+ setTimeout(function() {
1209
+ populateAppointmentId();
1210
+ // Also try again after a longer delay
1211
+ setTimeout(populateAppointmentId, 2000);
1212
+ }, 1000);
1213
+ } else {
1214
+ console.log('Gradio not detected, trying direct population...');
1215
+ populateAppointmentId();
1216
+ // Try again after a delay
1217
+ setTimeout(populateAppointmentId, 1000);
1218
  }
1219
  }
1220
+
1221
+ // Run when page loads
1222
  document.addEventListener('DOMContentLoaded', function() {
1223
+ console.log('DOM loaded, attempting to populate appointment ID...');
1224
+ waitForGradio();
1225
  });
1226
+
1227
+ // Also run when window loads
1228
  window.addEventListener('load', function() {
1229
+ console.log('Window loaded, attempting to populate appointment ID...');
1230
+ setTimeout(waitForGradio, 500);
1231
+ });
1232
+
1233
+ // Monitor for dynamic content changes
1234
+ var observer = new MutationObserver(function(mutations) {
1235
+ mutations.forEach(function(mutation) {
1236
+ if (mutation.type === 'childList') {
1237
+ setTimeout(populateAppointmentId, 100);
1238
+ }
1239
+ });
1240
+ });
1241
+
1242
+ // Start observing
1243
+ observer.observe(document.body, {
1244
+ childList: true,
1245
+ subtree: true
1246
  });
1247
  </script>
1248
  """)
1249
 
1250
+ # Run the interface
1251
  interface.launch()
1252
 
1253
+
1254
  if __name__ == "__main__":
1255
+ create_interface()