blackshadow1 commited on
Commit
2c25d29
·
verified ·
1 Parent(s): 621950a

update the code ✅✅

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