Spaces:
Sleeping
Sleeping
| """UI components for MelanoScope AI.""" | |
| import os | |
| import logging | |
| from typing import List | |
| import gradio as gr | |
| from ..config.settings import UIConfig | |
| from ..core.utils import create_empty_dataframe | |
| from .styles import get_custom_css, create_theme, get_header_html, get_footer_html, get_model_info_html | |
| logger = logging.getLogger(__name__) | |
| class MelanoScopeUI: | |
| """Handles UI components and layout.""" | |
| def __init__(self, model_instance, classes: List[str]): | |
| self.model = model_instance | |
| self.classes = classes | |
| self.theme = create_theme() | |
| self.css = get_custom_css() | |
| logger.info("UI initialized") | |
| def create_interface(self) -> gr.Blocks: | |
| """Create complete Gradio interface.""" | |
| with gr.Blocks(theme=self.theme, css=self.css) as interface: | |
| self._create_header() | |
| with gr.Row(equal_height=True): | |
| self._create_input_column() | |
| self._create_results_column() | |
| self._create_footer() | |
| self._setup_event_handlers() | |
| return interface | |
| def _create_header(self) -> None: | |
| """Create header section.""" | |
| with gr.Row(): | |
| with gr.Column(scale=6): | |
| gr.Markdown(get_header_html()) | |
| with gr.Column(scale=1, min_width=UIConfig.THEME_TOGGLE_MIN_WIDTH): | |
| try: | |
| self.dark_toggle = gr.ThemeMode(label="Theme", value="system") | |
| except Exception: | |
| gr.Markdown("") | |
| def _create_input_column(self) -> None: | |
| """Create input column with image upload and controls.""" | |
| with gr.Column(scale=UIConfig.LEFT_COLUMN_SCALE): | |
| self.image_input = gr.Image( | |
| type="numpy", | |
| label="Lesion Image", | |
| height=UIConfig.IMAGE_HEIGHT, | |
| sources=["upload", "clipboard"] | |
| ) | |
| with gr.Row(): | |
| self.analyze_btn = gr.Button("Analyze", variant="primary") | |
| self.clear_btn = gr.Button("Clear") | |
| self._create_examples_section() | |
| self.latency_output = gr.Label(label="Inference Time") | |
| def _create_examples_section(self) -> None: | |
| """Create examples section if files exist.""" | |
| example_files = [ | |
| "melanoma.jpg", "vascular_lesion.jpg" | |
| ] | |
| existing_examples = [f for f in example_files if os.path.exists(f)] | |
| if existing_examples: | |
| gr.Examples( | |
| examples=existing_examples, | |
| inputs=self.image_input, | |
| label="Quick Examples", | |
| cache_examples=False | |
| ) | |
| def _create_results_column(self) -> None: | |
| """Create results column with predictions and info.""" | |
| with gr.Column(scale=UIConfig.RIGHT_COLUMN_SCALE): | |
| self._create_prediction_results() | |
| self._create_information_tabs() | |
| def _create_prediction_results(self) -> None: | |
| """Create prediction results section.""" | |
| with gr.Group(): | |
| with gr.Row(): | |
| self.prediction_output = gr.Label(label="Primary Prediction", elem_classes=["pred-card"]) | |
| self.confidence_output = gr.Label(label="Confidence") | |
| self.probability_plot = gr.BarPlot( | |
| value=create_empty_dataframe(self.classes), | |
| x="item", y="probability", | |
| title="Probability Distribution (Top-k)", | |
| x_title="Class", y_title="Probability", | |
| vertical=False, tooltip=["item", "probability"], | |
| width=UIConfig.PLOT_WIDTH, height=UIConfig.PLOT_HEIGHT, | |
| ) | |
| def _create_information_tabs(self) -> None: | |
| """Create information tabs.""" | |
| with gr.Tabs(): | |
| with gr.TabItem("Medical Details"): | |
| self._create_medical_details() | |
| with gr.TabItem("About Model"): | |
| gr.Markdown(get_model_info_html()) | |
| def _create_medical_details(self) -> None: | |
| """Create medical details accordions.""" | |
| with gr.Accordion("Description", open=True): | |
| self.description_output = gr.Textbox(lines=UIConfig.TEXTBOX_LINES, interactive=False) | |
| with gr.Accordion("Symptoms", open=False): | |
| self.symptoms_output = gr.Textbox(lines=UIConfig.TEXTBOX_LINES, interactive=False) | |
| with gr.Accordion("Causes", open=False): | |
| self.causes_output = gr.Textbox(lines=UIConfig.TEXTBOX_LINES, interactive=False) | |
| with gr.Accordion("Treatment", open=False): | |
| self.treatment_output = gr.Textbox(lines=UIConfig.TEXTBOX_LINES, interactive=False) | |
| def _create_footer(self) -> None: | |
| """Create footer section.""" | |
| gr.Markdown(get_footer_html()) | |
| def _setup_event_handlers(self) -> None: | |
| """Set up event handlers.""" | |
| outputs = [ | |
| self.prediction_output, self.confidence_output, self.description_output, | |
| self.symptoms_output, self.causes_output, self.treatment_output, | |
| self.probability_plot, self.latency_output | |
| ] | |
| self.analyze_btn.click( | |
| fn=self.model.predict, inputs=[self.image_input], | |
| outputs=outputs, show_progress="full" | |
| ) | |
| self.clear_btn.click( | |
| fn=self._clear_all, inputs=[], | |
| outputs=[self.image_input] + outputs | |
| ) | |
| def _clear_all(self) -> tuple: | |
| """Clear all inputs and outputs.""" | |
| empty_df = create_empty_dataframe(self.classes) | |
| return (None, "", "", "", "", "", "", empty_df, "") | |