MelanoScopeAI / src /ui /components.py
dcavadia
update examples
bf08742
"""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, "")