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

updated UI code ✅✅

Browse files
Files changed (1) hide show
  1. mediSync/app.py +207 -416
mediSync/app.py CHANGED
@@ -386,7 +386,6 @@ os.makedirs(os.path.join(parent_dir, "data", "sample"), exist_ok=True)
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,7 +397,6 @@ import json
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,20 +404,16 @@ except ImportError:
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,25 +422,18 @@ logging.basicConfig(
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,19 +444,10 @@ class MediSyncApp:
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,12 +455,9 @@ class MediSyncApp:
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,25 +466,12 @@ class MediSyncApp:
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,47 +482,26 @@ class MediSyncApp:
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,40 +511,20 @@ class MediSyncApp:
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,168 +535,119 @@ class MediSyncApp:
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,7 +655,6 @@ def complete_appointment(appointment_id):
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,114 +664,183 @@ def complete_appointment(appointment_id):
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,19 +848,16 @@ def create_interface():
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,7 +865,7 @@ def create_interface():
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,55 +873,58 @@ def create_interface():
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,54 +933,43 @@ def create_interface():
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,15 +981,15 @@ def create_interface():
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,13 +997,12 @@ def create_interface():
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,173 +1011,36 @@ def create_interface():
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()
 
386
  import logging
387
  import os
388
  import sys
 
389
  from pathlib import Path
390
  import requests
391
  import gradio as gr
 
397
  try:
398
  from .config import get_flask_urls, get_doctors_page_urls, TIMEOUT_SETTINGS
399
  except ImportError:
 
400
  def get_flask_urls():
401
  return [
402
  "http://127.0.0.1:600/complete_appointment",
 
404
  "https://your-flask-app-domain.com/complete_appointment",
405
  "http://your-flask-app-ip:600/complete_appointment"
406
  ]
 
407
  def get_doctors_page_urls():
408
  return {
409
  "local": "http://127.0.0.1:600/doctors",
410
  "production": "https://your-flask-app-domain.com/doctors"
411
  }
 
412
  TIMEOUT_SETTINGS = {"connection_timeout": 5, "request_timeout": 10}
413
 
 
414
  parent_dir = os.path.dirname(os.path.abspath(__file__))
415
  sys.path.append(parent_dir)
416
 
 
417
  logging.basicConfig(
418
  level=logging.INFO,
419
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 
422
  logger = logging.getLogger(__name__)
423
 
424
  class MediSyncApp:
 
 
 
 
425
  def __init__(self):
 
426
  self.logger = logging.getLogger(__name__)
427
  self.logger.info("Initializing MediSync application")
428
+ self._temp_files = []
429
  self.fusion_model = None
430
  self.image_model = None
431
  self.text_model = None
432
 
433
  def __del__(self):
 
434
  self.cleanup_temp_files()
435
 
436
  def cleanup_temp_files(self):
 
437
  for temp_file in self._temp_files:
438
  try:
439
  if os.path.exists(temp_file):
 
444
  self._temp_files = []
445
 
446
  def load_models(self):
 
 
 
 
 
 
447
  if self.fusion_model is not None:
448
  return True
 
449
  try:
450
  self.logger.info("Loading models...")
 
 
451
  self.logger.info("Models loaded successfully (mock implementation)")
452
  return True
453
  except Exception as e:
 
455
  return False
456
 
457
  def enhance_image(self, image):
 
458
  if image is None:
459
  return None
 
460
  try:
 
461
  enhanced_image = image
462
  self.logger.info("Image enhanced successfully")
463
  return enhanced_image
 
466
  return image
467
 
468
  def analyze_image(self, image):
 
 
 
 
 
 
 
 
 
469
  if image is None:
470
  return None, "Please upload an image first.", None
 
471
  if not self.load_models():
472
  return image, "Error: Models not loaded properly.", None
 
473
  try:
474
  self.logger.info("Analyzing image")
 
 
475
  results = {
476
  "primary_finding": "Normal chest X-ray",
477
  "confidence": 0.85,
 
482
  ("Cardiomegaly", 0.05)
483
  ]
484
  }
 
 
485
  fig = self.plot_image_prediction(
486
  image,
487
  results.get("predictions", []),
488
  f"Primary Finding: {results.get('primary_finding', 'Unknown')}"
489
  )
 
 
490
  plot_html = self.fig_to_html(fig)
491
+ plt.close(fig)
 
 
492
  html_result = self.format_image_results(results)
 
493
  return image, html_result, plot_html
 
494
  except Exception as e:
495
  self.logger.error(f"Error in image analysis: {e}")
496
  return image, f"Error analyzing image: {str(e)}", None
497
 
498
  def analyze_text(self, text):
 
 
 
 
 
 
 
 
 
499
  if not text or text.strip() == "":
500
  return "", "Please enter medical report text.", None
 
501
  if not self.load_models():
502
  return text, "Error: Models not loaded properly.", None
 
503
  try:
504
  self.logger.info("Analyzing text")
 
 
505
  results = {
506
  "entities": [
507
  {"text": "chest X-ray", "type": "PROCEDURE", "confidence": 0.95},
 
511
  "sentiment": "neutral",
512
  "key_findings": ["Normal heart size", "Clear lungs", "8mm nodular opacity"]
513
  }
 
 
514
  html_result = self.format_text_results(results)
 
 
515
  plot_html = self.create_entity_visualization(results["entities"])
 
516
  return text, html_result, plot_html
 
517
  except Exception as e:
518
  self.logger.error(f"Error in text analysis: {e}")
519
  return text, f"Error analyzing text: {str(e)}", None
520
 
521
  def analyze_multimodal(self, image, text):
 
 
 
 
 
 
 
 
 
 
522
  if image is None and (not text or text.strip() == ""):
523
  return "Please provide either an image or text for analysis.", None
 
524
  if not self.load_models():
525
  return "Error: Models not loaded properly.", None
 
526
  try:
527
  self.logger.info("Performing multimodal analysis")
 
 
528
  results = {
529
  "combined_finding": "Normal chest X-ray with minor findings",
530
  "confidence": 0.92,
 
535
  "Monitor for any changes in symptoms"
536
  ]
537
  }
 
 
538
  html_result = self.format_multimodal_results(results)
 
 
539
  plot_html = self.create_multimodal_visualization(results)
 
540
  return html_result, plot_html
 
541
  except Exception as e:
542
  self.logger.error(f"Error in multimodal analysis: {e}")
543
  return f"Error in multimodal analysis: {str(e)}", None
544
 
545
  def format_image_results(self, results):
 
546
  html_result = f"""
547
+ <div class="medisync-card medisync-card-bg medisync-force-text">
548
+ <h2 class="medisync-title medisync-blue">X-ray Analysis Results</h2>
549
  <p><strong>Primary Finding:</strong> {results.get("primary_finding", "Unknown")}</p>
550
  <p><strong>Confidence:</strong> {results.get("confidence", 0):.1%}</p>
551
  <p><strong>Abnormality Detected:</strong> {"Yes" if results.get("has_abnormality", False) else "No"}</p>
 
552
  <h3>Top Predictions:</h3>
553
  <ul>
554
  """
 
555
  for label, prob in results.get("predictions", [])[:5]:
556
  html_result += f"<li>{label}: {prob:.1%}</li>"
 
557
  html_result += "</ul></div>"
558
  return html_result
559
 
560
  def format_text_results(self, results):
 
561
  html_result = f"""
562
+ <div class="medisync-card medisync-card-bg medisync-force-text">
563
+ <h2 class="medisync-title medisync-green">Text Analysis Results</h2>
564
  <p><strong>Sentiment:</strong> {results.get("sentiment", "Unknown").title()}</p>
 
565
  <h3>Key Findings:</h3>
566
  <ul>
567
  """
 
568
  for finding in results.get("key_findings", []):
569
  html_result += f"<li>{finding}</li>"
 
570
  html_result += "</ul>"
 
571
  html_result += "<h3>Extracted Entities:</h3><ul>"
572
  for entity in results.get("entities", [])[:5]:
573
  html_result += f"<li><strong>{entity['text']}</strong> ({entity['type']}) - {entity['confidence']:.1%}</li>"
 
574
  html_result += "</ul></div>"
575
  return html_result
576
 
577
  def format_multimodal_results(self, results):
 
578
  html_result = f"""
579
+ <div class="medisync-card medisync-card-bg medisync-force-text">
580
+ <h2 class="medisync-title medisync-purple">Multimodal Analysis Results</h2>
581
  <p><strong>Combined Finding:</strong> {results.get("combined_finding", "Unknown")}</p>
582
  <p><strong>Overall Confidence:</strong> {results.get("confidence", 0):.1%}</p>
 
583
  <h3>Image Contribution:</h3>
584
  <p>{results.get("image_contribution", "No image analysis available")}</p>
 
585
  <h3>Text Contribution:</h3>
586
  <p>{results.get("text_contribution", "No text analysis available")}</p>
 
587
  <h3>Recommendations:</h3>
588
  <ul>
589
  """
 
590
  for rec in results.get("recommendations", []):
591
  html_result += f"<li>{rec}</li>"
 
592
  html_result += "</ul></div>"
593
  return html_result
594
 
595
  def plot_image_prediction(self, image, predictions, title):
 
596
  fig, ax = plt.subplots(figsize=(10, 6))
597
  ax.imshow(image)
598
+ ax.set_title(title, fontsize=14, fontweight='bold', color='#007bff')
599
  ax.axis('off')
600
  return fig
601
 
602
  def create_entity_visualization(self, entities):
 
603
  if not entities:
604
  return "<p>No entities found in text.</p>"
 
605
  fig, ax = plt.subplots(figsize=(10, 6))
 
606
  entity_types = {}
607
  for entity in entities:
608
  entity_type = entity['type']
609
  if entity_type not in entity_types:
610
  entity_types[entity_type] = 0
611
  entity_types[entity_type] += 1
 
612
  if entity_types:
613
+ ax.bar(entity_types.keys(), entity_types.values(), color='#00bfae')
614
+ ax.set_title('Entity Types Found in Text', fontsize=14, fontweight='bold', color='#00bfae')
615
+ ax.set_ylabel('Count', color='#00bfae')
616
+ plt.xticks(rotation=45, color='#222')
617
+ plt.yticks(color='#222')
618
  return self.fig_to_html(fig)
619
 
620
  def create_multimodal_visualization(self, results):
 
621
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
 
 
622
  confidence = results.get("confidence", 0)
623
+ ax1.pie([confidence, 1-confidence], labels=['Confidence', 'Uncertainty'],
624
+ colors=['#00bfae', '#ff7675'], autopct='%1.1f%%', textprops={'color': '#222'})
625
+ ax1.set_title('Analysis Confidence', fontweight='bold', color='#00bfae')
 
 
626
  recommendations = results.get("recommendations", [])
627
+ ax2.bar(['Recommendations'], [len(recommendations)], color='#6c63ff')
628
+ ax2.set_title('Number of Recommendations', fontweight='bold', color='#6c63ff')
629
+ ax2.set_ylabel('Count', color='#6c63ff')
 
630
  plt.tight_layout()
631
  return self.fig_to_html(fig)
632
 
633
  def fig_to_html(self, fig):
 
634
  import io
635
  import base64
 
636
  buf = io.BytesIO()
637
+ fig.savefig(buf, format='png', bbox_inches='tight', dpi=100, facecolor=fig.get_facecolor())
638
  buf.seek(0)
639
  img_str = base64.b64encode(buf.read()).decode()
640
  buf.close()
641
+ return f'<img src="data:image/png;base64,{img_str}" style="max-width: 100%; height: auto; background: transparent;"/>'
 
642
 
643
  def complete_appointment(appointment_id):
 
 
 
 
 
 
 
 
 
644
  try:
 
645
  flask_urls = get_flask_urls()
 
646
  payload = {"appointment_id": appointment_id}
 
647
  for flask_api_url in flask_urls:
648
  try:
649
  logger.info(f"Trying to connect to: {flask_api_url}")
650
  response = requests.post(flask_api_url, json=payload, timeout=TIMEOUT_SETTINGS["connection_timeout"])
 
651
  if response.status_code == 200:
652
  return {"status": "success", "message": "Appointment completed successfully"}
653
  elif response.status_code == 404:
 
655
  else:
656
  logger.warning(f"Unexpected response from {flask_api_url}: {response.status_code}")
657
  continue
 
658
  except requests.exceptions.ConnectionError:
659
  logger.warning(f"Connection failed to {flask_api_url}")
660
  continue
 
664
  except Exception as e:
665
  logger.warning(f"Error with {flask_api_url}: {e}")
666
  continue
 
 
667
  return {
668
+ "status": "error",
669
  "message": "Cannot connect to Flask app. Please ensure the Flask app is running and accessible."
670
  }
 
671
  except Exception as e:
672
  logger.error(f"Error completing appointment: {e}")
673
  return {"status": "error", "message": f"Error: {str(e)}"}
674
 
675
  def create_interface():
 
 
676
  app = MediSyncApp()
 
 
677
  example_report = """
678
  CHEST X-RAY EXAMINATION
679
+
680
  CLINICAL HISTORY: 55-year-old male with cough and fever.
681
+
682
  FINDINGS: The heart size is at the upper limits of normal. The lungs are clear without focal consolidation,
683
  effusion, or pneumothorax. There is mild prominence of the pulmonary vasculature. No pleural effusion is seen.
684
  There is a small nodular opacity noted in the right lower lobe measuring approximately 8mm, which is suspicious
685
  and warrants further investigation. The mediastinum is unremarkable. The visualized bony structures show no acute abnormalities.
686
+
687
  IMPRESSION:
688
  1. Mild cardiomegaly.
689
  2. 8mm nodular opacity in the right lower lobe, recommend follow-up CT for further evaluation.
690
  3. No acute pulmonary parenchymal abnormality.
691
+
692
  RECOMMENDATIONS: Follow-up chest CT to further characterize the nodular opacity in the right lower lobe.
693
  """
694
 
 
695
  sample_images_dir = Path(parent_dir) / "data" / "sample"
696
+ sample_images = list(sample_images_dir.glob("*.png")) + list(sample_images_dir.glob("*.jpg"))
697
+ sample_image_path = str(sample_images[0]) if sample_images else None
 
 
 
 
 
698
 
 
699
  with gr.Blocks(
700
+ title="MediSync: Multi-Modal Medical Analysis System",
701
+ theme=gr.themes.Default(), # Use Default for HuggingFace dark/light support
702
+ css="""
703
+ /* Modern neumorphic card style for all result containers */
704
+ .medisync-card {
705
+ border-radius: 18px;
706
+ box-shadow: 0 4px 24px 0 rgba(0,0,0,0.10), 0 1.5px 4px 0 rgba(0,191,174,0.08);
707
+ margin: 18px 0;
708
+ padding: 24px 24px 18px 24px;
709
+ font-size: 1.08rem;
710
+ transition: background 0.2s, color 0.2s;
711
+ }
712
+ .medisync-card-bg {
713
+ background: var(--background-fill-primary, #f8f9fa);
714
+ color: var(--body-text-color, #222);
715
+ }
716
+ .medisync-title {
717
+ font-weight: 700;
718
+ margin-bottom: 0.7em;
719
+ }
720
+ .medisync-blue { color: #00bfae; }
721
+ .medisync-green { color: #28a745; }
722
+ .medisync-purple { color: #6c63ff; }
723
+ .medisync-card ul, .medisync-card ol {
724
+ margin-left: 1.2em;
725
+ }
726
+ .medisync-card li {
727
+ margin-bottom: 0.2em;
728
+ }
729
+ /* Button and input styling for modern look */
730
+ .gr-button, .end-consultation-btn {
731
+ border-radius: 8px !important;
732
+ font-weight: 600 !important;
733
+ font-size: 1.08rem !important;
734
+ transition: background 0.2s, color 0.2s;
735
+ }
736
+ .end-consultation-btn {
737
+ background: linear-gradient(90deg, #dc3545 60%, #ff7675 100%) !important;
738
+ border: none !important;
739
+ color: #fff !important;
740
+ box-shadow: 0 2px 8px 0 rgba(220,53,69,0.10);
741
+ }
742
+ .end-consultation-btn:hover {
743
+ background: linear-gradient(90deg, #c82333 60%, #ff7675 100%) !important;
744
+ }
745
+ /* Responsive tweaks */
746
+ @media (max-width: 900px) {
747
+ .medisync-card { padding: 16px 8px 12px 8px; }
748
+ }
749
+ /* Ensure text is visible in dark mode */
750
+ html[data-theme="dark"] .medisync-card-bg,
751
+ html[data-theme="dark"] .medisync-card-bg.medisync-force-text {
752
+ background: #23272f !important;
753
+ color: #f8fafc !important;
754
+ }
755
+ html[data-theme="dark"] .medisync-title {
756
+ color: #00bfae !important;
757
+ }
758
+ html[data-theme="dark"] .medisync-blue { color: #00bfae !important; }
759
+ html[data-theme="dark"] .medisync-green { color: #00e676 !important; }
760
+ html[data-theme="dark"] .medisync-purple { color: #a385ff !important; }
761
+ /* Make sure all gradio labels and text are visible */
762
+ label, .gr-label, .gr-text, .gr-html, .gr-markdown {
763
+ color: var(--body-text-color, #222) !important;
764
+ }
765
+ 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 {
766
+ color: #f8fafc !important;
767
+ }
768
+ /* Force all text in medisync-card and status outputs to be visible in all themes */
769
+ .medisync-force-text, .medisync-force-text * {
770
+ color: var(--body-text-color, #222) !important;
771
+ }
772
+ html[data-theme="dark"] .medisync-force-text, html[data-theme="dark"] .medisync-force-text * {
773
+ color: #f8fafc !important;
774
+ }
775
+ /* End consultation status output force text color */
776
+ #end_consultation_status, #end_consultation_status * {
777
+ color: var(--body-text-color, #222) !important;
778
+ }
779
+ html[data-theme="dark"] #end_consultation_status, html[data-theme="dark"] #end_consultation_status * {
780
+ color: #f8fafc !important;
781
+ }
782
+ """
783
  ) as interface:
784
+ gr.Markdown(
785
+ """
786
+ <div style="display: flex; align-items: center; gap: 16px; margin-bottom: 0.5em;">
787
+ <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);">
788
+ <span style="font-size: 2.1rem; font-weight: 700; color: #00bfae;">MediSync</span>
789
+ </div>
790
+ <div style="font-size: 1.18rem; margin-bottom: 1.2em;">
791
+ <span style="color: var(--body-text-color, #222);">AI-powered Multi-Modal Medical Analysis System</span>
792
+ </div>
793
+ <div style="font-size: 1.05rem; margin-bottom: 1.2em;">
794
+ <span style="color: var(--body-text-color, #222);">Seamlessly analyze X-ray images and medical reports for comprehensive healthcare insights.</span>
795
+ </div>
796
+ <div style="margin-bottom: 1.2em;">
797
+ <ul style="font-size: 1.01rem; color: var(--body-text-color, #222);">
798
+ <li>Upload a chest X-ray image</li>
799
+ <li>Enter the corresponding medical report text</li>
800
+ <li>Choose the analysis type: <b>Image</b>, <b>Text</b>, or <b>Multimodal</b></li>
801
+ <li>Click <b>End Consultation</b> to complete your appointment</li>
802
+ </ul>
803
+ </div>
804
+ """,
805
+ elem_id="medisync-header"
806
+ )
807
 
 
808
  with gr.Row():
 
809
  import urllib.parse
810
  try:
 
811
  url_params = {}
812
  if hasattr(gr, 'get_current_url'):
813
  current_url = gr.get_current_url()
814
  if current_url:
815
  parsed = urllib.parse.urlparse(current_url)
816
  url_params = urllib.parse.parse_qs(parsed.query)
 
817
  default_appointment_id = url_params.get('appointment_id', [''])[0]
818
  except:
819
  default_appointment_id = ""
 
820
  appointment_id_input = gr.Textbox(
821
  label="Appointment ID",
822
  placeholder="Enter your appointment ID here...",
823
  info="This will be automatically populated if you came from the doctors page",
824
+ value=default_appointment_id,
825
+ elem_id="appointment_id_input"
826
  )
827
 
828
+ with gr.Tab("🧬 Multimodal Analysis"):
829
  with gr.Row():
830
  with gr.Column():
831
+ multi_img_input = gr.Image(label="Upload X-ray Image", type="pil", elem_id="multi_img_input")
832
+ multi_img_enhance = gr.Button("Enhance Image", icon="✨")
 
833
  multi_text_input = gr.Textbox(
834
  label="Enter Medical Report Text",
835
  placeholder="Enter the radiologist's report text here...",
836
  lines=10,
837
  value=example_report if sample_image_path is None else None,
838
+ elem_id="multi_text_input"
839
  )
840
+ multi_analyze_btn = gr.Button("Analyze Image & Text", variant="primary", icon="🔎")
 
 
 
 
841
  with gr.Column():
842
+ multi_results = gr.HTML(label="Analysis Results", elem_id="multi_results")
843
+ multi_plot = gr.HTML(label="Visualization", elem_id="multi_plot")
 
 
844
  if sample_image_path:
845
  gr.Examples(
846
  examples=[[sample_image_path, example_report]],
 
848
  label="Example X-ray and Report",
849
  )
850
 
851
+ with gr.Tab("🖼️ Image Analysis"):
852
  with gr.Row():
853
  with gr.Column():
854
+ img_input = gr.Image(label="Upload X-ray Image", type="pil", elem_id="img_input")
855
+ img_enhance = gr.Button("Enhance Image", icon="✨")
856
+ img_analyze_btn = gr.Button("Analyze Image", variant="primary", icon="🔎")
 
857
  with gr.Column():
858
+ img_output = gr.Image(label="Processed Image", elem_id="img_output")
859
+ img_results = gr.HTML(label="Analysis Results", elem_id="img_results")
860
+ img_plot = gr.HTML(label="Visualization", elem_id="img_plot")
 
 
861
  if sample_image_path:
862
  gr.Examples(
863
  examples=[[sample_image_path]],
 
865
  label="Example X-ray Image",
866
  )
867
 
868
+ with gr.Tab("📝 Text Analysis"):
869
  with gr.Row():
870
  with gr.Column():
871
  text_input = gr.Textbox(
 
873
  placeholder="Enter the radiologist's report text here...",
874
  lines=10,
875
  value=example_report,
876
+ elem_id="text_input"
877
  )
878
+ text_analyze_btn = gr.Button("Analyze Text", variant="primary", icon="🔎")
 
879
  with gr.Column():
880
+ text_output = gr.Textbox(label="Processed Text", elem_id="text_output")
881
+ text_results = gr.HTML(label="Analysis Results", elem_id="text_results")
882
+ text_plot = gr.HTML(label="Entity Visualization", elem_id="text_plot")
 
 
883
  gr.Examples(
884
  examples=[[example_report]],
885
  inputs=[text_input],
886
  label="Example Medical Report",
887
  )
888
 
 
889
  with gr.Row():
890
  with gr.Column():
891
  end_consultation_btn = gr.Button(
892
+ "End Consultation",
893
+ variant="stop",
894
  size="lg",
895
+ elem_classes=["end-consultation-btn"],
896
+ icon="🛑"
897
  )
898
+ end_consultation_status = gr.HTML(label="Status", elem_id="end_consultation_status")
899
 
900
+ with gr.Tab("ℹ️ About"):
901
+ gr.Markdown(
902
+ """
903
+ <div class="medisync-card medisync-card-bg medisync-force-text">
904
+ <h2 class="medisync-title medisync-blue">About MediSync</h2>
905
+ <p>
906
+ <b>MediSync</b> is an AI-powered healthcare solution that uses multi-modal analysis to provide comprehensive insights from medical images and reports.
907
+ </p>
908
+ <h3>Key Features</h3>
909
+ <ul>
910
+ <li><b>X-ray Image Analysis</b>: Detects abnormalities in chest X-rays using pre-trained vision models</li>
911
+ <li><b>Medical Report Processing</b>: Extracts key information from patient reports using NLP models</li>
912
+ <li><b>Multi-modal Integration</b>: Combines insights from both image and text data for more accurate analysis</li>
913
+ </ul>
914
+ <h3>Models Used</h3>
915
+ <ul>
916
+ <li><b>X-ray Analysis</b>: facebook/deit-base-patch16-224-medical-cxr</li>
917
+ <li><b>Medical Text Analysis</b>: medicalai/ClinicalBERT</li>
918
+ </ul>
919
+ <h3 style="color:#dc3545;">Important Disclaimer</h3>
920
+ <p>
921
+ 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.
922
+ </p>
923
+ </div>
924
+ """
925
+ )
926
 
927
+ # Event handlers
928
  multi_img_enhance.click(
929
  app.enhance_image, inputs=multi_img_input, outputs=multi_img_input
930
  )
 
933
  inputs=[multi_img_input, multi_text_input],
934
  outputs=[multi_results, multi_plot],
935
  )
 
