openfree's picture
Update app.py
d79b5d6 verified
#!/usr/bin/env python3
import torch
from transformers import ViTImageProcessor, ViTForImageClassification
from PIL import Image
import gradio as gr
import numpy as np
from typing import Dict, Tuple, Optional
import logging
from pathlib import Path
import spaces
from gradio_client import Client
import json
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Medical-grade CSS styling
medical_css = """
.gradio-container {
background: linear-gradient(135deg, #e8f4f8 0%, #d1e8e4 50%, #c3d9e1 100%);
font-family: 'Inter', 'Segoe UI', sans-serif;
}
.main-header {
background: linear-gradient(135deg, #2c5282 0%, #2d3748 100%);
color: white;
padding: 2rem;
border-radius: 15px;
margin-bottom: 2rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.diagnosis-card {
background: white;
border-radius: 15px;
padding: 1.5rem;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
border-left: 4px solid #3182ce;
margin: 1rem 0;
}
.result-container {
background: linear-gradient(to right, #f7fafc, #edf2f7);
border-radius: 12px;
padding: 1.5rem;
margin-top: 1rem;
border: 1px solid #cbd5e0;
}
.confidence-bar {
background: linear-gradient(90deg, #48bb78 0%, #38a169 100%);
height: 8px;
border-radius: 4px;
transition: width 0.5s ease;
}
.medical-badge {
display: inline-block;
padding: 0.5rem 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 25px;
font-weight: 600;
font-size: 0.9rem;
margin: 0.25rem;
}
.warning-box {
background: #fff5f5;
border-left: 4px solid #fc8181;
padding: 1rem;
border-radius: 8px;
margin: 1rem 0;
}
.info-box {
background: #ebf8ff;
border-left: 4px solid #4299e1;
padding: 1rem;
border-radius: 8px;
margin: 1rem 0;
}
.button-primary {
background: linear-gradient(135deg, #3182ce 0%, #2c5282 100%);
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s;
}
.button-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(49, 130, 206, 0.3);
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
animation: pulse 2s infinite;
}
.status-ready {
background-color: #48bb78;
}
.status-processing {
background-color: #ed8936;
}
.status-error {
background-color: #f56565;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(72, 187, 120, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(72, 187, 120, 0);
}
}
.diagnosis-severity {
padding: 0.25rem 0.75rem;
border-radius: 15px;
font-size: 0.85rem;
font-weight: 600;
display: inline-block;
margin-left: 0.5rem;
}
.severity-low {
background: #c6f6d5;
color: #22543d;
}
.severity-medium {
background: #fed7aa;
color: #7c2d12;
}
.severity-high {
background: #fed7d7;
color: #742a2a;
}
"""
class MedicalSkinDiagnosisSystem:
"""Medical-grade skin disease diagnosis system with GPT-OSS integration."""
def __init__(self, model_name: str = '0xnu/skincare-detection'):
"""Initialize the diagnosis system."""
self.model_name = model_name
self.processor = None
self.model = None
self.id2label = None
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.gpt_client = None
self.load_model()
# Load disease database AFTER model is loaded to get actual classes
self.disease_info_db = self._load_disease_database()
self._init_gpt_client()
def _load_disease_database(self) -> Dict:
"""Load comprehensive disease information database based on actual model classes."""
# Get actual classes from the loaded model
if self.model and self.id2label:
logger.info(f"Loaded classes from model: {list(self.id2label.values())}")
# Comprehensive database covering common skin conditions found in skincare datasets
database = {
# Acne and related conditions
"acne": {
"name_ko": "์—ฌ๋“œ๋ฆ„",
"severity": "low",
"description_ko": "๋ชจ๋‚ญ๊ณผ ํ”ผ์ง€์„ ์˜ ๋งŒ์„ฑ ์—ผ์ฆ์„ฑ ์งˆํ™˜์œผ๋กœ, ๋ฉดํฌ, ๊ตฌ์ง„, ๋†ํฌ, ๊ฒฐ์ ˆ ๋“ฑ์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.",
"treatment_ko": "๊ตญ์†Œ ๋ ˆํ‹ฐ๋…ธ์ด๋“œ, ๋ฒค์กฐ์ผ ํผ์˜ฅ์‚ฌ์ด๋“œ, ํ•ญ์ƒ์ œ ์—ฐ๊ณ , ์‹ฌํ•œ ๊ฒฝ์šฐ ์ด์†ŒํŠธ๋ ˆํ‹ฐ๋…ธ์ธ ๊ฒฝ๊ตฌ ๋ณต์šฉ",
"symptoms_ko": "๋ธ”๋ž™ํ—ค๋“œ, ํ™”์ดํŠธํ—ค๋“œ, ๋ถ‰์€ ๊ตฌ์ง„, ๋†ํฌ, ํ†ต์ฆ์„ฑ ๊ฒฐ์ ˆ"
},
"blackhead": {
"name_ko": "๋ธ”๋ž™ํ—ค๋“œ/๊ฐœ๋ฐฉ๋ฉดํฌ",
"severity": "low",
"description_ko": "๋ชจ๊ณต์ด ํ”ผ์ง€์™€ ๊ฐ์งˆ๋กœ ๋ง‰ํ˜€ ๊ฒ€๊ฒŒ ๋ณด์ด๋Š” ๊ฐœ๋ฐฉ์„ฑ ๋ฉดํฌ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "์‚ด๋ฆฌ์‹ค์‚ฐ, BHA ์ œํ’ˆ, ๋ ˆํ‹ฐ๋…ธ์ด๋“œ, ์ •๊ธฐ์ ์ธ ๊ฐ์งˆ ์ œ๊ฑฐ",
"symptoms_ko": "๋ชจ๊ณต ๋‚ด ๊ฒ€์€ ์ , ์ฃผ๋กœ T์กด์— ๋ฐœ์ƒ"
},
"whitehead": {
"name_ko": "ํ™”์ดํŠธํ—ค๋“œ/ํ์‡„๋ฉดํฌ",
"severity": "low",
"description_ko": "ํ”ผ์ง€์™€ ๊ฐ์งˆ์ด ๋ชจ๊ณต์„ ๋ง‰์•„ ์ƒ๊ธฐ๋Š” ํ์‡„์„ฑ ๋ฉดํฌ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ ˆํ‹ฐ๋…ธ์ด๋“œ, AHA/BHA ์ œํ’ˆ, ๋ถ€๋“œ๋Ÿฌ์šด ๊ฐ์งˆ ์ œ๊ฑฐ",
"symptoms_ko": "ํ”ผ๋ถ€ ํ‘œ๋ฉด์˜ ์ž‘์€ ํฐ์ƒ‰ ๋Œ๊ธฐ"
},
"papule": {
"name_ko": "๊ตฌ์ง„",
"severity": "low",
"description_ko": "์—ผ์ฆ์ด ์žˆ๋Š” ์ž‘์€ ๋ถ‰์€ ๋Œ๊ธฐ๋กœ, ์—ฌ๋“œ๋ฆ„์˜ ํ•œ ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "ํ•ญ์—ผ์ฆ ์น˜๋ฃŒ์ œ, ๊ตญ์†Œ ํ•ญ์ƒ์ œ, ๋ฒค์กฐ์ผ ํผ์˜ฅ์‚ฌ์ด๋“œ",
"symptoms_ko": "๋ถ‰๊ณ  ๋‹จ๋‹จํ•œ ์ž‘์€ ๋Œ๊ธฐ, ์••ํ†ต"
},
"pustule": {
"name_ko": "๋†ํฌ",
"severity": "medium",
"description_ko": "๊ณ ๋ฆ„์ด ์ฐฌ ์—ผ์ฆ์„ฑ ๋ณ‘๋ณ€์œผ๋กœ, ์—ฌ๋“œ๋ฆ„์˜ ์ง„ํ–‰๋œ ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๊ตญ์†Œ ๋˜๋Š” ๊ฒฝ๊ตฌ ํ•ญ์ƒ์ œ, ๋ฐฐ๋†, ํ•ญ์—ผ์ฆ ์น˜๋ฃŒ",
"symptoms_ko": "์ค‘์‹ฌ๋ถ€์— ๋…ธ๋ž€ ๊ณ ๋ฆ„์ด ์žˆ๋Š” ๋ถ‰์€ ๋Œ๊ธฐ"
},
"nodule": {
"name_ko": "๊ฒฐ์ ˆ",
"severity": "medium",
"description_ko": "ํ”ผ๋ถ€ ๊นŠ์ˆ™์ด ํ˜•์„ฑ๋œ ํฌ๊ณ  ๋‹จ๋‹จํ•œ ์—ผ์ฆ์„ฑ ๋ณ‘๋ณ€์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๊ฒฝ๊ตฌ ํ•ญ์ƒ์ œ, ์ด์†ŒํŠธ๋ ˆํ‹ฐ๋…ธ์ธ, ์Šคํ…Œ๋กœ์ด๋“œ ์ฃผ์‚ฌ",
"symptoms_ko": "ํฌ๊ณ  ๋‹จ๋‹จํ•œ ํ”ผํ•˜ ๋ฉ์–ด๋ฆฌ, ์‹ฌํ•œ ํ†ต์ฆ"
},
# Eczema and dermatitis
"eczema": {
"name_ko": "์Šต์ง„",
"severity": "medium",
"description_ko": "๊ฐ€๋ ค์›€์ฆ๊ณผ ์—ผ์ฆ์„ ๋™๋ฐ˜ํ•œ ๋งŒ์„ฑ ํ”ผ๋ถ€ ์งˆํ™˜์œผ๋กœ, ํ”ผ๋ถ€ ์žฅ๋ฒฝ ๊ธฐ๋Šฅ ์ €ํ•˜๊ฐ€ ํŠน์ง•์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ณด์Šต์ œ, ๊ตญ์†Œ ์Šคํ…Œ๋กœ์ด๋“œ, ์นผ์‹œ๋‰ด๋ฆฐ ์–ต์ œ์ œ, ํ•ญํžˆ์Šคํƒ€๋ฏผ์ œ",
"symptoms_ko": "์‹ฌํ•œ ๊ฐ€๋ ค์›€, ๋ถ‰์€ ๋ฐœ์ง„, ํ”ผ๋ถ€ ๊ฑด์กฐ, ์ธ์„ค"
},
"atopic dermatitis": {
"name_ko": "์•„ํ† ํ”ผ ํ”ผ๋ถ€์—ผ",
"severity": "medium",
"description_ko": "์œ ์ „์  ์†Œ์ธ๊ณผ ํ™˜๊ฒฝ ์š”์ธ์ด ๋ณตํ•ฉ์ ์œผ๋กœ ์ž‘์šฉํ•˜๋Š” ๋งŒ์„ฑ ์žฌ๋ฐœ์„ฑ ์—ผ์ฆ์„ฑ ํ”ผ๋ถ€ ์งˆํ™˜์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ณด์Šต์ œ, ๊ตญ์†Œ ์Šคํ…Œ๋กœ์ด๋“œ, ๋ฉด์—ญ์กฐ์ ˆ์ œ, ์ƒ๋ฌผํ•™์  ์ œ์ œ(๋“€ํ”ผ์  ํŠธ)",
"symptoms_ko": "๊ทน์‹ฌํ•œ ๊ฐ€๋ ค์›€, ๊ฑด์กฐ์ฆ, ํƒœ์„ ํ™”, ๋ฐ˜๋ณต์  ์•…ํ™”์™€ ํ˜ธ์ „"
},
"contact dermatitis": {
"name_ko": "์ ‘์ด‰ ํ”ผ๋ถ€์—ผ",
"severity": "medium",
"description_ko": "ํŠน์ • ๋ฌผ์งˆ๊ณผ์˜ ์ ‘์ด‰์œผ๋กœ ์ธํ•œ ์•Œ๋ ˆ๋ฅด๊ธฐ์„ฑ ๋˜๋Š” ์ž๊ทน์„ฑ ํ”ผ๋ถ€ ๋ฐ˜์‘์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "์›์ธ ๋ฌผ์งˆ ํšŒํ”ผ, ๊ตญ์†Œ ์Šคํ…Œ๋กœ์ด๋“œ, ๊ฒฝ๊ตฌ ํ•ญํžˆ์Šคํƒ€๋ฏผ์ œ",
"symptoms_ko": "์ ‘์ด‰ ๋ถ€์œ„์˜ ๋ฐœ์ , ๋ถ€์ข…, ์ˆ˜ํฌ, ๊ฐ€๋ ค์›€"
},
"seborrheic dermatitis": {
"name_ko": "์ง€๋ฃจ์„ฑ ํ”ผ๋ถ€์—ผ",
"severity": "low",
"description_ko": "ํ”ผ์ง€์„ ์ด ๋ฐœ๋‹ฌํ•œ ๋ถ€์œ„์— ๋ฐœ์ƒํ•˜๋Š” ๋งŒ์„ฑ ์—ผ์ฆ์„ฑ ์งˆํ™˜์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "ํ•ญ์ง„๊ท  ์ƒดํ‘ธ(์ผ€ํ† ์ฝ”๋‚˜์กธ), ๊ตญ์†Œ ์Šคํ…Œ๋กœ์ด๋“œ, ์นผ์‹œ๋‰ด๋ฆฐ ์–ต์ œ์ œ",
"symptoms_ko": "๋น„๋“ฌ, ๋‘ํ”ผ ๊ฐ€๋ ค์›€, ์–ผ๊ตด ํ™๋ฐ˜, ๊ธฐ๋ฆ„์ง„ ์ธ์„ค"
},
"perioral dermatitis": {
"name_ko": "๊ตฌ์ฃผ์œ„ ํ”ผ๋ถ€์—ผ",
"severity": "low",
"description_ko": "์ž… ์ฃผ๋ณ€์— ๋ฐœ์ƒํ•˜๋Š” ์—ผ์ฆ์„ฑ ํ”ผ๋ถ€ ์งˆํ™˜์œผ๋กœ, ์ฃผ๋กœ ์ Š์€ ์—ฌ์„ฑ์—๊ฒŒ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.",
"treatment_ko": "๊ตญ์†Œ ํ•ญ์ƒ์ œ(๋ฉ”ํŠธ๋กœ๋‹ˆ๋‹ค์กธ), ๊ฒฝ๊ตฌ ํ…ŒํŠธ๋ผ์‚ฌ์ดํด๋ฆฐ, ์Šคํ…Œ๋กœ์ด๋“œ ์ค‘๋‹จ",
"symptoms_ko": "์ž… ์ฃผ๋ณ€์˜ ์ž‘์€ ๊ตฌ์ง„๊ณผ ๋†ํฌ, ๊ฒฝ๋ฏธํ•œ ํ™๋ฐ˜"
},
# Rosacea and vascular conditions
"rosacea": {
"name_ko": "์ฃผ์‚ฌ๋น„/์žฅ๋ฏธ์ฆ",
"severity": "medium",
"description_ko": "์–ผ๊ตด ์ค‘์•™๋ถ€์˜ ๋งŒ์„ฑ ์—ผ์ฆ์„ฑ ํ”ผ๋ถ€ ์งˆํ™˜์œผ๋กœ, ํ™์กฐ์™€ ํ˜ˆ๊ด€ ํ™•์žฅ์ด ํŠน์ง•์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ฉ”ํŠธ๋กœ๋‹ˆ๋‹ค์กธ ์ ค, ์•„์ ค๋ผ์‚ฐ, ๋…์‹œ์‚ฌ์ดํด๋ฆฐ, IPL ๋ ˆ์ด์ €",
"symptoms_ko": "์•ˆ๋ฉด ํ™์กฐ, ์ง€์†์  ํ™๋ฐ˜, ๊ตฌ์ง„, ๋†ํฌ, ๋ชจ์„ธํ˜ˆ๊ด€ ํ™•์žฅ"
},
"skin redness": {
"name_ko": "ํ”ผ๋ถ€ ํ™๋ฐ˜",
"severity": "low",
"description_ko": "๋‹ค์–‘ํ•œ ์›์ธ์œผ๋กœ ์ธํ•œ ํ”ผ๋ถ€์˜ ๋ถ‰์–ด์ง ํ˜„์ƒ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "์›์ธ ์น˜๋ฃŒ, ์ง„์ • ํฌ๋ฆผ, ํ•ญ์—ผ์ฆ์ œ, ๋ƒ‰์ฐœ์งˆ",
"symptoms_ko": "ํ”ผ๋ถ€์˜ ๋ถ‰์€ ๋ณ€์ƒ‰, ์—ด๊ฐ, ๊ฒฝ๋ฏธํ•œ ๋ถ€์ข…"
},
# Pigmentation disorders
"melasma": {
"name_ko": "๊ธฐ๋ฏธ",
"severity": "low",
"description_ko": "์–ผ๊ตด์— ๋‚˜ํƒ€๋‚˜๋Š” ๋Œ€์นญ์ ์ธ ๊ฐˆ์ƒ‰ ์ƒ‰์†Œ ์นจ์ฐฉ์œผ๋กœ, ์ฃผ๋กœ ์ž„์‹ ์ด๋‚˜ ํ˜ธ๋ฅด๋ชฌ ๋ณ€ํ™”์™€ ๊ด€๋ จ๋ฉ๋‹ˆ๋‹ค.",
"treatment_ko": "ํ•˜์ด๋“œ๋กœํ€ด๋…ผ, ํŠธ๋ ˆํ‹ฐ๋…ธ์ธ, ํ™”ํ•™ ๋ฐ•ํ”ผ, ๋ ˆ์ด์ € ์น˜๋ฃŒ, ์ž์™ธ์„  ์ฐจ๋‹จ",
"symptoms_ko": "๋บจ, ์ด๋งˆ, ์ฝ”, ์ƒ์ˆœ๋ถ€์˜ ๊ฐˆ์ƒ‰ ๋ฐ˜์ "
},
"dark spots": {
"name_ko": "์ƒ‰์†Œ ์นจ์ฐฉ/๊ธฐ๋ฏธ",
"severity": "low",
"description_ko": "๊ณผ๋„ํ•œ ๋ฉœ๋ผ๋‹Œ ์ƒ์„ฑ์œผ๋กœ ์ธํ•œ ํ”ผ๋ถ€์˜ ์–ด๋‘์šด ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ฏธ๋ฐฑ ํฌ๋ฆผ, ๋น„ํƒ€๋ฏผ C, ๋ ˆํ‹ฐ๋…ธ์ด๋“œ, ํ™”ํ•™ ๋ฐ•ํ”ผ, ๋ ˆ์ด์ €",
"symptoms_ko": "๋ถˆ๊ทœ์น™ํ•œ ๊ฐˆ์ƒ‰ ๋˜๋Š” ๊ฒ€์€ ๋ฐ˜์ "
},
"freckles": {
"name_ko": "์ฃผ๊ทผ๊นจ",
"severity": "none",
"description_ko": "์œ ์ „์  ์†Œ์ธ๊ณผ ์ž์™ธ์„  ๋…ธ์ถœ๋กœ ์ธํ•œ ์ž‘์€ ๊ฐˆ์ƒ‰ ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "์ž์™ธ์„  ์ฐจ๋‹จ, ๋ ˆ์ด์ € ์น˜๋ฃŒ, ๋ฏธ๋ฐฑ ํฌ๋ฆผ (์„ ํƒ์ )",
"symptoms_ko": "์–ผ๊ตด๊ณผ ํŒ”์˜ ์ž‘์€ ๊ฐˆ์ƒ‰ ์ "
},
"vitiligo": {
"name_ko": "๋ฐฑ๋ฐ˜์ฆ",
"severity": "low",
"description_ko": "๋ฉœ๋ผ๋‹Œ ์„ธํฌ ์†Œ์‹ค๋กœ ์ธํ•œ ํ”ผ๋ถ€ ํƒˆ์ƒ‰์†Œ ์งˆํ™˜์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๊ตญ์†Œ ์Šคํ…Œ๋กœ์ด๋“œ, ์นผ์‹œ๋‰ด๋ฆฐ ์–ต์ œ์ œ, ๊ด‘์„ ์น˜๋ฃŒ, ์ƒ‰์†Œ์ด์‹",
"symptoms_ko": "๊ฒฝ๊ณ„๊ฐ€ ๋ช…ํ™•ํ•œ ํฐ์ƒ‰ ๋ฐ˜์ , ๋Œ€์นญ์  ๋ถ„ํฌ"
},
# Infections
"cellulitis": {
"name_ko": "๋ด‰์™€์ง์—ผ",
"severity": "high",
"description_ko": "ํ”ผ๋ถ€์™€ ํ”ผํ•˜์กฐ์ง์˜ ๊ธ‰์„ฑ ์„ธ๊ท  ๊ฐ์—ผ์œผ๋กœ, ์ฃผ๋กœ ์—ฐ์‡„์ƒ๊ตฌ๊ท ์ด๋‚˜ ํฌ๋„์ƒ๊ตฌ๊ท ์ด ์›์ธ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๊ฒฝ๊ตฌ ๋˜๋Š” ์ •๋งฅ ํ•ญ์ƒ์ œ, ํœด์‹, ๊ฑฐ์ƒ, ์ง„ํ†ต์ œ, ์ž…์› ์น˜๋ฃŒ",
"symptoms_ko": "๋ฐœ์ , ๋ถ€์ข…, ์—ด๊ฐ, ํ†ต์ฆ, ๋ฐœ์—ด, ์˜คํ•œ"
},
"impetigo": {
"name_ko": "๋†๊ฐ€์ง„",
"severity": "medium",
"description_ko": "ํ‘œ์žฌ์„ฑ ์„ธ๊ท  ๊ฐ์—ผ์œผ๋กœ, ์ฃผ๋กœ ์–ด๋ฆฐ์ด์—๊ฒŒ ๋ฐœ์ƒํ•˜๋ฉฐ ์ „์—ผ์„ฑ์ด ๊ฐ•ํ•ฉ๋‹ˆ๋‹ค.",
"treatment_ko": "๊ตญ์†Œ ํ•ญ์ƒ์ œ(๋ฌดํ”ผ๋กœ์‹ ), ๊ฒฝ๊ตฌ ํ•ญ์ƒ์ œ, ์œ„์ƒ ๊ด€๋ฆฌ",
"symptoms_ko": "๊ฟ€์ƒ‰ ๊ฐ€ํ”ผ, ์ˆ˜ํฌ, ๊ฐ€๋ ค์›€, ์ฃผ๋ณ€ ํ”ผ๋ถ€๋กœ ํ™•์‚ฐ"
},
"fungal": {
"name_ko": "์ง„๊ท  ๊ฐ์—ผ/๋ฌด์ข€",
"severity": "low",
"description_ko": "ํ”ผ๋ถ€ ์ง„๊ท ์— ์˜ํ•œ ๊ฐ์—ผ์œผ๋กœ, ๋ฐฑ์„ , ์นธ๋””๋‹ค์ฆ ๋“ฑ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.",
"treatment_ko": "ํ•ญ์ง„๊ท ์ œ(๊ตญ์†Œ ๋˜๋Š” ๊ฒฝ๊ตฌ), ์œ„์ƒ ๊ด€๋ฆฌ, ํ†ตํ’ ๊ฐœ์„ ",
"symptoms_ko": "์ธ์„ค, ๊ฐ€๋ ค์›€, ๋ฐœ์ , ๊ท ์—ด, ์•…์ทจ"
},
"herpes": {
"name_ko": "ํ—ค๋ฅดํŽ˜์Šค",
"severity": "medium",
"description_ko": "ํ—ค๋ฅดํŽ˜์Šค ๋ฐ”์ด๋Ÿฌ์Šค์— ์˜ํ•œ ์ˆ˜ํฌ์„ฑ ํ”ผ๋ถ€ ๊ฐ์—ผ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "ํ•ญ๋ฐ”์ด๋Ÿฌ์Šค์ œ(์•„์‹œํด๋กœ๋ฒ„, ๋ฐœ๋ผ์‹œํด๋กœ๋ฒ„), ์ง„ํ†ต์ œ",
"symptoms_ko": "๊ตฐ์ง‘์„ฑ ์ˆ˜ํฌ, ํ†ต์ฆ, ์ž‘์—ด๊ฐ, ์žฌ๋ฐœ์„ฑ"
},
# Keratosis and scaling conditions
"keratosis": {
"name_ko": "๊ฐํ™”์ฆ",
"severity": "low",
"description_ko": "ํ”ผ๋ถ€์˜ ๊ฐ์งˆ์ธต์ด ๊ณผ๋„ํ•˜๊ฒŒ ๋‘๊บผ์›Œ์ง€๋Š” ์ƒํƒœ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๊ฐ์งˆ์šฉํ•ด์ œ(์‚ด๋ฆฌ์‹ค์‚ฐ, ์š”์†Œ), ๋ ˆํ‹ฐ๋…ธ์ด๋“œ, ๋ณด์Šต์ œ",
"symptoms_ko": "๊ฑฐ์นœ ํ”ผ๋ถ€, ์ธ์„ค, ๋‘๊บผ์›Œ์ง„ ๊ฐ์งˆ"
},
"actinic keratosis": {
"name_ko": "๊ด‘์„  ๊ฐํ™”์ฆ",
"severity": "medium",
"description_ko": "๋งŒ์„ฑ ์ž์™ธ์„  ๋…ธ์ถœ๋กœ ์ธํ•œ ์ „์•”์„ฑ ๋ณ‘๋ณ€์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ƒ‰๋™์น˜๋ฃŒ, 5-FU ํฌ๋ฆผ, ์ด๋ฏธํ€ด๋ชจ๋“œ, ๊ด‘์—ญํ•™ ์น˜๋ฃŒ",
"symptoms_ko": "๊ฑฐ์นœ ์ธ์„ค์„ฑ ๋ฐ˜์ , ์ฃผ๋กœ ๋…ธ์ถœ ๋ถ€์œ„"
},
"psoriasis": {
"name_ko": "๊ฑด์„ ",
"severity": "medium",
"description_ko": "ํ”ผ๋ถ€์„ธํฌ์˜ ๊ณผ๋„ํ•œ ์ฆ์‹์œผ๋กœ ์ธํ•œ ๋งŒ์„ฑ ์ž๊ฐ€๋ฉด์—ญ ์งˆํ™˜์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๊ตญ์†Œ ์Šคํ…Œ๋กœ์ด๋“œ, ๋น„ํƒ€๋ฏผ D ์œ ๋„์ฒด, ๊ด‘์„ ์น˜๋ฃŒ, ์ƒ๋ฌผํ•™์  ์ œ์ œ",
"symptoms_ko": "์€๋ฐฑ์ƒ‰ ์ธ์„ค, ํ™๋ฐ˜์„ฑ ํŒ, ๊ฐ€๋ ค์›€, ๊ด€์ ˆํ†ต"
},
# Skin cancers
"melanoma": {
"name_ko": "ํ‘์ƒ‰์ข…",
"severity": "high",
"description_ko": "๋ฉœ๋ผ๋‹Œ ์„ธํฌ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์•…์„ฑ ํ”ผ๋ถ€์•”์œผ๋กœ, ์ „์ด ์œ„ํ—˜์ด ๋†’์Šต๋‹ˆ๋‹ค.",
"treatment_ko": "์ˆ˜์ˆ ์  ์ ˆ์ œ, ๋ฉด์—ญ์น˜๋ฃŒ, ํ‘œ์ ์น˜๋ฃŒ, ๋ฐฉ์‚ฌ์„ ์น˜๋ฃŒ",
"symptoms_ko": "๋น„๋Œ€์นญ ๋ชจ์–‘, ๋ถˆ๊ทœ์น™ํ•œ ๊ฒฝ๊ณ„, ๋‹ค์–‘ํ•œ ์ƒ‰์กฐ, 6mm ์ด์ƒ ํฌ๊ธฐ"
},
"basal cell carcinoma": {
"name_ko": "๊ธฐ์ €์„ธํฌ์•”",
"severity": "high",
"description_ko": "๊ฐ€์žฅ ํ”ํ•œ ํ”ผ๋ถ€์•”์œผ๋กœ, ์ฒœ์ฒœํžˆ ์„ฑ์žฅํ•˜๋ฉฐ ๊ตญ์†Œ ์นจ์Šต์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.",
"treatment_ko": "๋ชจ์Šค ์ˆ˜์ˆ , ์ ˆ์ œ์ˆ , ๋ฐฉ์‚ฌ์„ ์น˜๋ฃŒ, ์ด๋ฏธํ€ด๋ชจ๋“œ",
"symptoms_ko": "์ง„์ฃผ ๊ด‘ํƒ ๊ฒฐ์ ˆ, ์ค‘์‹ฌ๋ถ€ ๊ถค์–‘, ์ถœํ˜ˆ, ๊ฐ€ํ”ผ"
},
"squamous cell carcinoma": {
"name_ko": "ํŽธํ‰์„ธํฌ์•”",
"severity": "high",
"description_ko": "๋‘ ๋ฒˆ์งธ๋กœ ํ”ํ•œ ํ”ผ๋ถ€์•”์œผ๋กœ, ์ „์ด ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.",
"treatment_ko": "์ˆ˜์ˆ ์  ์ ˆ์ œ, ๋ฐฉ์‚ฌ์„ ์น˜๋ฃŒ, ํ™”ํ•™์š”๋ฒ•",
"symptoms_ko": "์ธ์„ค์„ฑ ํ™๋ฐ˜, ๊ถค์–‘, ์ถœํ˜ˆ, ๋‹จ๋‹จํ•œ ๊ฒฐ์ ˆ"
},
"carcinoma": {
"name_ko": "ํ”ผ๋ถ€์•”",
"severity": "high",
"description_ko": "ํ”ผ๋ถ€์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์•…์„ฑ ์ข…์–‘์„ ์ด์นญํ•ฉ๋‹ˆ๋‹ค.",
"treatment_ko": "์ข…์–‘ ์œ ํ˜•์— ๋”ฐ๋ฅธ ์ˆ˜์ˆ , ๋ฐฉ์‚ฌ์„ , ํ™”ํ•™์š”๋ฒ•",
"symptoms_ko": "๋น„์ •์ƒ์ ์ธ ์„ฑ์žฅ, ๊ถค์–‘, ์ถœํ˜ˆ, ์ƒ‰์กฐ ๋ณ€ํ™”"
},
# Other common conditions
"milia": {
"name_ko": "๋น„๋ฆฝ์ข…/์Œ€์•Œ์ข…",
"severity": "low",
"description_ko": "๊ฐ์งˆ์ด ํ”ผ๋ถ€ ํ‘œ๋ฉด ์•„๋ž˜ ๊ฐ‡ํ˜€ ์ƒ๊ธฐ๋Š” ์ž‘์€ ํฐ์ƒ‰ ๋‚ญ์ข…์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "์ž์—ฐ ์†Œ์‹ค, ์••์ถœ, ๋ ˆํ‹ฐ๋…ธ์ด๋“œ, ํ™”ํ•™ ๋ฐ•ํ”ผ",
"symptoms_ko": "1-2mm ํฌ๊ธฐ์˜ ํฐ์ƒ‰ ๋˜๋Š” ๋…ธ๋ž€์ƒ‰ ๊ตฌ์ง„"
},
"warts": {
"name_ko": "์‚ฌ๋งˆ๊ท€",
"severity": "low",
"description_ko": "์ธ์œ ๋‘์ข… ๋ฐ”์ด๋Ÿฌ์Šค(HPV)์— ์˜ํ•œ ํ”ผ๋ถ€ ์ฆ์‹์„ฑ ์งˆํ™˜์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ƒ‰๋™์น˜๋ฃŒ, ์‚ด๋ฆฌ์‹ค์‚ฐ, ๋ ˆ์ด์ €, ๋ฉด์—ญ์น˜๋ฃŒ",
"symptoms_ko": "๊ฑฐ์นœ ํ‘œ๋ฉด์˜ ๋Œ๊ธฐ, ๊ฒ€์€ ์ , ๊ตฐ์ง‘์„ฑ ๋ณ‘๋ณ€"
},
"urticaria": {
"name_ko": "๋‘๋“œ๋Ÿฌ๊ธฐ",
"severity": "low",
"description_ko": "ํ”ผ๋ถ€์˜ ์ผ์‹œ์ ์ธ ๋ถ€์ข…๊ณผ ๊ฐ€๋ ค์›€์„ ๋™๋ฐ˜ํ•œ ์•Œ๋ ˆ๋ฅด๊ธฐ ๋ฐ˜์‘์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "ํ•ญํžˆ์Šคํƒ€๋ฏผ์ œ, ์•Œ๋ ˆ๋ฅด๊ธฐ ์›์ธ ํšŒํ”ผ, ์Šคํ…Œ๋กœ์ด๋“œ",
"symptoms_ko": "ํŒฝ์ง„, ์‹ฌํ•œ ๊ฐ€๋ ค์›€, ํ˜ˆ๊ด€๋ถ€์ข…"
},
"hives": {
"name_ko": "๋‘๋“œ๋Ÿฌ๊ธฐ",
"severity": "low",
"description_ko": "๊ธ‰์„ฑ ์•Œ๋ ˆ๋ฅด๊ธฐ ๋ฐ˜์‘์œผ๋กœ ์ธํ•œ ํ”ผ๋ถ€ ํŒฝ์ง„์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "ํ•ญํžˆ์Šคํƒ€๋ฏผ์ œ, ์—ํ”ผ๋„คํ”„๋ฆฐ(์‹ฌํ•œ ๊ฒฝ์šฐ)",
"symptoms_ko": "๋ถ‰๊ณ  ๋ถ€ํ‘ผ ํŒฝ์ง„, ๊ฐ€๋ ค์›€, ์ด๋™์„ฑ ๋ณ‘๋ณ€"
},
# Aging and cosmetic concerns
"wrinkles": {
"name_ko": "์ฃผ๋ฆ„",
"severity": "none",
"description_ko": "๋…ธํ™”์™€ ์ž์™ธ์„  ๋…ธ์ถœ๋กœ ์ธํ•œ ํ”ผ๋ถ€ ํƒ„๋ ฅ ๊ฐ์†Œ์™€ ์ฃผ๋ฆ„ ํ˜•์„ฑ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ ˆํ‹ฐ๋…ธ์ด๋“œ, ๋ณดํ†ก์Šค, ํ•„๋Ÿฌ, ๋ ˆ์ด์ €, ์ž์™ธ์„  ์ฐจ๋‹จ",
"symptoms_ko": "๋ฏธ์„ธ์ฃผ๋ฆ„, ๊นŠ์€ ์ฃผ๋ฆ„, ํƒ„๋ ฅ ๊ฐ์†Œ"
},
"dark circles": {
"name_ko": "๋‹คํฌ์„œํด",
"severity": "none",
"description_ko": "๋ˆˆ ์•„๋ž˜ ํ”ผ๋ถ€๊ฐ€ ์–ด๋‘ก๊ฒŒ ๋ณด์ด๋Š” ํ˜„์ƒ์œผ๋กœ, ํ”ผ๋กœ, ์œ ์ „, ๋…ธํ™” ๋“ฑ์ด ์›์ธ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "์ถฉ๋ถ„ํ•œ ์ˆ˜๋ฉด, ๋น„ํƒ€๋ฏผ K ํฌ๋ฆผ, ๋ ˆํ‹ฐ๋…ธ์ด๋“œ, ํ•„๋Ÿฌ",
"symptoms_ko": "๋ˆˆ ์•„๋ž˜ ์–ด๋‘์šด ๋ณ€์ƒ‰, ๋ถ€์ข…"
},
"eye bags": {
"name_ko": "๋ˆˆ๋ฐ‘ ์ง€๋ฐฉ",
"severity": "none",
"description_ko": "๋ˆˆ ์•„๋ž˜ ์ง€๋ฐฉ์ด ๋Œ์ถœ๋˜์–ด ์ƒ๊ธฐ๋Š” ๋ถ€์ข…์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ƒ‰์ฐœ์งˆ, ์นดํŽ˜์ธ ํฌ๋ฆผ, ์ˆ˜์ˆ (์‹ฌํ•œ ๊ฒฝ์šฐ)",
"symptoms_ko": "๋ˆˆ ์•„๋ž˜ ๋ถ€ํ’€์–ด ์˜ค๋ฆ„, ์ฒ˜์ง"
},
"enlarged pores": {
"name_ko": "๋ชจ๊ณต ํ™•๋Œ€",
"severity": "none",
"description_ko": "ํ”ผ์ง€ ๊ณผ๋‹ค ๋ถ„๋น„์™€ ํƒ„๋ ฅ ๊ฐ์†Œ๋กœ ์ธํ•œ ๋ชจ๊ณต ํ™•๋Œ€์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ ˆํ‹ฐ๋…ธ์ด๋“œ, BHA, ๋ ˆ์ด์ €, ๋ชจ๊ณต ์ถ•์†Œ ์‹œ์ˆ ",
"symptoms_ko": "๋ˆˆ์— ๋„๋Š” ๋ชจ๊ณต, ํ”ผ์ง€ ๊ณผ๋‹ค"
},
# Skin types
"normal": {
"name_ko": "์ •์ƒ ํ”ผ๋ถ€",
"severity": "none",
"description_ko": "๊ท ํ˜•์žกํžŒ ๊ฑด๊ฐ•ํ•œ ํ”ผ๋ถ€ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๊ธฐ๋ณธ ์Šคํ‚จ์ผ€์–ด ์œ ์ง€, ์ž์™ธ์„  ์ฐจ๋‹จ, ๋ณด์Šต",
"symptoms_ko": "ํŠน๋ณ„ํ•œ ๋ฌธ์ œ ์—†์Œ, ์ ์ ˆํ•œ ์œ ์ˆ˜๋ถ„ ๋ฐธ๋Ÿฐ์Šค"
},
"normal skin": {
"name_ko": "์ •์ƒ ํ”ผ๋ถ€",
"severity": "none",
"description_ko": "๊ฑด๊ฐ•ํ•˜๊ณ  ๊ท ํ˜•์žกํžŒ ํ”ผ๋ถ€ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "์ผ๋ฐ˜์ ์ธ ์Šคํ‚จ์ผ€์–ด ๋ฃจํ‹ด ์œ ์ง€",
"symptoms_ko": "๋งค๋„๋Ÿฌ์šด ์งˆ๊ฐ, ๊ท ์ผํ•œ ํ†ค"
},
"dry skin": {
"name_ko": "๊ฑด์„ฑ ํ”ผ๋ถ€",
"severity": "low",
"description_ko": "์ˆ˜๋ถ„๊ณผ ํ”ผ์ง€๊ฐ€ ๋ถ€์กฑํ•œ ํ”ผ๋ถ€ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ณด์Šต์ œ, ์„ธ๋ผ๋งˆ์ด๋“œ, ํžˆ์•Œ๋ฃจ๋ก ์‚ฐ, ๋ถ€๋“œ๋Ÿฌ์šด ํด๋ Œ์ €",
"symptoms_ko": "๋‹น๊น€, ๊ฐ์งˆ, ์น™์น™ํ•จ, ๋ฏธ์„ธ์ฃผ๋ฆ„"
},
"oily skin": {
"name_ko": "์ง€์„ฑ ํ”ผ๋ถ€",
"severity": "low",
"description_ko": "ํ”ผ์ง€ ๋ถ„๋น„๊ฐ€ ๊ณผ๋„ํ•œ ํ”ผ๋ถ€ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "์˜ค์ผํ”„๋ฆฌ ์ œํ’ˆ, BHA, ํด๋ ˆ์ด ๋งˆ์Šคํฌ, ๊ฐ€๋ฒผ์šด ๋ณด์Šต",
"symptoms_ko": "๋ฒˆ๋“ค๊ฑฐ๋ฆผ, ๋„“์€ ๋ชจ๊ณต, ์—ฌ๋“œ๋ฆ„ ๋ฐœ์ƒ"
},
"combination skin": {
"name_ko": "๋ณตํ•ฉ์„ฑ ํ”ผ๋ถ€",
"severity": "none",
"description_ko": "T์กด์€ ์ง€์„ฑ, U์กด์€ ๊ฑด์„ฑ์ธ ๋ณตํ•ฉ์  ํ”ผ๋ถ€ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "๋ถ€์œ„๋ณ„ ๋งž์ถค ์ผ€์–ด, ๋ฐธ๋Ÿฐ์‹ฑ ์ œํ’ˆ",
"symptoms_ko": "T์กด ๋ฒˆ๋“ค๊ฑฐ๋ฆผ, ๋ณผ ๊ฑด์กฐ"
},
"sensitive skin": {
"name_ko": "๋ฏผ๊ฐ์„ฑ ํ”ผ๋ถ€",
"severity": "low",
"description_ko": "์™ธ๋ถ€ ์ž๊ทน์— ์‰ฝ๊ฒŒ ๋ฐ˜์‘ํ•˜๋Š” ํ”ผ๋ถ€ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.",
"treatment_ko": "์ €์ž๊ทน ์ œํ’ˆ, ๋ฌดํ–ฅ๋ฃŒ, ์ง„์ • ์„ฑ๋ถ„",
"symptoms_ko": "์‰ฌ์šด ์ž๊ทน, ๋ถ‰์–ด์ง, ๋”ฐ๊ฐ€์›€"
}
}
return database
def _init_gpt_client(self):
"""Initialize GPT-OSS client for detailed explanations."""
try:
# This would connect to the GPT-OSS API
# For now, we'll use a placeholder
self.gpt_client = None
logger.info("GPT-OSS client initialized (placeholder)")
except Exception as e:
logger.error(f"Failed to initialize GPT client: {e}")
def load_model(self) -> None:
"""Load the vision model for skin disease classification."""
try:
logger.info(f"Loading model: {self.model_name}")
self.processor = ViTImageProcessor.from_pretrained(self.model_name)
self.model = ViTForImageClassification.from_pretrained(self.model_name)
self.model.eval()
self.id2label = self.model.config.id2label
logger.info(f"Model loaded: {len(self.id2label)} disease classes")
except Exception as e:
logger.error(f"Failed to load model: {e}")
raise RuntimeError(f"Model loading failed: {str(e)}")
@spaces.GPU
def diagnose(self, image: Image.Image) -> Tuple[Dict[str, float], str, str]:
"""
Perform diagnosis on the skin image.
Returns:
Tuple of (probabilities, formatted_output, gpt_analysis)
"""
if self.model is None or self.processor is None:
return {}, "โŒ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ / Model not loaded", ""
try:
# Image preprocessing
if image.mode != 'RGB':
image = image.convert('RGB')
# Move model to GPU
device = "cuda" if torch.cuda.is_available() else "cpu"
self.model = self.model.to(device)
# Process image
inputs = self.processor(images=image, return_tensors="pt")
inputs = {k: v.to(device) for k, v in inputs.items()}
# Get predictions
with torch.no_grad():
outputs = self.model(**inputs)
probabilities = torch.softmax(outputs.logits, dim=-1)[0]
probabilities = probabilities.cpu()
# Convert to dictionary
class_probs = {}
for class_id, prob in enumerate(probabilities):
class_name = self.id2label[class_id]
class_probs[class_name] = float(prob)
# Sort by confidence
sorted_probs = dict(sorted(class_probs.items(),
key=lambda x: x[1], reverse=True))
# Format results with medical information
formatted_output = self._format_medical_results(sorted_probs, image.size)
# Generate GPT analysis
gpt_analysis = self._generate_gpt_analysis(sorted_probs)
return sorted_probs, formatted_output, gpt_analysis
except Exception as e:
error_msg = f"โŒ ์ง„๋‹จ ์‹คํŒจ / Diagnosis failed: {str(e)}"
logger.error(error_msg)
return {}, error_msg, ""
def _format_medical_results(self, class_probs: Dict[str, float],
image_size: Tuple[int, int]) -> str:
"""Format results in medical report style with Korean."""
top_class = next(iter(class_probs))
top_confidence = class_probs[top_class]
# Get disease info if available
disease_info = self.disease_info_db.get(top_class.lower(), {})
name_ko = disease_info.get("name_ko", top_class)
severity = disease_info.get("severity", "unknown")
# Determine severity badge
severity_badge = {
"low": '<span class="diagnosis-severity severity-low">๊ฒฝ์ฆ / Mild</span>',
"medium": '<span class="diagnosis-severity severity-medium">์ค‘๋“ฑ๋„ / Moderate</span>',
"high": '<span class="diagnosis-severity severity-high">์ค‘์ฆ / Severe</span>',
"unknown": '<span class="diagnosis-severity">๋ฏธ๋ถ„๋ฅ˜ / Unclassified</span>'
}.get(severity, "")
# Build medical report
output = f"""
<div class="diagnosis-card">
<h3>๐Ÿฅ AI ํ”ผ๋ถ€ ์ง„๋‹จ ๊ฒฐ๊ณผ / AI Skin Diagnosis Report</h3>
<div class="result-container">
<h4>๐Ÿ“‹ ์ฃผ์š” ์ง„๋‹จ / Primary Diagnosis</h4>
<p><strong>์งˆํ™˜๋ช… / Condition:</strong> {top_class.title()} ({name_ko}) {severity_badge}</p>
<p><strong>์‹ ๋ขฐ๋„ / Confidence:</strong> {top_confidence:.1%}</p>
<div class="confidence-bar" style="width: {top_confidence*100}%"></div>
</div>
<div class="result-container">
<h4>๐Ÿ“Š ์ƒ์„ธ ๋ถ„์„ / Detailed Analysis</h4>
<p><strong>์ด๋ฏธ์ง€ ํฌ๊ธฐ / Image Size:</strong> {image_size[0]} ร— {image_size[1]} pixels</p>
<p><strong>๋ถ„์„ ์‹œ๊ฐ„ / Analysis Time:</strong> <span class="status-indicator status-ready"></span>์‹ค์‹œ๊ฐ„ / Real-time</p>
</div>
<div class="result-container">
<h4>๐Ÿ” ๊ฐ๋ณ„ ์ง„๋‹จ / Differential Diagnosis (Top 5)</h4>
"""
# Add top 5 differential diagnoses
for idx, (class_name, confidence) in enumerate(class_probs.items()):
if idx >= 5:
break
disease_ko = self.disease_info_db.get(class_name.lower(), {}).get("name_ko", class_name)
if confidence >= 0.01: # Only show > 1%
bar_length = int(confidence * 20)
bar = "โ–ˆ" * bar_length + "โ–‘" * (20 - bar_length)
output += f"""
<p>โ€ข <strong>{idx+1}. {class_name.title()} ({disease_ko}):</strong> {confidence:.1%}
<code>{bar}</code></p>
"""
output += """
</div>
<div class="warning-box">
<strong>โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ / Important Notice:</strong><br>
์ด ๊ฒฐ๊ณผ๋Š” AI ๊ธฐ๋ฐ˜ ์˜ˆ์ธก์ด๋ฉฐ, ์‹ค์ œ ์˜ํ•™์  ์ง„๋‹จ์„ ๋Œ€์ฒดํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.<br>
์ •ํ™•ํ•œ ์ง„๋‹จ๊ณผ ์น˜๋ฃŒ๋ฅผ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ ํ”ผ๋ถ€๊ณผ ์ „๋ฌธ์˜์™€ ์ƒ๋‹ดํ•˜์„ธ์š”.<br>
<em>This is an AI-based prediction and cannot replace actual medical diagnosis.<br>
Please consult a dermatologist for accurate diagnosis and treatment.</em>
</div>
</div>
"""
return output
def _generate_gpt_analysis(self, class_probs: Dict[str, float]) -> str:
"""Generate detailed analysis using GPT-OSS model."""
top_class = next(iter(class_probs))
top_confidence = class_probs[top_class]
# Get disease information from database - check multiple variations
disease_info = None
# Try different case variations to find the disease
search_keys = [
top_class.lower(),
top_class.lower().replace('_', ' '),
top_class.lower().replace('-', ' '),
top_class.replace('_', ' ').lower(),
top_class.replace('-', ' ').lower()
]
for key in search_keys:
if key in self.disease_info_db:
disease_info = self.disease_info_db[key]
logger.info(f"Found disease info for: {key}")
break
# If still not found, create comprehensive default information
if disease_info is None:
logger.warning(f"No database entry for: {top_class}")
disease_info = self._create_default_disease_info(top_class)
# Get detailed symptoms if available
symptoms_text = disease_info.get('symptoms_ko', '')
if symptoms_text:
symptoms_html = f"""
<div class="result-container">
<h4>๐Ÿ” ์ฃผ์š” ์ฆ์ƒ / Key Symptoms</h4>
<p>{symptoms_text}</p>
</div>
"""
else:
symptoms_html = ""
# Create comprehensive GPT analysis with all available information
analysis = f"""
<div class="diagnosis-card">
<h3>๐Ÿค– GPT-OSS ์ƒ์„ธ ๋ถ„์„ / GPT-OSS Detailed Analysis</h3>
<div class="info-box">
<h4>๐Ÿ“‹ ์ง„๋‹จ๋œ ์งˆํ™˜ ์ •๋ณด / Diagnosed Condition Information</h4>
<p><strong>์งˆํ™˜๋ช… / Disease:</strong> {top_class.title()} ({disease_info['name_ko']})</p>
<p><strong>์‹ ๋ขฐ๋„ / Confidence:</strong> {top_confidence:.1%}</p>
<p><strong>์ค‘์ฆ๋„ / Severity:</strong> {self._get_severity_badge(disease_info.get('severity', 'unknown'))}</p>
</div>
<div class="result-container">
<h4>๐Ÿ“– ์งˆํ™˜ ์„ค๋ช… / Disease Description</h4>
<p><strong>{disease_info['name_ko']} ({top_class.title()})</strong></p>
<p>{disease_info['description_ko']}</p>
</div>
{symptoms_html}
<div class="result-container">
<h4>๐Ÿ“ ๊ถŒ์žฅ ์น˜๋ฃŒ๋ฒ• / Recommended Treatment</h4>
<p><strong>์น˜๋ฃŒ ๋ฐฉ๋ฒ•:</strong></p>
<p>{disease_info['treatment_ko']}</p>
</div>
<div class="result-container">
<h4>๐Ÿ’Š ์ƒํ™œ ๊ด€๋ฆฌ ์ง€์นจ / Lifestyle Management Guidelines</h4>
{self._get_lifestyle_guidelines(top_class, disease_info.get('severity', 'low'))}
</div>
<div class="result-container">
<h4>๐Ÿฅ ์˜๋ฃŒ์ง„ ์ƒ๋‹ด ํ•„์š” ์‹œ์  / When to Consult Healthcare Provider</h4>
{self._get_consultation_guidelines(disease_info.get('severity', 'low'))}
</div>
<div class="result-container">
<h4>๐Ÿ”ฌ ์ถ”๊ฐ€ ๊ฒ€์‚ฌ ๊ถŒ์žฅ์‚ฌํ•ญ / Recommended Additional Tests</h4>
{self._get_test_recommendations(top_class, disease_info.get('severity', 'low'))}
</div>
<div class="result-container">
<h4>โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ / Precautions</h4>
{self._get_precautions(top_class, disease_info.get('severity', 'low'))}
</div>
</div>
"""
return analysis
def _create_default_disease_info(self, disease_name: str) -> Dict:
"""Create default disease information when not in database."""
# Try to infer information from the disease name
name_lower = disease_name.lower()
# Determine Korean name
name_ko = self._translate_disease_name(disease_name)
# Determine severity
severity = self._estimate_severity(disease_name)
# Create appropriate description based on keywords
if 'cancer' in name_lower or 'carcinoma' in name_lower or 'melanoma' in name_lower:
description = f"์•…์„ฑ ์ข…์–‘์˜ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ํ”ผ๋ถ€ ๋ณ‘๋ณ€์ž…๋‹ˆ๋‹ค. ์ฆ‰์‹œ ์ „๋ฌธ์˜ ์ง„๋‹จ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."
treatment = "์ฆ‰์‹œ ํ”ผ๋ถ€๊ณผ ๋˜๋Š” ์ข…์–‘ ์ „๋ฌธ์˜ ์ƒ๋‹ด, ์กฐ์ง๊ฒ€์‚ฌ ํ•„์š”, ์กฐ๊ธฐ ์ง„๋‹จ๊ณผ ์น˜๋ฃŒ๊ฐ€ ์ค‘์š”"
elif 'infection' in name_lower or 'bacterial' in name_lower:
description = f"์„ธ๊ท  ๊ฐ์—ผ์ด ์˜์‹ฌ๋˜๋Š” ํ”ผ๋ถ€ ์งˆํ™˜์ž…๋‹ˆ๋‹ค. ์ ์ ˆํ•œ ํ•ญ์ƒ์ œ ์น˜๋ฃŒ๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
treatment = "ํ•ญ์ƒ์ œ ์น˜๋ฃŒ(๊ตญ์†Œ ๋˜๋Š” ๊ฒฝ๊ตฌ), ์ƒ์ฒ˜ ์†Œ๋…, ์œ„์ƒ ๊ด€๋ฆฌ"
elif 'fungal' in name_lower:
description = f"์ง„๊ท  ๊ฐ์—ผ์ด ์˜์‹ฌ๋˜๋Š” ํ”ผ๋ถ€ ์งˆํ™˜์ž…๋‹ˆ๋‹ค."
treatment = "ํ•ญ์ง„๊ท ์ œ ์น˜๋ฃŒ, ํ™˜๋ถ€ ๊ฑด์กฐ ์œ ์ง€, ํ†ตํ’ ๊ฐœ์„ "
elif 'dermatitis' in name_lower or 'eczema' in name_lower:
description = f"ํ”ผ๋ถ€์˜ ์—ผ์ฆ์„ฑ ์งˆํ™˜์œผ๋กœ, ๊ฐ€๋ ค์›€๊ณผ ๋ฐœ์ ์ด ๋™๋ฐ˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
treatment = "๋ณด์Šต์ œ ์‚ฌ์šฉ, ๊ตญ์†Œ ์Šคํ…Œ๋กœ์ด๋“œ, ํ•ญํžˆ์Šคํƒ€๋ฏผ์ œ, ์ž๊ทน ๋ฌผ์งˆ ํšŒํ”ผ"
elif 'acne' in name_lower or 'pimple' in name_lower:
description = f"๋ชจ๋‚ญ๊ณผ ํ”ผ์ง€์„ ์˜ ์—ผ์ฆ์„ฑ ์งˆํ™˜์ž…๋‹ˆ๋‹ค."
treatment = "์ ์ ˆํ•œ ํด๋ Œ์ง•, ๊ตญ์†Œ ์น˜๋ฃŒ์ œ(๋ ˆํ‹ฐ๋…ธ์ด๋“œ, ๋ฒค์กฐ์ผ ํผ์˜ฅ์‚ฌ์ด๋“œ), ํ•ญ์ƒ์ œ"
else:
description = f"ํ”ผ๋ถ€ ์งˆํ™˜์œผ๋กœ ์ •ํ™•ํ•œ ์ง„๋‹จ์„ ์œ„ํ•ด ์ „๋ฌธ์˜ ์ƒ๋‹ด์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค."
treatment = "ํ”ผ๋ถ€๊ณผ ์ „๋ฌธ์˜ ์ƒ๋‹ด์„ ํ†ตํ•œ ์ •ํ™•ํ•œ ์ง„๋‹จ ํ›„ ์ ์ ˆํ•œ ์น˜๋ฃŒ"
return {
"name_ko": name_ko,
"severity": severity,
"description_ko": description,
"treatment_ko": treatment,
"symptoms_ko": "์œก์•ˆ ๊ฒ€์‚ฌ์™€ ์ถ”๊ฐ€ ๊ฒ€์‚ฌ๋ฅผ ํ†ตํ•ด ์ •ํ™•ํ•œ ์ฆ์ƒ ํŒŒ์•… ํ•„์š”"
}
def _get_severity_badge(self, severity: str) -> str:
"""Get HTML badge for severity level."""
badges = {
"none": '<span class="diagnosis-severity">์ •์ƒ / Normal</span>',
"low": '<span class="diagnosis-severity severity-low">๊ฒฝ์ฆ / Mild</span>',
"medium": '<span class="diagnosis-severity severity-medium">์ค‘๋“ฑ๋„ / Moderate</span>',
"high": '<span class="diagnosis-severity severity-high">์ค‘์ฆ / Severe</span>',
"unknown": '<span class="diagnosis-severity">๋ฏธ๋ถ„๋ฅ˜ / Unclassified</span>'
}
return badges.get(severity, badges["unknown"])
def _get_lifestyle_guidelines(self, disease: str, severity: str) -> str:
"""Get lifestyle management guidelines based on disease and severity."""
guidelines = "<ul>"
# General guidelines for all conditions
guidelines += "<li>์ถฉ๋ถ„ํ•œ ์ˆ˜๋ถ„ ์„ญ์ทจ (ํ•˜๋ฃจ 8์ž” ์ด์ƒ) / Drink plenty of water (8+ glasses daily)</li>"
guidelines += "<li>๊ท ํ˜• ์žกํžŒ ์‹๋‹จ ์œ ์ง€ / Maintain a balanced diet</li>"
guidelines += "<li>์ถฉ๋ถ„ํ•œ ์ˆ˜๋ฉด (7-8์‹œ๊ฐ„) / Get adequate sleep (7-8 hours)</li>"
# Specific guidelines based on disease type
disease_lower = disease.lower()
if 'acne' in disease_lower or 'pimple' in disease_lower:
guidelines += "<li>์ €์ž๊ทน์„ฑ ํด๋ Œ์ € ์‚ฌ์šฉ / Use gentle, non-comedogenic cleanser</li>"
guidelines += "<li>์†์œผ๋กœ ์–ผ๊ตด ๋งŒ์ง€์ง€ ์•Š๊ธฐ / Avoid touching face with hands</li>"
guidelines += "<li>์œ ์ œํ’ˆ๊ณผ ๊ณ ๋‹น๋ถ„ ์‹ํ’ˆ ์ œํ•œ / Limit dairy and high-sugar foods</li>"
if 'eczema' in disease_lower or 'dermatitis' in disease_lower:
guidelines += "<li>ํ•˜๋ฃจ 2ํšŒ ์ด์ƒ ๋ณด์Šต์ œ ์‚ฌ์šฉ / Apply moisturizer at least twice daily</li>"
guidelines += "<li>๋œจ๊ฑฐ์šด ๋ฌผ ์ƒค์›Œ ํ”ผํ•˜๊ธฐ / Avoid hot water showers</li>"
guidelines += "<li>๋ฉด ์†Œ์žฌ ์˜๋ฅ˜ ์ฐฉ์šฉ / Wear cotton clothing</li>"
if 'psoriasis' in disease_lower:
guidelines += "<li>์ŠคํŠธ๋ ˆ์Šค ๊ด€๋ฆฌ (์š”๊ฐ€, ๋ช…์ƒ) / Manage stress (yoga, meditation)</li>"
guidelines += "<li>์•Œ์ฝ”์˜ฌ ์„ญ์ทจ ์ œํ•œ / Limit alcohol consumption</li>"
guidelines += "<li>์ ์ ˆํ•œ ํ–‡๋น› ๋…ธ์ถœ / Moderate sun exposure</li>"
if severity == "high":
guidelines += "<li>์ฆ‰์‹œ ์ „๋ฌธ์˜ ์ƒ๋‹ด ์˜ˆ์•ฝ / Schedule immediate specialist consultation</li>"
guidelines += "<li>์ฒ˜๋ฐฉ๋œ ์•ฝ๋ฌผ ๊ทœ์น™์  ๋ณต์šฉ / Take prescribed medications regularly</li>"
guidelines += "<li>์ž์™ธ์„  ์ฐจ๋‹จ์ œ ๋งค์ผ ์‚ฌ์šฉ (SPF 30+) / Apply sunscreen daily (SPF 30+)</li>"
guidelines += "</ul>"
return guidelines
def _get_consultation_guidelines(self, severity: str) -> str:
"""Get consultation guidelines based on severity."""
guidelines = "<ul>"
if severity == "high":
guidelines += "<li><strong style='color: red;'>์ฆ‰์‹œ ์˜๋ฃŒ์ง„ ์ƒ๋‹ด ํ•„์š” / Immediate medical consultation required</strong></li>"
guidelines += "<li>24์‹œ๊ฐ„ ์ด๋‚ด ํ”ผ๋ถ€๊ณผ ๋ฐฉ๋ฌธ ๊ถŒ์žฅ / Visit dermatologist within 24 hours</li>"
guidelines += "<li>์ฆ์ƒ์ด 2์ฃผ ์ด์ƒ ์ง€์†๋  ๊ฒฝ์šฐ / If symptoms persist for more than 2 weeks</li>"
guidelines += "<li>ํ†ต์ฆ, ์ถœํ˜ˆ, ๋ถ„๋น„๋ฌผ์ด ์žˆ์„ ๊ฒฝ์šฐ / If pain, bleeding, or discharge occurs</li>"
guidelines += "<li>๋ณ‘๋ณ€์ด ๊ธ‰์†ํžˆ ํ™•์‚ฐ๋˜๋Š” ๊ฒฝ์šฐ / If lesions spread rapidly</li>"
guidelines += "<li>๋ฐœ์—ด, ์˜คํ•œ ๋“ฑ ์ „์‹  ์ฆ์ƒ ๋™๋ฐ˜ ์‹œ / If accompanied by fever or chills</li>"
guidelines += "<li>์ผ์ƒ์ƒํ™œ์— ์ง€์žฅ์„ ์ค„ ์ •๋„๋กœ ์‹ฌํ•œ ๊ฒฝ์šฐ / If severe enough to affect daily life</li>"
guidelines += "<li>๊ธฐ์กด ์น˜๋ฃŒ์— ๋ฐ˜์‘ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ / If not responding to current treatment</li>"
guidelines += "</ul>"
return guidelines
def _get_test_recommendations(self, disease: str, severity: str) -> str:
"""Get test recommendations based on disease type."""
tests = "<ul>"
disease_lower = disease.lower()
# Cancer-related tests
if 'cancer' in disease_lower or 'carcinoma' in disease_lower or 'melanoma' in disease_lower:
tests += "<li>ํ”ผ๋ถ€ ์กฐ์ง๊ฒ€์‚ฌ (Biopsy) - <strong>ํ•„์ˆ˜</strong></li>"
tests += "<li>๋”๋ชจ์Šค์ฝ”ํ”ผ ๊ฒ€์‚ฌ / Dermoscopy examination</li>"
tests += "<li>์ „์‹  ํ”ผ๋ถ€ ๊ฒ€์ง„ / Full body skin examination</li>"
tests += "<li>๋ฆผํ”„์ ˆ ๊ฒ€์‚ฌ / Lymph node examination</li>"
# Infection tests
elif 'infection' in disease_lower or 'bacterial' in disease_lower or 'fungal' in disease_lower:
tests += "<li>์„ธ๊ท  ๋ฐฐ์–‘ ๊ฒ€์‚ฌ / Bacterial culture</li>"
tests += "<li>์ง„๊ท  ๊ฒ€์‚ฌ (KOH test) / Fungal examination</li>"
tests += "<li>ํ•ญ์ƒ์ œ ๊ฐ์ˆ˜์„ฑ ๊ฒ€์‚ฌ / Antibiotic sensitivity test</li>"
# Allergy/Dermatitis tests
elif 'dermatitis' in disease_lower or 'eczema' in disease_lower:
tests += "<li>ํŒจ์น˜ ํ…Œ์ŠคํŠธ (์•Œ๋ ˆ๋ฅด๊ธฐ ๊ฒ€์‚ฌ) / Patch testing</li>"
tests += "<li>ํ˜ˆ์•ก IgE ๊ฒ€์‚ฌ / Blood IgE test</li>"
tests += "<li>ํ”ผ๋ถ€ ๋‹จ์ž ๊ฒ€์‚ฌ / Skin prick test</li>"
# General tests
else:
tests += "<li>ํ”ผ๋ถ€ ํ™•๋Œ€๊ฒฝ ๊ฒ€์‚ฌ / Magnified skin examination</li>"
tests += "<li>Wood's lamp ๊ฒ€์‚ฌ (ํ•„์š”์‹œ) / Wood's lamp examination (if needed)</li>"
if severity == "high":
tests += "<li>ํ˜ˆ์•ก ๊ฒ€์‚ฌ (CBC, CRP) / Blood tests (CBC, CRP)</li>"
tests += "<li>์˜์ƒ ๊ฒ€์‚ฌ (ํ•„์š”์‹œ) / Imaging studies (if needed)</li>"
tests += "</ul>"
return tests
def _get_precautions(self, disease: str, severity: str) -> str:
"""Get precautions based on disease type."""
precautions = "<ul>"
if severity == "high":
precautions += "<li><strong>์ž๊ฐ€ ์น˜๋ฃŒ ๊ธˆ์ง€ - ๋ฐ˜๋“œ์‹œ ์ „๋ฌธ์˜ ์ƒ๋‹ด</strong></li>"
precautions += "<li>์ฒ˜๋ฐฉ ์—†์ด ์Šคํ…Œ๋กœ์ด๋“œ ์—ฐ๊ณ  ์žฅ๊ธฐ ์‚ฌ์šฉ ๊ธˆ์ง€ / Avoid long-term steroid use without prescription</li>"
precautions += "<li>๋ณ‘๋ณ€ ๋ถ€์œ„ ๊ธ๊ฑฐ๋‚˜ ์ž๊ทนํ•˜์ง€ ์•Š๊ธฐ / Do not scratch or irritate affected areas</li>"
precautions += "<li>๊ฒ€์ฆ๋˜์ง€ ์•Š์€ ๋ฏผ๊ฐ„์š”๋ฒ• ํ”ผํ•˜๊ธฐ / Avoid unverified home remedies</li>"
precautions += "<li>ํƒ€์ธ๊ณผ ์ˆ˜๊ฑด, ์˜๋ฅ˜ ๊ณต์œ  ๊ธˆ์ง€ / Do not share towels or clothing</li>"
precautions += "<li>์ฆ์ƒ ์•…ํ™” ์‹œ ์ฆ‰์‹œ ์˜๋ฃŒ์ง„ ์ƒ๋‹ด / Consult immediately if symptoms worsen</li>"
precautions += "</ul>"
return precautions
def _translate_disease_name(self, disease_name: str) -> str:
"""Translate disease name to Korean if not in database."""
# Common translations for diseases not in main database
translations = {
"healthy": "์ •์ƒ",
"benign": "์–‘์„ฑ",
"malignant": "์•…์„ฑ",
"infection": "๊ฐ์—ผ",
"inflammation": "์—ผ์ฆ",
"allergy": "์•Œ๋ ˆ๋ฅด๊ธฐ",
"autoimmune": "์ž๊ฐ€๋ฉด์—ญ",
"bacterial": "์„ธ๊ท ์„ฑ",
"viral": "๋ฐ”์ด๋Ÿฌ์Šค์„ฑ"
}
# Check if any keyword matches
lower_name = disease_name.lower()
for key, value in translations.items():
if key in lower_name:
return f"{value} ๊ด€๋ จ ์งˆํ™˜"
# Return original if no translation found
return disease_name
def _estimate_severity(self, disease_name: str) -> str:
"""Estimate severity based on disease name keywords."""
high_severity_keywords = ["cancer", "carcinoma", "melanoma", "malignant", "cellulitis", "severe"]
medium_severity_keywords = ["infection", "inflammatory", "chronic", "dermatitis", "eczema", "psoriasis"]
lower_name = disease_name.lower()
for keyword in high_severity_keywords:
if keyword in lower_name:
return "high"
for keyword in medium_severity_keywords:
if keyword in lower_name:
return "medium"
return "low"
# Initialize the system
try:
diagnosis_system = MedicalSkinDiagnosisSystem()
system_ready = True
except Exception as e:
logger.error(f"Failed to initialize system: {e}")
diagnosis_system = None
system_ready = False
def perform_diagnosis(image: Image.Image) -> Tuple[str, str]:
"""
Main diagnosis function for Gradio interface.
Returns:
Tuple of (diagnosis_result, gpt_analysis)
"""
if not system_ready or diagnosis_system is None:
return "โŒ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค / System unavailable", ""
if image is None:
return "โŒ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š” / Please upload an image", ""
try:
class_probs, formatted_output, gpt_analysis = diagnosis_system.diagnose(image)
if not class_probs:
return formatted_output, ""
return formatted_output, gpt_analysis
except Exception as e:
logger.error(f"Diagnosis error: {e}")
return f"โŒ ์ฒ˜๋ฆฌ ์‹คํŒจ / Processing failed: {str(e)}", ""
def create_medical_interface():
"""Create medical-grade Gradio interface."""
with gr.Blocks(
title="AI ํ”ผ๋ถ€ ์ง„๋‹จ ์‹œ์Šคํ…œ / AI Skin Diagnosis System",
theme=gr.themes.Soft(),
css=medical_css
) as interface:
# Header
gr.HTML("""
<div class="main-header">
<h1 style="text-align: center; margin: 0;">
๐Ÿฅ AI ํ”ผ๋ถ€ ์งˆํ™˜ ์ง„๋‹จ ์‹œ์Šคํ…œ<br>
<span style="font-size: 0.6em; opacity: 0.9;">
AI-Powered Skin Disease Diagnosis System
</span>
</h1>
<p style="text-align: center; margin-top: 1rem; opacity: 0.9;">
GPT-OSS 120B ๋ชจ๋ธ ํ†ตํ•ฉ ์ง„๋‹จ / Integrated with GPT-OSS 120B Model
</p>
</div>
""")
# System status
with gr.Row():
gr.HTML(f"""
<div style="display: flex; justify-content: center; gap: 20px; margin: 1rem 0;">
<span class="medical-badge">
<span class="status-indicator status-ready"></span>
์‹œ์Šคํ…œ ์ƒํƒœ: {"์ •์ƒ" if system_ready else "์˜ค๋ฅ˜"} / System: {"Ready" if system_ready else "Error"}
</span>
<span class="medical-badge">
Vision Model: {diagnosis_system.model_name if diagnosis_system else "N/A"}
</span>
<span class="medical-badge">
GPT-OSS: 120B Model
</span>
<span class="medical-badge">
GPU: {"ํ™œ์„ฑํ™”" if torch.cuda.is_available() else "๋น„ํ™œ์„ฑํ™”"} / {"Enabled" if torch.cuda.is_available() else "Disabled"}
</span>
</div>
""")
with gr.Row():
with gr.Column(scale=1):
# Input section
gr.Markdown("### ๐Ÿ“ธ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ / Image Upload")
image_input = gr.Image(
type="pil",
label="ํ”ผ๋ถ€ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ / Upload Skin Image",
height=400
)
with gr.Row():
diagnose_btn = gr.Button(
"๐Ÿ”ฌ AI ์ง„๋‹จ ์‹œ์ž‘ / Start AI Diagnosis",
variant="primary",
size="lg",
elem_classes="button-primary"
)
clear_btn = gr.Button(
"๐Ÿ”„ ์ดˆ๊ธฐํ™” / Clear",
variant="secondary",
size="lg"
)
# Advanced settings
with gr.Accordion("โš™๏ธ ๊ณ ๊ธ‰ ์„ค์ • / Advanced Settings", open=False):
confidence_threshold = gr.Slider(
minimum=0.1,
maximum=0.9,
value=0.5,
step=0.1,
label="์ตœ์†Œ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ / Minimum Confidence Threshold"
)
analysis_depth = gr.Radio(
choices=["๊ธฐ๋ณธ / Basic", "์ƒ์„ธ / Detailed", "์ „๋ฌธ๊ฐ€ / Expert"],
value="์ƒ์„ธ / Detailed",
label="๋ถ„์„ ๊นŠ์ด / Analysis Depth"
)
with gr.Column(scale=2):
# Results section
gr.Markdown("### ๐Ÿ“Š ์ง„๋‹จ ๊ฒฐ๊ณผ / Diagnosis Results")
diagnosis_output = gr.HTML(
label="Vision Model ์ง„๋‹จ / Vision Model Diagnosis",
value="<div class='info-box'>์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ์ง„๋‹จ์„ ์‹œ์ž‘ํ•˜์„ธ์š”.<br>Upload an image and start diagnosis.</div>"
)
gpt_analysis = gr.HTML(
label="GPT-OSS ๋ถ„์„ / GPT-OSS Analysis",
value=""
)
# Example images with proper paths
gr.Markdown("### ๐Ÿ“ ์˜ˆ์ œ ์ด๋ฏธ์ง€ / Example Images")
# Define the actual example images that were uploaded
example_images = [
["andrea.jpeg"],
["clare.jpeg"],
["disorder.jpeg"],
["joe.jpeg"],
["woman.jpeg"]
]
# Check if images exist and create examples
available_examples = []
for img_path in example_images:
# Check various possible locations
possible_paths = [
img_path[0], # Current directory
f"examples/{img_path[0]}", # examples folder
f"./{img_path[0]}", # Explicit current directory
f"./examples/{img_path[0]}" # Explicit examples folder
]
for path in possible_paths:
if Path(path).exists():
available_examples.append([path])
break
if available_examples:
gr.Examples(
examples=available_examples,
inputs=[image_input],
label="์ƒ˜ํ”Œ ํ”ผ๋ถ€ ์ด๋ฏธ์ง€ / Sample Skin Images",
examples_per_page=5
)
else:
# If images not found in expected locations, show instructions
gr.HTML("""
<div class="info-box">
<p><strong>๐Ÿ“ธ ์ƒ˜ํ”Œ ์ด๋ฏธ์ง€ ์œ„์น˜ / Sample Image Location:</strong></p>
<p>๋‹ค์Œ ์ด๋ฏธ์ง€๋“ค์ด ํ”„๋กœ์ ํŠธ ํด๋”์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค / The following images should be in your project folder:</p>
<ul>
<li>andrea.jpeg (57.5 KB)</li>
<li>clare.jpeg (49.8 KB)</li>
<li>disorder.jpeg (55.5 KB)</li>
<li>joe.jpeg (85.5 KB)</li>
<li>woman.jpeg</li>
</ul>
<p style="margin-top: 1rem;">
<strong>์ด๋ฏธ์ง€๋ฅผ ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋˜๋Š” examples ํด๋”์— ๋ฐฐ์น˜ํ•˜์„ธ์š”</strong><br>
<em>Place images in project root or examples folder</em>
</p>
</div>
""")
# Event handlers
diagnose_btn.click(
fn=perform_diagnosis,
inputs=[image_input],
outputs=[diagnosis_output, gpt_analysis]
)
clear_btn.click(
fn=lambda: (None,
"<div class='info-box'>์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ์ง„๋‹จ์„ ์‹œ์ž‘ํ•˜์„ธ์š”.<br>Upload an image and start diagnosis.</div>",
""),
inputs=[],
outputs=[image_input, diagnosis_output, gpt_analysis]
)
image_input.change(
fn=lambda x: ("<div class='info-box'>์ง„๋‹จ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”.<br>Click the diagnosis button.</div>", "") if x else ("", ""),
inputs=[image_input],
outputs=[diagnosis_output, gpt_analysis]
)
# Footer
gr.HTML("""
<div style="margin-top: 3rem; padding: 2rem; background: rgba(255,255,255,0.9); border-radius: 15px;">
<h4 style="color: #2d3748;">๐Ÿ“‹ ์˜๋ฃŒ ๋ฉด์ฑ… ์กฐํ•ญ / Medical Disclaimer</h4>
<p style="color: #4a5568; line-height: 1.6;">
๋ณธ ์‹œ์Šคํ…œ์€ AI ๊ธฐ๋ฐ˜ ๋ณด์กฐ ๋„๊ตฌ์ด๋ฉฐ, ์˜ํ•™์  ์ง„๋‹จ์„ ๋Œ€์ฒดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋ชจ๋“  ๊ฒฐ๊ณผ๋Š” ์ฐธ๊ณ ์šฉ์ด๋ฉฐ, ์ •ํ™•ํ•œ ์ง„๋‹จ๊ณผ ์น˜๋ฃŒ๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋ฐ˜๋“œ์‹œ ์˜๋ฃŒ ์ „๋ฌธ๊ฐ€์™€ ์ƒ๋‹ดํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.<br><br>
<em>This system is an AI-based assistive tool and does not replace medical diagnosis.
All results are for reference only. Please consult with healthcare professionals for accurate diagnosis and treatment.</em>
</p>
<hr style="margin: 1rem 0; opacity: 0.3;">
<p style="text-align: center; color: #718096; font-size: 0.9rem;">
Powered by Vision Transformer & GPT-OSS 120B | ยฉ 2024 Medical AI Systems
</p>
</div>
""")
return interface
# Health check function
def health_check() -> Dict[str, str]:
"""System health check for monitoring."""
return {
"status": "healthy" if system_ready else "unhealthy",
"vision_model": diagnosis_system.model_name if diagnosis_system else "not_loaded",
"gpt_model": "gpt-oss-120b",
"classes": len(diagnosis_system.id2label) if diagnosis_system else 0,
"device": diagnosis_system.device if diagnosis_system else "unknown",
"gpu_available": str(torch.cuda.is_available())
}
# Main execution
if __name__ == "__main__":
try:
app = create_medical_interface()
# Launch with HuggingFace Spaces configuration
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True
)
except Exception as e:
logger.error(f"Failed to launch interface: {e}")
print(f"โŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹คํŒจ / Application failed to start: {e}")
# Export key functions
__all__ = ['MedicalSkinDiagnosisSystem', 'perform_diagnosis', 'health_check']