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