936
  img_enhance.click(app.enhance_image, inputs=img_input, outputs=img_output)
937
  img_analyze_btn.click(
938
  app.analyze_image,
939
  inputs=img_input,
940
  outputs=[img_output, img_results, img_plot],
941
  )
 
942
  text_analyze_btn.click(
943
  app.analyze_text,
944
  inputs=text_input,
945
  outputs=[text_output, text_results, text_plot],
946
  )
947
 
 
948
  def handle_end_consultation(appointment_id):
949
  if not appointment_id or appointment_id.strip() == "":
950
+ return "<div class='medisync-force-text' style='color: #dc3545; padding: 10px; background-color: #ffe6e6; border-radius: 5px;'>Please enter your appointment ID first.</div>"
 
 
951
  result = complete_appointment(appointment_id.strip())
 
952
  if result["status"] == "success":
 
953
  doctors_urls = get_doctors_page_urls()
 
 
954
  html_response = f"""
955
+ <div class='medisync-force-text' style='color: #28a745; padding: 15px; background-color: #e6ffe6; border-radius: 5px; margin: 10px 0;'>
956
  <h3>✅ Consultation Completed Successfully!</h3>
957
  <p>{result['message']}</p>
958
  <p>Your appointment has been marked as completed.</p>
959
  <button onclick="window.open('{doctors_urls['local']}', '_blank')"
960
+ style="background-color: #00bfae; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px;">
961
  Return to Doctors Page (Local)
962
  </button>
963
  <button onclick="window.open('{doctors_urls['production']}', '_blank')"
964
+ style="background-color: #6c63ff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px; margin-left: 10px;">
965
  Return to Doctors Page (Production)
966
  </button>
967
  </div>
968
  """
