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

updated the UI ✅✅

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