import os import base64 import json import tempfile from datetime import datetime from dataclasses import asdict from typing import List, Dict, Any from pathlib import Path import gradio as gr import pydicom from src.syntax_pred.config import CFG from src.syntax_pred.infer import Study, run_inference # ==================== КОНФИГУРАЦИЯ ДИЗАЙНА ==================== APPLE_STYLE_CSS = """ /* Сбалансированная современная цветовая гамма */ :root { --bg-primary: #4f46e5; --bg-secondary: #7c3aed; --bg-gradient: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #a855f7 100%); --bg-surface: linear-gradient(135deg, rgba(255,255,255,0.97) 0%, rgba(255,255,255,0.94) 100%); --accent-cyan: #06b6d4; --accent-teal: #3b82f6; --accent-purple: #8b5cf6; --accent-pink: #d946ef; --accent-orange: #f97316; --accent-green: #10b981; --accent-red: #ef4444; --accent-yellow: #eab308; --accent-cyan-light: rgba(6, 182, 212, 0.08); --accent-teal-light: rgba(59, 130, 246, 0.08); --accent-purple-light: rgba(139, 92, 246, 0.08); --accent-pink-light: rgba(217, 70, 239, 0.08); --accent-green-light: rgba(16, 185, 129, 0.08); --accent-red-light: rgba(239, 68, 68, 0.08); --accent-orange-light: rgba(249, 115, 22, 0.08); --text-primary: #1e293b; --text-secondary: #64748b; --text-light: #94a3b8; --text-white: #ffffff; --border-color: rgba(255, 255, 255, 0.2); --border-dark: rgba(0, 0, 0, 0.06); --shadow-sm: 0 4px 6px -1px rgba(0, 0, 0, 0.05); --shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.08); --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1); --shadow-glow: 0 0 0 3px rgba(6, 182, 212, 0.2); --shadow-glow-purple: 0 0 0 3px rgba(139, 92, 246, 0.2); } * { font-family: -apple-system, "SF Pro Text", "SF Pro Display", "Inter", "Helvetica Neue", system-ui, sans-serif; } body, .gradio-container { background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #a855f7 100%) !important; position: relative; } body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(circle at 20% 50%, rgba(255,255,255,0.08) 0%, transparent 70%); pointer-events: none; z-index: 0; } .gradio-container { position: relative; z-index: 1; } .gr-box, .gr-form, .panel, .tabs, .tab-nav, .accordion { background: transparent !important; border: none !important; } .apple-container { max-width: 1400px; margin: 0 auto; padding: 2rem 1.5rem; } .apple-header { text-align: center; margin-bottom: 2rem; position: relative; padding: 1rem 0; } .apple-title { font-size: 3.2rem; font-weight: 800; background: linear-gradient(135deg, #ffffff 0%, #e2e8f0 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; letter-spacing: -0.02em; margin: 0; text-shadow: 0 2px 10px rgba(0,0,0,0.05); } .apple-subtitle { font-size: 1.1rem; color: rgba(255, 255, 255, 0.85); margin-top: 0.5rem; font-weight: 400; } .apple-card { background: rgba(255, 255, 255, 0.96); backdrop-filter: blur(20px); border-radius: 24px; border: 1px solid rgba(255, 255, 255, 0.3); box-shadow: var(--shadow-md); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; } .apple-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); background: rgba(255, 255, 255, 0.98); } .apple-card-header { padding: 1.25rem 1.5rem; border-bottom: 1px solid rgba(0, 0, 0, 0.05); background: linear-gradient(135deg, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0.2) 100%); } .apple-card-title { font-size: 1.25rem; font-weight: 700; background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin: 0; display: flex; align-items: center; gap: 0.5rem; } .apple-card-content { padding: 1.5rem; } .add-study-card { background: linear-gradient(135deg, rgba(255,255,255,0.98) 0%, rgba(255,255,255,0.96) 100%); border: 1px solid rgba(255,255,255,0.4); position: relative; overflow: hidden; } .apple-button { background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%); color: white; border: none; border-radius: 980px; padding: 0.75rem 1.5rem; font-size: 0.9375rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: var(--shadow-sm); display: inline-flex; align-items: center; gap: 0.5rem; position: relative; overflow: hidden; } .apple-button::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.15), transparent); transition: left 0.5s; } .apple-button:hover::before { left: 100%; } .apple-button:hover { transform: translateY(-1px); box-shadow: var(--shadow-md); background: linear-gradient(135deg, #0891b2 0%, #2563eb 100%); } .apple-button-add { background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%); color: white; border: none; border-radius: 980px; padding: 0.75rem 1.5rem; font-size: 0.9375rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: var(--shadow-sm); display: inline-flex; align-items: center; gap: 0.5rem; position: relative; overflow: hidden; } .apple-button-add::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.15), transparent); transition: left 0.5s; } .apple-button-add:hover::before { left: 100%; } .apple-button-add:hover { transform: translateY(-1px); box-shadow: var(--shadow-md); background: linear-gradient(135deg, #7c3aed 0%, #9333ea 100%); } .apple-button-clear { background: linear-gradient(135deg, #f97316 0%, #fbbf24 100%); color: white; border: none; border-radius: 980px; padding: 0.75rem 1.5rem; font-size: 0.9375rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: var(--shadow-sm); display: inline-flex; align-items: center; gap: 0.5rem; position: relative; overflow: hidden; } .apple-button-clear::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.15), transparent); transition: left 0.5s; } .apple-button-clear:hover::before { left: 100%; } .apple-button-clear:hover { transform: translateY(-1px); box-shadow: var(--shadow-md); background: linear-gradient(135deg, #ea580c 0%, #eab308 100%); } .apple-input { border: 1.5px solid rgba(0, 0, 0, 0.05) !important; border-radius: 14px !important; padding: 0.75rem 1rem !important; font-size: 0.9375rem !important; background: rgba(255, 255, 255, 0.95) !important; transition: all 0.3s ease !important; } .apple-input:focus { outline: none !important; border-color: #06b6d4 !important; box-shadow: var(--shadow-glow) !important; background: white !important; } .apple-table { border-radius: 16px; overflow: hidden; border: none; } .apple-table table { background: rgba(255, 255, 255, 0.95) !important; border-collapse: collapse !important; width: 100%; } .apple-table th { background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%) !important; color: white !important; font-weight: 600 !important; font-size: 0.875rem !important; padding: 12px 16px !important; border-bottom: none !important; border-top: none !important; border-left: none !important; border-right: none !important; } .apple-table td { background: rgba(255, 255, 255, 0.98) !important; color: var(--text-primary) !important; padding: 12px 16px !important; border-bottom: 1px solid rgba(0, 0, 0, 0.05) !important; border-top: none !important; border-left: none !important; border-right: none !important; } .apple-table tr:last-child td { border-bottom: none !important; } .apple-table tr:hover td { background: rgba(139, 92, 246, 0.04) !important; } .status-badge { display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.25rem 0.75rem; border-radius: 20px; font-size: 0.8125rem; font-weight: 600; white-space: nowrap; } .status-running { background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%); color: white; box-shadow: var(--shadow-sm); } .status-done { background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; box-shadow: var(--shadow-sm); } .status-error { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white; box-shadow: var(--shadow-sm); } .status-queued { background: linear-gradient(135deg, #64748b 0%, #475569 100%); color: white; box-shadow: var(--shadow-sm); } @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.85; transform: scale(1.02); } } .pulse { animation: pulse 1.5s ease-in-out infinite; } ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.15); border-radius: 4px; } ::-webkit-scrollbar-thumb { background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: linear-gradient(135deg, #0891b2 0%, #2563eb 100%); } .upload-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } .instruction-box { background: linear-gradient(135deg, rgba(139, 92, 246, 0.06) 0%, rgba(6, 182, 212, 0.06) 100%); border-radius: 16px; padding: 1rem; margin-top: 1rem; border: 1px solid rgba(139, 92, 246, 0.15); backdrop-filter: blur(10px); } .instruction-title { font-size: 0.875rem; font-weight: 700; background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.5rem; } .instruction-list { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem; } .instruction-list li { font-size: 0.8125rem; color: var(--text-secondary); display: flex; align-items: center; gap: 0.5rem; font-weight: 500; } @media (max-width: 768px) { .upload-grid { grid-template-columns: 1fr; gap: 1rem; } .instruction-list { grid-template-columns: 1fr; } .apple-title { font-size: 2rem; } } """ # Логотип (base64) DEFAULT_LOGO = "assets/logo.png" LOGO_PATH = os.environ.get("LOGO_PATH", DEFAULT_LOGO) def _logo_html() -> str: """Генерация HTML для логотипа""" path = LOGO_PATH if not path or not os.path.exists(path): return "" try: with open(path, "rb") as f: data = base64.b64encode(f.read()).decode("ascii") ext = os.path.splitext(path)[1].lower() mime = "image/png" if ext in {".png", ""} else "image/jpeg" return f'Логотип' except Exception: return "" def _is_dicom_path(path: str) -> bool: """Проверка на DICOM файл""" if not os.path.exists(path): return False ext = os.path.splitext(path)[1].lower() if ext not in {".dcm", ""}: return False try: pydicom.dcmread(path, stop_before_pixels=True) return True except Exception: return False def _files_to_paths(files) -> List[str]: """Конвертация файлов в пути""" if not files: return [] raw_paths = [f.name for f in files if hasattr(f, "name") and os.path.exists(f.name)] return [p for p in raw_paths if _is_dicom_path(p)] def _collect_input_paths(files_all, folder_files) -> List[str]: """Сбор всех путей из загрузок""" combined = _files_to_paths(files_all) + _files_to_paths(folder_files) return list(dict.fromkeys(combined)) def _build_report_file(result: Dict[str, Any]) -> str: """Создание отчета в JSON""" ts = datetime.now().strftime("%Y%m%d_%H%M%S") with tempfile.NamedTemporaryFile( mode="w", prefix=f"autoangioscore_report_{ts}_", suffix=".json", encoding="utf-8", delete=False, ) as f: json.dump(result, f, ensure_ascii=False, indent=2) return f.name def _status_badge_html(state: str, with_animation: bool = False) -> str: """Генерация HTML для статус бейджа""" state = state.lower() status_config = { "running": {"class": "status-running", "icon": "⏱️", "text": "Выполняется"}, "done": {"class": "status-done", "icon": "✅", "text": "Завершено"}, "error": {"class": "status-error", "icon": "❌", "text": "Ошибка"}, "queued": {"class": "status-queued", "icon": "⏳", "text": "В очереди"} } config = status_config.get(state, status_config["queued"]) animation_class = " pulse" if with_animation and state == "running" else "" return f'{config["icon"]} {config["text"]}' def _update_status_table(studies, status: str) -> List[List]: """Обновление таблицы со статусами""" badge = _status_badge_html(status, with_animation=(status == "running")) return [ [s.get("name", "-"), s.get("description", "-") or "-", len(s.get("files", [])), badge] for s in (studies or []) ] def _format_results_html(result: Dict[str, Any]) -> str: """Форматирование результатов в красивом HTML с детальной информацией (одна кнопка на исследование)""" if not result: return '''
📊 Результаты появятся после запуска анализа
''' if "error" in result: return f'''
⚠️
Ошибка
{result.get("error", "Неизвестная ошибка")}
''' studies = result.get("studies", []) or [] if not studies: return '''
📋 Нет исследований для отображения
''' # Добавляем стили для аккордеона и кнопки (без JavaScript) accordion_styles = """ """ cards_html = [accordion_styles] for idx, s in enumerate(studies): study_name = s.get("study", "-") description = s.get("description", "") unique_id = f"details_{idx}_{study_name.replace(' ', '_').replace('/', '_')}" left_data = s.get("left", {}) right_data = s.get("right", {}) other_data = s.get("other", []) total_obj = s.get("total", {}) left_mean = left_data.get("mean", 0.0) right_mean = right_data.get("mean", 0.0) total_mean = total_obj.get("mean", 0.0) left_files_count = left_data.get("n_files", 0) right_files_count = right_data.get("n_files", 0) other_files_count = len(other_data) left_per_model = left_data.get("per_model", []) right_per_model = right_data.get("per_model", []) risk_key = next((k for k in total_obj.keys() if "High-risk" in str(k)), None) is_high_risk = bool(total_obj.get(risk_key, False)) if risk_key else total_mean > 0.5 if is_high_risk: badge_bg = "#fee2e2" badge_color = "#991b1b" badge_text = "⚠️ Высокий риск" else: badge_bg = "#dcfce7" badge_color = "#14532d" badge_text = "✅ Низкий риск" badge_style = f""" background: {badge_bg}; color: {badge_color}; padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: 700; display: inline-flex; align-items: center; gap: 6px; border: 1px solid rgba(0,0,0,0.05); """ # Генерация HTML для моделей левой артерии left_models_html = "" if left_per_model: left_models_html = '
' for i, model_info in enumerate(left_per_model, 1): model_score = model_info.get("score", 0.0) left_models_html += f'''
🎯 Модель {i} {model_score:.3f}
''' left_models_html += '
' else: left_models_html = '
Нет данных моделей
' # Генерация HTML для моделей правой артерии right_models_html = "" if right_per_model: right_models_html = '
' for i, model_info in enumerate(right_per_model, 1): model_score = model_info.get("score", 0.0) right_models_html += f'''
🎯 Модель {i} {model_score:.3f}
''' right_models_html += '
' else: right_models_html = '
Нет данных моделей
' # Файлы левой артерии (ВСЕ файлы) left_files_with_probs = left_data.get("files", []) left_files_html = "" if left_files_with_probs: left_files_html = '
' for file_info in left_files_with_probs: file_path = file_info.get("path", "unknown") file_name = os.path.basename(file_path) if file_path else "unknown" artery_prob = file_info.get("artery_prob", 0.0) left_files_html += f'''
📄 {file_name[:50]} {artery_prob:.3f}
''' left_files_html += '
' else: left_files_html = '
Нет файлов
' # Файлы правой артерии (ВСЕ файлы) right_files_with_probs = right_data.get("files", []) right_files_html = "" if right_files_with_probs: right_files_html = '
' for file_info in right_files_with_probs: file_path = file_info.get("path", "unknown") file_name = os.path.basename(file_path) if file_path else "unknown" artery_prob = file_info.get("artery_prob", 0.0) right_files_html += f'''
📄 {file_name[:50]} {artery_prob:.3f}
''' right_files_html += '
' else: right_files_html = '
Нет файлов
' # Нераспознанные файлы (ВСЕ файлы) other_files_html = "" if other_data: other_files_html = '
' for file_info in other_data: file_path = file_info.get("path", "unknown") file_name = os.path.basename(file_path) if file_path else "unknown" artery_prob = file_info.get("artery_prob", 0.0) left_threshold = 0.10 right_threshold = 0.90 if artery_prob <= left_threshold: prob_status = "⚠️ Ниже порога левой артерии" prob_color = "#f97316" elif artery_prob >= right_threshold: prob_status = "⚠️ Выше порога правой артерии" prob_color = "#f97316" else: prob_status = "❓ В серой зоне (неопределён)" prob_color = "#ef4444" other_files_html += f'''
📄 {file_name[:50]} {prob_status}
{artery_prob:.3f}
вероятность
''' other_files_html += '
' else: other_files_html = '
Нераспознанных файлов нет
' card_html = f'''
🏥 {study_name}
{badge_text}
{f'
{description}
' if description else ''}
🧬 Левая артерия
{left_mean:.3f}
SYNTAX Score
📁 {left_files_count} файлов
🧬 Правая артерия
{right_mean:.3f}
SYNTAX Score
📁 {right_files_count} файлов
📊 Общий SYNTAX Score
{total_mean:.3f}
{'⚠️ Рекомендовано вмешательство' if is_high_risk else '✅ Плановое наблюдение'}
Показать детальную информацию Скрыть детальную информацию
🎯 SYNTAX Score по моделям (левая артерия)
{left_models_html}
🎯 SYNTAX Score по моделям (правая артерия)
{right_models_html}
📂 Файлы левой артерии (вероятность ≤ 0.10) {left_files_count}
{left_files_html}
📂 Файлы правой артерии (вероятность ≥ 0.90) {right_files_count}
{right_files_html}
Нераспознанные файлы (0.10 < вероятность < 0.90) {other_files_count}
{other_files_html}
💡 Вероятность классификации артерии: ≤0.10 → левая, ≥0.90 → правая
🎯 Порог высокого риска SYNTAX: ≥22.0 🧠 Ансамбль моделей: {len(left_per_model)} левых / {len(right_per_model)} правых
''' cards_html.append(card_html) return "".join(cards_html) def create_ui(): """Создание интерфейса в стиле Apple""" with gr.Blocks(title="AutoAngioScore", css=APPLE_STYLE_CSS) as demo: # Главный контейнер with gr.Column(elem_classes="apple-container"): # Хедер with gr.Row(elem_classes="apple-header"): with gr.Column(scale=1, min_width=0): logo_html = _logo_html() if logo_html: gr.HTML(f'
{logo_html}
') gr.HTML('''

AutoAngioScore

Автоматическая оценка степени коронарного поражения по видеозаписям ангиографии

''') # ==================== БЛОК ДОБАВЛЕНИЯ ИССЛЕДОВАНИЯ ==================== with gr.Group(elem_classes="apple-card add-study-card"): with gr.Column(elem_classes="apple-card-header"): gr.HTML('

✨ Добавление нового исследования

') with gr.Column(elem_classes="apple-card-content"): # Информация об исследовании with gr.Row(): study_name = gr.Textbox( label="Идентификатор исследования", placeholder="Например: INV-2024-001", elem_classes="apple-input", scale=1 ) study_desc = gr.Textbox( label="Описание (необязательно)", placeholder="Клинические данные, особенности...", elem_classes="apple-input", scale=1 ) # Загрузка DICOM - две колонки gr.HTML('
') with gr.Column(elem_classes="apple-card", variant="panel"): with gr.Column(elem_classes="apple-card-header"): gr.HTML('

📄 Отдельные файлы

') with gr.Column(elem_classes="apple-card-content"): files_all = gr.File( label="Выберите DICOM файлы", file_count="multiple", elem_classes="apple-input" ) gr.HTML('''
✓ Поддерживаются файлы .dcm
''') with gr.Column(elem_classes="apple-card", variant="panel"): with gr.Column(elem_classes="apple-card-header"): gr.HTML('

📁 Папка с исследованием

') with gr.Column(elem_classes="apple-card-content"): files_folder = gr.File( label="Выберите папку", file_count="directory", elem_classes="apple-input" ) gr.HTML('''
✓ Выберите папку, содержащую DICOM файлы
''') gr.HTML('
') # Кнопки управления добавлением with gr.Row(): btn_add = gr.Button( "✨ Добавить в очередь", elem_classes="apple-button-add", scale=1 ) btn_clear = gr.Button( "🗑️ Очистить форму", elem_classes="apple-button-clear", scale=1 ) # Краткая инструкция gr.HTML('''
🎯 Быстрый старт
''') # ==================== ОЧЕРЕДЬ ИССЛЕДОВАНИЙ ==================== with gr.Group(elem_classes="apple-card"): with gr.Column(elem_classes="apple-card-header"): gr.HTML('

📋 Очередь исследований

') with gr.Column(elem_classes="apple-card-content"): queue_table = gr.Dataframe( headers=["Исследование", "Описание", "Файлов", "Статус"], datatype=["str", "str", "number", "html"], interactive=False, elem_classes="apple-table", row_count=(0, "dynamic"), wrap=True ) gr.HTML('''
💡 Добавьте исследования в очередь для пакетного анализа
''') # ==================== ЗАПУСК АНАЛИЗА ==================== with gr.Row(): run_btn = gr.Button( "🚀 Запустить анализ очереди", elem_classes="apple-button" ) # ==================== РЕЗУЛЬТАТЫ ==================== with gr.Group(elem_classes="apple-card"): with gr.Column(elem_classes="apple-card-header"): gr.HTML('

📊 Результаты анализа

') with gr.Column(elem_classes="apple-card-content"): out_summary = gr.HTML(label="") out_json = gr.JSON(label="Подробные данные", visible=False) # Скрытый JSON для логики with gr.Row(): out_report = gr.File( label="📥 Скачать отчет JSON", interactive=False, elem_classes="apple-input" ) # Footer gr.HTML('''

⚠️ Для исследовательских целей. Не является медицинским устройством.

© 2024 AutoAngioScore | Инновационная оценка коронарного поражения

''') # ==================== ЛОГИКА ==================== studies_state = gr.State([]) def _add_study_fn(studies: List[Dict[str, Any]], name: str, desc: str, files, folder): name = (name or "").strip() or f"Исследование_{len(studies)+1}" desc = (desc or "").strip() paths = _collect_input_paths(files, folder) if not paths: table = _update_status_table(studies, "Queued") return studies, table, name, desc, files, folder new_study = { "name": name, "description": desc, "files": paths } studies = studies + [new_study] table = _update_status_table(studies, "Queued") return studies, table, "", "", None, None btn_add.click( _add_study_fn, inputs=[studies_state, study_name, study_desc, files_all, files_folder], outputs=[studies_state, queue_table, study_name, study_desc, files_all, files_folder] ) def _clear_form(): return "", "", None, None btn_clear.click(_clear_form, inputs=None, outputs=[study_name, study_desc, files_all, files_folder]) def _clear_all(): return [], [] # Добавляем кнопку очистки очереди (скрытая функциональность) with gr.Row(visible=False): clear_queue = gr.Button("Очистить очередь") clear_queue.click(_clear_all, inputs=None, outputs=[studies_state, queue_table]) def _before_run(studies): return _update_status_table(studies, "Running") def _run_infer(studies): study_objs = [] for s in (studies or []): study_objs.append(Study(name=s["name"], description=s.get("description", ""), files=s["files"])) result = run_inference(study_objs) report_path = _build_report_file(result) return result, report_path def _after_run(studies, result, report_path): table = _update_status_table(studies, "Done") return _format_results_html(result), result, report_path, table run_btn.click( _before_run, inputs=[studies_state], outputs=[queue_table] ).then( _run_infer, inputs=[studies_state], outputs=[out_json, out_report] ).then( _after_run, inputs=[studies_state, out_json, out_report], outputs=[out_summary, out_json, out_report, queue_table] ) return demo if __name__ == "__main__": favicon = LOGO_PATH if (LOGO_PATH and os.path.exists(LOGO_PATH)) else None demo = create_ui() demo.launch( favicon_path=favicon, server_name="0.0.0.0", server_port=7860, share=False, )