969
  else:
 
970
  if "Cannot connect to Flask app" in result['message']:
 
971
  html_response = f"""
972
+ <div class='medisync-force-text' style='color: #ff9800; padding: 15px; background-color: #fff3cd; border-radius: 5px; margin: 10px 0;'>
973
  <h3>⚠️ Consultation Ready to Complete</h3>
974
  <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>
975
  <p><strong>Appointment ID:</strong> {appointment_id.strip()}</p>
 
981
  </ol>
982
  <div style="margin-top: 15px;">
983
  <button onclick="window.open('http://127.0.0.1:600/complete_appointment_manual?appointment_id={appointment_id.strip()}', '_blank')"
984
+ style="background-color: #00bfae; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px;">
985
  Complete Appointment
986
  </button>
987
  <button onclick="window.open('http://127.0.0.1:600/doctors', '_blank')"
988
+ style="background-color: #6c63ff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-right: 10px;">
989
  Return to Doctors Page
990
  </button>
991
  <button onclick="navigator.clipboard.writeText('{appointment_id.strip()}')"
992
+ style="background-color: #23272f; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;">
993
  Copy Appointment ID
994
  </button>
995
  </div>
 
997
  """
998
  else:
999
  html_response = f"""
1000
+ <div class='medisync-force-text' style='color: #dc3545; padding: 15px; background-color: #ffe6e6; border-radius: 5px; margin: 10px 0;'>
1001
  <h3>❌ Error Completing Consultation</h3>
1002
  <p>{result['message']}</p>
1003
  <p>Please try again or contact support if the problem persists.</p>
1004
  </div>
1005
  """
 
1006
  return html_response
1007
 
1008
  end_consultation_btn.click(
 
1011
  outputs=[end_consultation_status]
1012
  )
1013
 
1014
+ # JavaScript for appointment ID auto-population
1015
  gr.HTML("""
 
 
 
 
 
 
 
 
 
 
 
 
 
1016
  <script>
 
1017
  function getUrlParameter(name) {
1018
  name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
1019
  var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
1020
  var results = regex.exec(location.search);
1021
+ return results === null ? '' : decodeURIComponent(results[1].replace(/\\+/g, ' '));
1022
  }
 
 
1023
  function populateAppointmentId() {
1024
  var appointmentId = getUrlParameter('appointment_id');
 
 
1025
  if (appointmentId) {
1026
+ var input = document.getElementById('appointment_id_input');
1027
+ if (input) {
1028
+ input.value = appointmentId;
 
 
 
 
1029
  var event = new Event('input', { bubbles: true });
1030
+ input.dispatchEvent(event);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1031
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1032
  }
1033
  }
 
 
1034
  document.addEventListener('DOMContentLoaded', function() {
1035
+ setTimeout(populateAppointmentId, 800);
 
1036
  });
 
 
1037
  window.addEventListener('load', function() {
1038
+ setTimeout(populateAppointmentId, 1200);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1039
  });
1040
  </script>
1041
  """)
1042
 
 
1043
  interface.launch()
1044
 
 
1045
  if __name__ == "__main__":
1046
+ create_interface()