Spaces:
Running
Running
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# app.py โ ๊ทธ๋ก์ผ๋ AI Day 3 (์ ์ง ์ปจ์
ยท ๋ฐ๋ปํ ๋ฒ์ )
|
| 3 |
+
# ์์ฐ์ด ํ๋กฌํํธ ๊ธฐ๋ฐ ๋์์ธ / ๋ฌด์ญ์ง ์๊ณ ์น๊ทผํ ์ ์ง
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
import warnings
|
| 7 |
+
warnings.filterwarnings("ignore", message=".*HF_TOKEN.*")
|
| 8 |
+
|
| 9 |
+
import gradio as gr
|
| 10 |
+
import torch, torch.nn as nn, timm
|
| 11 |
+
from torchvision import models, transforms
|
| 12 |
+
from huggingface_hub import hf_hub_download
|
| 13 |
+
from PIL import Image
|
| 14 |
+
import numpy as np
|
| 15 |
+
|
| 16 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 17 |
+
# 1. 5๊ฐ AI ๋ชจ๋ธ ๋ก๋ (Space ์์ ์ 1๋ฒ๋ง) โป ๊ทธ๋๋ก ์ ์ง
|
| 18 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 19 |
+
REPO_ID = "junkim1209/isic-models"
|
| 20 |
+
MODEL_NAMES = ['efficientnet_b3', 'efficientnet_b5',
|
| 21 |
+
'convnext_tiny', 'convnext_base', 'swin_base']
|
| 22 |
+
|
| 23 |
+
def build_model(name):
|
| 24 |
+
if name == 'efficientnet_b3':
|
| 25 |
+
m = models.efficientnet_b3(weights=None)
|
| 26 |
+
m.classifier[1] = nn.Linear(m.classifier[1].in_features, 1); return m, 300
|
| 27 |
+
elif name == 'efficientnet_b5':
|
| 28 |
+
return timm.create_model('efficientnet_b5', pretrained=False, num_classes=1), 456
|
| 29 |
+
elif name == 'convnext_tiny':
|
| 30 |
+
m = models.convnext_tiny(weights=None)
|
| 31 |
+
m.classifier[2] = nn.Linear(m.classifier[2].in_features, 1); return m, 224
|
| 32 |
+
elif name == 'convnext_base':
|
| 33 |
+
return timm.create_model('convnext_base', pretrained=False, num_classes=1), 224
|
| 34 |
+
elif name == 'swin_base':
|
| 35 |
+
return timm.create_model('swin_base_patch4_window12_384',
|
| 36 |
+
pretrained=False, num_classes=1), 384
|
| 37 |
+
|
| 38 |
+
print("๐ฅ ๋ฌด๋น 5๋ช
๋ชจ์๋ ์ค...")
|
| 39 |
+
LOADED = {}
|
| 40 |
+
for name in MODEL_NAMES:
|
| 41 |
+
path = hf_hub_download(repo_id=REPO_ID, filename=f"{name}.pth")
|
| 42 |
+
model, size = build_model(name)
|
| 43 |
+
state = torch.load(path, map_location='cpu', weights_only=False)
|
| 44 |
+
model.load_state_dict(state['model_state_dict'], strict=True)
|
| 45 |
+
model.eval()
|
| 46 |
+
LOADED[name] = {'model': model, 'size': size}
|
| 47 |
+
print("โ
์ ์ง ๊ฐ์ ์ค๋น ์๋ฃ")
|
| 48 |
+
|
| 49 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 50 |
+
# 2. ๋ณธ์ธ 1์ผ์ฐจ ๊ฐ์ค์น
|
| 51 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 52 |
+
MY_WEIGHTS = {
|
| 53 |
+
'efficientnet_b3': 1.0, 'efficientnet_b5': 1.0,
|
| 54 |
+
'convnext_tiny': 1.0, 'convnext_base': 1.0,
|
| 55 |
+
'swin_base': 1.0,
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 59 |
+
# 3. ์ถ๋ก + ๋ฌธ์ง ๊ฒฐํฉ โป predict ๋ณธ์ฒด๋ ์ ์งํ๊ณ ํจ์ ์๊ทธ๋์ฒ๋ง ํ์ฅ
|
| 60 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 61 |
+
def predict_with_questionnaire(image, age, family_history, mole_change):
|
| 62 |
+
if image is None:
|
| 63 |
+
return None, ""
|
| 64 |
+
|
| 65 |
+
img = Image.fromarray(image).convert('RGB')
|
| 66 |
+
probs = {}
|
| 67 |
+
for name, info in LOADED.items():
|
| 68 |
+
tfm = transforms.Compose([
|
| 69 |
+
transforms.Resize((info['size'], info['size'])),
|
| 70 |
+
transforms.ToTensor(),
|
| 71 |
+
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
| 72 |
+
])
|
| 73 |
+
x = tfm(img).unsqueeze(0)
|
| 74 |
+
with torch.no_grad():
|
| 75 |
+
probs[name] = torch.sigmoid(info['model'](x)).item()
|
| 76 |
+
|
| 77 |
+
total = sum(MY_WEIGHTS.values())
|
| 78 |
+
ai_prob = sum(probs[k] * w / total for k, w in MY_WEIGHTS.items())
|
| 79 |
+
|
| 80 |
+
bonus = 0.0
|
| 81 |
+
if family_history == "์": bonus += 0.10
|
| 82 |
+
if mole_change == "์": bonus += 0.15
|
| 83 |
+
if age >= 50: bonus += 0.05
|
| 84 |
+
final = min(ai_prob + bonus, 1.0)
|
| 85 |
+
|
| 86 |
+
label_data = {
|
| 87 |
+
"ํ(ๅถ) โ ๋ฉ๋ผ๋
ธ๋ง ์์ฌ": float(final),
|
| 88 |
+
"๊ธธ(ๅ) โ ์์ฑ ๋ชจ๋ฐ": float(1 - final),
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
# ์ํ๋๋ณ ์ ๊ด ํ์ด (๋ฐ๋ปํ ํค)
|
| 92 |
+
if final < 0.20:
|
| 93 |
+
tag = "๊ธธ์กฐ (ๅๅ
)"
|
| 94 |
+
msg = "์ด ์ ์ ํ์จํ ์ ์ด๋ผ๋ค.<br>์ผ๋ฐ์ ์ธ ๋ชจ๋ฐ์ผ๋ก ๋ณด์ด์ค. ํ์์ฒ๋ผ ์๊ฐ ๊ด์ฐฐ์ ์ด์ด๊ฐ์์ค."
|
| 95 |
+
tag_color = "#3F8B5F" # ๋ถ๋๋ฌ์ด ์ด๋ก
|
| 96 |
+
tag_bg = "#EDF6EF"
|
| 97 |
+
elif final < 0.50:
|
| 98 |
+
tag = "์ฃผ์ (ๆณจๆ)"
|
| 99 |
+
msg = "์กฐ๊ธ ์ ๊ฒฝ ์ฐ์ด๋ ๊ธฐ์ด์ด ์๊ตฌ๋ ค.<br>๊ฐ๊น์ด ํผ๋ถ๊ณผ์์ ํ๋ฒ ๋ด๋ฌ๋ผ ํ์์ค."
|
| 100 |
+
tag_color = "#C9722E" # ๋ฐ๋ปํ ์ฃผํฉ
|
| 101 |
+
tag_bg = "#FCF1E0"
|
| 102 |
+
else:
|
| 103 |
+
tag = "ํ์กฐ (ๅถๅ
)"
|
| 104 |
+
msg = "์ด ์ ์ ์ข ์ดํด๋ด์ผ ํ ๊ฒ ๊ฐ์.<br>๊ฐ๋ฅํ ๋นจ๋ฆฌ ํผ๋ถ๊ณผ ์ ๋ฌธ์์๊ฒ ์ง๋ฃ๋ฅผ ๋ฐ์ผ์์ค."
|
| 105 |
+
tag_color = "#B5394A" # ๋ฌด์ญ์ง ์์ ์ง์ ์ฃผํ
|
| 106 |
+
tag_bg = "#FBEAEC"
|
| 107 |
+
|
| 108 |
+
detail_html = f"""
|
| 109 |
+
<div style='background:{tag_bg}; border-left:5px solid {tag_color};
|
| 110 |
+
padding: 16px 18px; border-radius: 12px; margin-top: 14px;'>
|
| 111 |
+
<div style='color:{tag_color}; font-weight:800; font-size:16px;
|
| 112 |
+
margin-bottom:8px; letter-spacing:1.5px;'>
|
| 113 |
+
๐ฎ {tag}
|
| 114 |
+
</div>
|
| 115 |
+
<div style='color:#4A3826; font-size:13.5px; line-height:1.65;'>{msg}</div>
|
| 116 |
+
</div>
|
| 117 |
+
|
| 118 |
+
<div style='background:#FFFBF0; padding:14px 16px; border-radius:12px;
|
| 119 |
+
margin-top:12px; font-size:12.5px; color:#6B4423;
|
| 120 |
+
border: 1px dashed #D4B873;'>
|
| 121 |
+
<div style='font-weight:700; margin-bottom:8px; color:#A8462C;
|
| 122 |
+
letter-spacing: 1px;'>๐ ์ ๊ด ํ์ด</div>
|
| 123 |
+
AI ๋ฌด๋น์ ์ง๊ด: <strong>{ai_prob*100:.1f}%</strong><br>
|
| 124 |
+
์ด์(๋ฌธ์ง) ๊ฐ์ฐ: <strong>+{bonus*100:.1f}%</strong><br>
|
| 125 |
+
โ ์ต์ข
ํ์กฐ ์ ๋: <strong style='color:{tag_color}; font-size:14px;'>{final*100:.1f}%</strong>
|
| 126 |
+
</div>
|
| 127 |
+
"""
|
| 128 |
+
return label_data, detail_html
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 132 |
+
# 4. ์ ์ง CSS โ ๋ฐ๋ปํ ํ์งยท์ฃผํ ํค
|
| 133 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 134 |
+
custom_css = """
|
| 135 |
+
:root {
|
| 136 |
+
--brand: #C24536; /* ๋ฐ๋ปํ ์ฃผํ (ํ
๋ผ์ฝํ) */
|
| 137 |
+
--brand-dark: #9E3528;
|
| 138 |
+
--brand-soft: #E8533D; /* ๋ ๋ฐ์ ์ฃผํ (ํฌ์ธํธ) */
|
| 139 |
+
--gold: #D4A93B; /* ๋ถ๋๋ฌ์ด ๊ธ์ */
|
| 140 |
+
--gold-light: #E8C868;
|
| 141 |
+
--paper: #FFF8E7; /* ํ์ง์ */
|
| 142 |
+
--paper-warm: #FCEFD3; /* ๋ ์งํ ํ์ง */
|
| 143 |
+
--ink: #4A3826;
|
| 144 |
+
--ink-sub: #7A5C3D;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
.gradio-container {
|
| 148 |
+
max-width: 430px !important;
|
| 149 |
+
margin: 0 auto !important;
|
| 150 |
+
padding: 0 !important;
|
| 151 |
+
background: var(--paper) !important;
|
| 152 |
+
min-height: 100vh !important;
|
| 153 |
+
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont,
|
| 154 |
+
'Noto Sans KR', sans-serif !important;
|
| 155 |
+
background-image:
|
| 156 |
+
radial-gradient(circle at 15% 8%, rgba(212,169,59,0.12) 0%, transparent 45%),
|
| 157 |
+
radial-gradient(circle at 85% 92%, rgba(232,83,61,0.08) 0%, transparent 45%);
|
| 158 |
+
}
|
| 159 |
+
footer { display: none !important; }
|
| 160 |
+
|
| 161 |
+
/* ํ์ง ์ ๋์ฅ ๋๋ ํค๋ */
|
| 162 |
+
.scr-header {
|
| 163 |
+
background: linear-gradient(135deg, #C24536 0%, #9E3528 100%);
|
| 164 |
+
color: white;
|
| 165 |
+
padding: 22px 22px 20px;
|
| 166 |
+
margin-bottom: 14px;
|
| 167 |
+
position: relative;
|
| 168 |
+
border-radius: 0 0 16px 16px;
|
| 169 |
+
}
|
| 170 |
+
.scr-header::after {
|
| 171 |
+
content: "";
|
| 172 |
+
position: absolute; left: 18px; right: 18px; bottom: -2px; height: 2px;
|
| 173 |
+
background: repeating-linear-gradient(
|
| 174 |
+
90deg, var(--gold) 0, var(--gold) 8px,
|
| 175 |
+
transparent 8px, transparent 14px
|
| 176 |
+
);
|
| 177 |
+
}
|
| 178 |
+
.scr-header .scr-title {
|
| 179 |
+
font-size: 22px;
|
| 180 |
+
font-weight: 800;
|
| 181 |
+
letter-spacing: 1.5px;
|
| 182 |
+
font-family: 'Noto Serif KR', 'Nanum Myeongjo', serif;
|
| 183 |
+
}
|
| 184 |
+
.scr-header .scr-sub {
|
| 185 |
+
font-size: 12.5px;
|
| 186 |
+
opacity: 0.94;
|
| 187 |
+
margin-top: 5px;
|
| 188 |
+
letter-spacing: 0.3px;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/* ์งํ ํ์ */
|
| 192 |
+
.progress-dots {
|
| 193 |
+
display: flex;
|
| 194 |
+
gap: 7px;
|
| 195 |
+
margin: 6px 18px 14px;
|
| 196 |
+
justify-content: center;
|
| 197 |
+
}
|
| 198 |
+
.progress-dot {
|
| 199 |
+
width: 28px;
|
| 200 |
+
height: 5px;
|
| 201 |
+
border-radius: 3px;
|
| 202 |
+
background: #ECDBB0;
|
| 203 |
+
}
|
| 204 |
+
.progress-dot.active {
|
| 205 |
+
background: var(--gold);
|
| 206 |
+
box-shadow: 0 0 8px rgba(212,169,59,0.5);
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
/* ํ์ง ์นด๋ */
|
| 210 |
+
.app-card {
|
| 211 |
+
background: white;
|
| 212 |
+
border-radius: 14px;
|
| 213 |
+
padding: 16px 18px;
|
| 214 |
+
margin: 0 18px 12px;
|
| 215 |
+
border: 1px solid #EEDFB8;
|
| 216 |
+
box-shadow: 0 2px 8px rgba(194,69,54,0.06);
|
| 217 |
+
}
|
| 218 |
+
.app-card-title {
|
| 219 |
+
font-size: 14.5px;
|
| 220 |
+
font-weight: 800;
|
| 221 |
+
color: var(--brand);
|
| 222 |
+
margin-bottom: 6px;
|
| 223 |
+
letter-spacing: 0.5px;
|
| 224 |
+
font-family: 'Noto Serif KR', serif;
|
| 225 |
+
}
|
| 226 |
+
.app-card-desc {
|
| 227 |
+
font-size: 12.8px;
|
| 228 |
+
color: var(--ink-sub);
|
| 229 |
+
line-height: 1.7;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
/* ๋์คํด๋ ์ด๋จธ โ ๋ถ์ ๋๋ */
|
| 233 |
+
.disclaimer {
|
| 234 |
+
background: #FFF3DE;
|
| 235 |
+
color: #8B5A2B;
|
| 236 |
+
padding: 11px 14px;
|
| 237 |
+
border-radius: 10px;
|
| 238 |
+
margin: 0 18px 12px;
|
| 239 |
+
font-size: 12px;
|
| 240 |
+
font-weight: 600;
|
| 241 |
+
text-align: center;
|
| 242 |
+
border: 1.5px dashed #D4A93B;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
/* ํ ํ์ด๋ก */
|
| 246 |
+
.jeomjip-hero {
|
| 247 |
+
text-align: center;
|
| 248 |
+
padding: 12px 24px 12px;
|
| 249 |
+
}
|
| 250 |
+
.mudang-svg {
|
| 251 |
+
width: 240px;
|
| 252 |
+
max-width: 100%;
|
| 253 |
+
height: auto;
|
| 254 |
+
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.08));
|
| 255 |
+
}
|
| 256 |
+
.hero-title {
|
| 257 |
+
font-size: 30px;
|
| 258 |
+
font-weight: 900;
|
| 259 |
+
color: var(--brand);
|
| 260 |
+
margin: 14px 0 8px;
|
| 261 |
+
letter-spacing: 3px;
|
| 262 |
+
font-family: 'Noto Serif KR', 'Nanum Myeongjo', serif;
|
| 263 |
+
}
|
| 264 |
+
.hero-sub {
|
| 265 |
+
font-size: 14px;
|
| 266 |
+
color: var(--ink-sub);
|
| 267 |
+
line-height: 1.75;
|
| 268 |
+
margin-bottom: 4px;
|
| 269 |
+
}
|
| 270 |
+
.hero-quote {
|
| 271 |
+
color: var(--brand);
|
| 272 |
+
font-weight: 700;
|
| 273 |
+
font-style: italic;
|
| 274 |
+
display: inline-block;
|
| 275 |
+
margin-top: 8px;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
/* ๋์ฅ ๋๋ ์ฃผ์ ๋ฒํผ */
|
| 279 |
+
button.primary-btn {
|
| 280 |
+
background: var(--brand) !important;
|
| 281 |
+
color: white !important;
|
| 282 |
+
border: none !important;
|
| 283 |
+
padding: 15px !important;
|
| 284 |
+
font-size: 16px !important;
|
| 285 |
+
font-weight: 800 !important;
|
| 286 |
+
border-radius: 14px !important;
|
| 287 |
+
min-height: 56px !important;
|
| 288 |
+
margin: 0 18px 10px !important;
|
| 289 |
+
width: calc(100% - 36px) !important;
|
| 290 |
+
letter-spacing: 2.5px !important;
|
| 291 |
+
box-shadow: 0 4px 0 var(--brand-dark),
|
| 292 |
+
0 6px 12px rgba(194,69,54,0.18) !important;
|
| 293 |
+
font-family: 'Noto Serif KR', serif !important;
|
| 294 |
+
transition: all 0.15s !important;
|
| 295 |
+
}
|
| 296 |
+
button.primary-btn:hover {
|
| 297 |
+
background: var(--brand-soft) !important;
|
| 298 |
+
transform: translateY(2px);
|
| 299 |
+
box-shadow: 0 2px 0 var(--brand-dark),
|
| 300 |
+
0 3px 6px rgba(194,69,54,0.18) !important;
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
/* ์ข
์ด ๋๋ ๋ณด์กฐ ๋ฒํผ */
|
| 304 |
+
button.secondary-btn {
|
| 305 |
+
background: white !important;
|
| 306 |
+
color: var(--brand) !important;
|
| 307 |
+
border: 2px solid var(--brand) !important;
|
| 308 |
+
padding: 13px !important;
|
| 309 |
+
font-size: 14.5px !important;
|
| 310 |
+
font-weight: 800 !important;
|
| 311 |
+
border-radius: 14px !important;
|
| 312 |
+
min-height: 50px !important;
|
| 313 |
+
margin: 0 18px 10px !important;
|
| 314 |
+
width: calc(100% - 36px) !important;
|
| 315 |
+
letter-spacing: 1.5px !important;
|
| 316 |
+
font-family: 'Noto Serif KR', serif !important;
|
| 317 |
+
}
|
| 318 |
+
button.secondary-btn:hover {
|
| 319 |
+
background: #FFF3DE !important;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
.row-buttons button { width: 100% !important; margin: 0 !important; }
|
| 323 |
+
.row-buttons { padding: 0 18px 10px; gap: 10px !important; }
|
| 324 |
+
|
| 325 |
+
label, .label, .gradio-container label { color: var(--ink) !important; }
|
| 326 |
+
"""
|
| 327 |
+
|
| 328 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 329 |
+
# 5. ์น๊ทผํ ๋ฌด๋น SVG โ ๋ฏธ์ ๋ค ํ์
|
| 330 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 331 |
+
MUDANG_SVG = """
|
| 332 |
+
<svg class="mudang-svg" viewBox="0 0 260 230" xmlns="http://www.w3.org/2000/svg">
|
| 333 |
+
<!-- ๋ถ์ ์ (๋ฐฐ๊ฒฝ ์ฅ์) -->
|
| 334 |
+
<circle cx="30" cy="40" r="3" fill="#D4A93B" opacity="0.4"/>
|
| 335 |
+
<circle cx="240" cy="60" r="2.5" fill="#D4A93B" opacity="0.4"/>
|
| 336 |
+
<circle cx="20" cy="180" r="2" fill="#C24536" opacity="0.3"/>
|
| 337 |
+
|
| 338 |
+
<!-- ๊น๋ -->
|
| 339 |
+
<line x1="55" y1="30" x2="55" y2="210" stroke="#7A4F2A" stroke-width="5" stroke-linecap="round"/>
|
| 340 |
+
<circle cx="55" cy="30" r="6" fill="#D4A93B" stroke="#A8821A" stroke-width="0.6"/>
|
| 341 |
+
|
| 342 |
+
<!-- ๋ง์ ๊น๋ฐ (๋ถ๋๋ฌ์ด ๋นจ๊ฐ) -->
|
| 343 |
+
<path d="M 55 38 L 142 38 L 130 65 L 142 92 L 55 92 Z"
|
| 344 |
+
fill="#C24536" stroke="#9E3528" stroke-width="1"/>
|
| 345 |
+
<text x="92" y="76" text-anchor="middle" fill="#FFF3DE"
|
| 346 |
+
font-size="34" font-weight="bold" font-family="serif">ๅ</text>
|
| 347 |
+
<!-- ๊น๋ฐ ๋ฐ๋ ํ๋ ๋ฆผ ํ์ -->
|
| 348 |
+
<path d="M 55 92 Q 60 96 55 100" stroke="#9E3528" stroke-width="1.5" fill="none"/>
|
| 349 |
+
|
| 350 |
+
<!-- ๊ทธ๋ฆผ์ -->
|
| 351 |
+
<ellipse cx="175" cy="210" rx="42" ry="5" fill="#4A3826" opacity="0.13"/>
|
| 352 |
+
|
| 353 |
+
<!-- ํ๋ณต ์น๋ง (๋ฐ์ ์ฃผํ) -->
|
| 354 |
+
<path d="M 138 138 Q 175 132 212 138 L 222 205 Q 175 211 128 205 Z"
|
| 355 |
+
fill="#E8533D" stroke="#9E3528" stroke-width="1"/>
|
| 356 |
+
<!-- ์น๋ง ๋ฌด๋ฌ (๊ฝ์) -->
|
| 357 |
+
<circle cx="155" cy="175" r="2.5" fill="#D4A93B"/>
|
| 358 |
+
<circle cx="175" cy="185" r="2.5" fill="#D4A93B"/>
|
| 359 |
+
<circle cx="195" cy="172" r="2.5" fill="#D4A93B"/>
|
| 360 |
+
<path d="M 140 195 Q 175 192 210 195" stroke="#D4A93B" stroke-width="1.2" fill="none" opacity="0.6"/>
|
| 361 |
+
|
| 362 |
+
<!-- ๊ธ์ ๋ -->
|
| 363 |
+
<rect x="133" y="130" width="84" height="10" fill="#D4A93B" rx="1"/>
|
| 364 |
+
<rect x="133" y="130" width="84" height="10" fill="none" stroke="#A8821A" stroke-width="0.5" rx="1"/>
|
| 365 |
+
|
| 366 |
+
<!-- ์ ๊ณ ๋ฆฌ (๋ฐ๋ปํ ํฐ์) -->
|
| 367 |
+
<path d="M 143 95 Q 175 90 207 95 L 210 132 L 140 132 Z"
|
| 368 |
+
fill="#FFF8E7" stroke="#7A4F2A" stroke-width="1"/>
|
| 369 |
+
<!-- ์ ๊ณ ๋ฆฌ ๊น (์ฃผํ) -->
|
| 370 |
+
<path d="M 165 95 L 175 112 L 185 95" fill="#E8533D" stroke="#C24536" stroke-width="1.5" stroke-linejoin="round"/>
|
| 371 |
+
|
| 372 |
+
<!-- ์ผํ (๊น๋ ์ก๊ธฐ) -->
|
| 373 |
+
<path d="M 143 105 Q 100 100 65 65" stroke="#FFF8E7" stroke-width="12"
|
| 374 |
+
fill="none" stroke-linecap="round"/>
|
| 375 |
+
<path d="M 143 105 Q 100 100 65 65" stroke="#7A4F2A" stroke-width="1"
|
| 376 |
+
fill="none" stroke-linecap="round" opacity="0.25"/>
|
| 377 |
+
<circle cx="63" cy="65" r="7" fill="#F5C9A0" stroke="#7A4F2A" stroke-width="0.8"/>
|
| 378 |
+
|
| 379 |
+
<!-- ์ค๋ฅธํ (๋ฐ๊ฐ๊ฒ ๋ค๊ธฐ) -->
|
| 380 |
+
<path d="M 205 105 Q 230 105 235 125" stroke="#FFF8E7" stroke-width="12"
|
| 381 |
+
fill="none" stroke-linecap="round"/>
|
| 382 |
+
<path d="M 205 105 Q 230 105 235 125" stroke="#7A4F2A" stroke-width="1"
|
| 383 |
+
fill="none" stroke-linecap="round" opacity="0.25"/>
|
| 384 |
+
<!-- ์ ํ๋๋ ๋ชจ์ -->
|
| 385 |
+
<circle cx="237" cy="128" r="7" fill="#F5C9A0" stroke="#7A4F2A" stroke-width="0.8"/>
|
| 386 |
+
<!-- ์๊ฐ๋ฝ ํ์ -->
|
| 387 |
+
<line x1="235" y1="122" x2="234" y2="118" stroke="#7A4F2A" stroke-width="1" stroke-linecap="round"/>
|
| 388 |
+
<line x1="240" y1="122" x2="241" y2="118" stroke="#7A4F2A" stroke-width="1" stroke-linecap="round"/>
|
| 389 |
+
|
| 390 |
+
<!-- ์ผ๊ตด (๋ฅ๊ธ๊ณ ๋ฐ๋ปํ๊ฒ) -->
|
| 391 |
+
<ellipse cx="175" cy="73" rx="22" ry="24" fill="#F5C9A0" stroke="#7A4F2A" stroke-width="1"/>
|
| 392 |
+
|
| 393 |
+
<!-- ๋จธ๋ฆฌ์นด๋ฝ -->
|
| 394 |
+
<path d="M 153 64 Q 153 42 175 40 Q 197 42 197 64 Q 197 58 192 53 L 158 53 Q 153 58 153 64 Z"
|
| 395 |
+
fill="#3A2415"/>
|
| 396 |
+
<!-- ๋น๋
-->
|
| 397 |
+
<line x1="190" y1="51" x2="208" y2="40" stroke="#D4A93B" stroke-width="2.8" stroke-linecap="round"/>
|
| 398 |
+
<circle cx="208" cy="40" r="3" fill="#D4A93B" stroke="#A8821A" stroke-width="0.5"/>
|
| 399 |
+
|
| 400 |
+
<!-- ์ด๋ง ์ (์์ ๋นจ๊ฐ์ , ๋ถ์ ์๋ฏธ) -->
|
| 401 |
+
<circle cx="175" cy="63" r="2.4" fill="#C24536"/>
|
| 402 |
+
|
| 403 |
+
<!-- ์๋ ๋ (๋ฐ๋ฌ ๋ชจ์) -->
|
| 404 |
+
<path d="M 165 74 Q 168 71 171 74" stroke="#3A2415" stroke-width="2"
|
| 405 |
+
fill="none" stroke-linecap="round"/>
|
| 406 |
+
<path d="M 179 74 Q 182 71 185 74" stroke="#3A2415" stroke-width="2"
|
| 407 |
+
fill="none" stroke-linecap="round"/>
|
| 408 |
+
|
| 409 |
+
<!-- ์
(์๋ ์
) -->
|
| 410 |
+
<path d="M 168 83 Q 175 88 182 83" stroke="#9E3528" stroke-width="2"
|
| 411 |
+
fill="none" stroke-linecap="round"/>
|
| 412 |
+
|
| 413 |
+
<!-- ๋ณผํฐ์น (๋ถํ) -->
|
| 414 |
+
<ellipse cx="159" cy="79" rx="3.5" ry="2.5" fill="#E89090" opacity="0.55"/>
|
| 415 |
+
<ellipse cx="191" cy="79" rx="3.5" ry="2.5" fill="#E89090" opacity="0.55"/>
|
| 416 |
+
</svg>
|
| 417 |
+
"""
|
| 418 |
+
|
| 419 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 420 |
+
# 6. UI โ 5๊ฐ ํ๋ฉด
|
| 421 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 422 |
+
with gr.Blocks(css=custom_css, title="์ฐ๋ฆฌ ๋๋ค ์ ์ง",
|
| 423 |
+
theme=gr.themes.Soft(primary_hue="red")) as demo:
|
| 424 |
+
|
| 425 |
+
# =============== ํ๋ฉด 1: ํ ===============
|
| 426 |
+
with gr.Column(visible=True) as home_screen:
|
| 427 |
+
gr.HTML(f"""
|
| 428 |
+
<div class="scr-header">
|
| 429 |
+
<div class="scr-title">์ฐ๋ฆฌ ๋๋ค ์ ์ง</div>
|
| 430 |
+
<div class="scr-sub">๊ทธ๋ก์ผ๋ ยท AI ๋ฌด๋น์ด ๋ด์ฃผ๋ ์ (้ป) ์ (ๅ )</div>
|
| 431 |
+
</div>
|
| 432 |
+
<div class="disclaimer">โ ๏ธ ๊ต์ก์ฉ ๋๊ตฌ ยท ์๋ฃ ์ง๋จ ๋์ฒด ๋ถ๊ฐ</div>
|
| 433 |
+
<div class="jeomjip-hero">
|
| 434 |
+
{MUDANG_SVG}
|
| 435 |
+
<div class="hero-title">์ ๋ณด๋ฌ ์ค์
จ์</div>
|
| 436 |
+
<div class="hero-sub">
|
| 437 |
+
"์ด์ ์ค์์ค, ์๋.<br>
|
| 438 |
+
๊ทธ ์ , ๋ฌด๋น์ด ๋ด๋๋ฆฌ๋ฆฌ๋ค."<br>
|
| 439 |
+
<span class="hero-quote">โ AI ๋ฌด๋น 5๋ช
์ด ํฉ์ฌํ์ฌ ์ ๊ด๋ฅผ ํ๋๋ค</span>
|
| 440 |
+
</div>
|
| 441 |
+
</div>
|
| 442 |
+
""")
|
| 443 |
+
start_btn = gr.Button("๐ฎ ์ ๊ฒฌ์ ๋ณด๊ธฐ", elem_classes="primary-btn")
|
| 444 |
+
info_from_home = gr.Button("๐ ์ ์ด๋?", elem_classes="secondary-btn")
|
| 445 |
+
|
| 446 |
+
# =============== ํ๋ฉด 2: ์ด์ ํ์ธ (๋ฌธ์ง) ===============
|
| 447 |
+
with gr.Column(visible=False) as questionnaire_screen:
|
| 448 |
+
gr.HTML("""
|
| 449 |
+
<div class="scr-header">
|
| 450 |
+
<div class="scr-title">๐ ์ด์(้ๆธ) ํ์ธ</div>
|
| 451 |
+
<div class="scr-sub">1 / 3 ยท ์ ๊ด ํ๊ธฐ ์ ์ฌ์ฃผ ๋ฌป๊ธฐ</div>
|
| 452 |
+
</div>
|
| 453 |
+
<div class="progress-dots">
|
| 454 |
+
<div class="progress-dot active"></div>
|
| 455 |
+
<div class="progress-dot"></div>
|
| 456 |
+
<div class="progress-dot"></div>
|
| 457 |
+
</div>
|
| 458 |
+
<div class="app-card">
|
| 459 |
+
<div class="app-card-title">โ ๋ฌด๋น์ ์ฒซ ์ง๋ฌธ โ</div>
|
| 460 |
+
<div class="app-card-desc">์ฌ์ฃผ๋ฅผ ์์์ผ ์ ์ด ๋ ์ ํํ๋ค์ค. ์์งํ๊ฒ ๋ตํด์ฃผ์์ค.</div>
|
| 461 |
+
</div>
|
| 462 |
+
""")
|
| 463 |
+
with gr.Column(elem_classes="app-card"):
|
| 464 |
+
age = gr.Slider(0, 80, value=30, step=1, label="๐ ์๋ ๋์ด๋ ์ด์ฐ ๋์์ค?",
|
| 465 |
+
info="ํ์์ข
์ ๋์ด๊ฐ ๋ค์๋ก ๋ฐ์ ๋น๋๊ฐ ๋์์ง๋ค์ค")
|
| 466 |
+
family = gr.Radio(["์๋์ค", "์"], value="์๋์ค",
|
| 467 |
+
label="๐จโ๐ฉโ๐ง ์ง์์ ํ์์ข
๊ฐ์ง ๋ถ์ด ์์?",
|
| 468 |
+
info="๋ถ๋ชจยทํ์ ๊ธฐ์ค")
|
| 469 |
+
mole_change = gr.Radio(["์๋์ค", "์"], value="์๋์ค",
|
| 470 |
+
label="๐ ๊ทธ ์ ์ด ์ต๊ทผ ๋ณ(่ฎ)ํ์?",
|
| 471 |
+
info="ํฌ๊ธฐยท์ยท๋ชจ์ ๋ณํ (์ต๊ทผ 6๊ฐ์ ์ด๋ด)")
|
| 472 |
+
with gr.Row(elem_classes="row-buttons"):
|
| 473 |
+
q_back = gr.Button("โ ๋์๊ฐ๊ธฐ", elem_classes="secondary-btn")
|
| 474 |
+
q_next = gr.Button("์ ๋ณด์ฌ์ฃผ์ค โ", elem_classes="primary-btn")
|
| 475 |
+
|
| 476 |
+
# =============== ํ๋ฉด 3: ์ ์ฌ์ง ===============
|
| 477 |
+
with gr.Column(visible=False) as photo_screen:
|
| 478 |
+
gr.HTML("""
|
| 479 |
+
<div class="scr-header">
|
| 480 |
+
<div class="scr-title">๐ธ ์ (้ป) ์ฌ์ง</div>
|
| 481 |
+
<div class="scr-sub">2 / 3 ยท ๋ฌด๋น์๊ฒ ์ ์ ๋ณด์ฌ์ฃผ์ค</div>
|
| 482 |
+
</div>
|
| 483 |
+
<div class="progress-dots">
|
| 484 |
+
<div class="progress-dot active"></div>
|
| 485 |
+
<div class="progress-dot active"></div>
|
| 486 |
+
<div class="progress-dot"></div>
|
| 487 |
+
</div>
|
| 488 |
+
<div class="app-card">
|
| 489 |
+
<div class="app-card-title">โ ์ ์ ๊ฐ๊น์ด ๋น์ถ์์ค โ</div>
|
| 490 |
+
<div class="app-card-desc">๋ฐ๏ฟฝ๏ฟฝ๏ฟฝ ๊ณณ์์, ์ ์ด ํ๋ฉด์ ๊ฐ๋ ์ฐจ๊ฒ ์ฐ์ด์ฃผ์์ค. ์นด๋ฉ๋ผ๋ก ์ฐ๊ฑฐ๋ ๊ฐค๋ฌ๋ฆฌ์์ ๊ณจ๋ผ๋ ์ข์.</div>
|
| 491 |
+
</div>
|
| 492 |
+
""")
|
| 493 |
+
image_input = gr.Image(sources=["upload", "webcam"], type="numpy",
|
| 494 |
+
label="", show_label=False, height=300)
|
| 495 |
+
with gr.Row(elem_classes="row-buttons"):
|
| 496 |
+
p_back = gr.Button("โ ๋์๊ฐ๊ธฐ", elem_classes="secondary-btn")
|
| 497 |
+
analyze = gr.Button("๐ฎ ์ ๊ด ๋ณด๊ธฐ", elem_classes="primary-btn")
|
| 498 |
+
|
| 499 |
+
# =============== ํ๋ฉด 4: ์ ๊ด (๊ฒฐ๊ณผ) ===============
|
| 500 |
+
with gr.Column(visible=False) as result_screen:
|
| 501 |
+
gr.HTML("""
|
| 502 |
+
<div class="scr-header">
|
| 503 |
+
<div class="scr-title">๐ฎ ์ค๋์ ์ ๊ด</div>
|
| 504 |
+
<div class="scr-sub">3 / 3 ยท ๋ฌด๋น์ ํ์ด</div>
|
| 505 |
+
</div>
|
| 506 |
+
<div class="progress-dots">
|
| 507 |
+
<div class="progress-dot active"></div>
|
| 508 |
+
<div class="progress-dot active"></div>
|
| 509 |
+
<div class="progress-dot active"></div>
|
| 510 |
+
</div>
|
| 511 |
+
""")
|
| 512 |
+
with gr.Column(elem_classes="app-card"):
|
| 513 |
+
result_label = gr.Label(label="", show_label=False, num_top_classes=2)
|
| 514 |
+
result_detail = gr.HTML()
|
| 515 |
+
gr.HTML('<div class="disclaimer">์ฐธ๊ณ ์ฉ ์ ๊ด์ผ ๋ฟ, ์ง์ง ์ง๋จ์ ํผ๋ถ๊ณผ ์ ๋ฌธ์์๊ฒ ๋ฐ์ผ์์ค.</div>')
|
| 516 |
+
with gr.Row(elem_classes="row-buttons"):
|
| 517 |
+
again_btn = gr.Button("๐ ๋ค์ ์ ์น๊ธฐ", elem_classes="primary-btn")
|
| 518 |
+
info_from_result = gr.Button("๐ ์ ์ด๋?", elem_classes="secondary-btn")
|
| 519 |
+
|
| 520 |
+
# =============== ํ๋ฉด 5: ์ ์ด๋ (์ ๋ณด) ===============
|
| 521 |
+
with gr.Column(visible=False) as info_screen:
|
| 522 |
+
gr.HTML("""
|
| 523 |
+
<div class="scr-header">
|
| 524 |
+
<div class="scr-title">๐ ์ ์ด๋ ๋ฌด์์ด์ค?</div>
|
| 525 |
+
<div class="scr-sub">์์๋๋ฉด ์ข์ ์ํ ์ด์ผ๊ธฐ</div>
|
| 526 |
+
</div>
|
| 527 |
+
""")
|
| 528 |
+
with gr.Column(elem_classes="app-card"):
|
| 529 |
+
with gr.Accordion("๐ ํ์์ข
์ด๋?", open=True):
|
| 530 |
+
gr.Markdown(
|
| 531 |
+
"**ํ์์ข
(Melanoma)** ์ ํผ๋ถ์ ๋ฉ๋ผ๋
ธ์ฌ์ดํธ(์์ ์ธํฌ)๊ฐ "
|
| 532 |
+
"์
์ฑ ๋ณํํ ์์ด์ค. ๋ค๋ฅธ ํผ๋ถ์๋ณด๋ค ๋ฐ์ ๋น๋๋ ๋ฎ์ง๋ง "
|
| 533 |
+
"**์ ์ด๊ฐ ๋นจ๋ผ ๊ฐ์ฅ ๊ณต๊ฒฉ์ **์ด๋ผ ์กฐ๊ธฐ ๋ฐ๊ฒฌ์ด ์ค์ํ๋ค์ค."
|
| 534 |
+
)
|
| 535 |
+
with gr.Accordion("๐ ABCDE ๊ท์น โ ์๊ฐ ๊ฒ์ง"):
|
| 536 |
+
gr.Markdown(
|
| 537 |
+
"- **A**symmetry โ ๋น๋์นญ\n"
|
| 538 |
+
"- **B**order โ ๋ค์ญ๋ ์ญํ ๊ฒฝ๊ณ\n"
|
| 539 |
+
"- **C**olor โ ํ ์ ์ ์ฌ๋ฌ ์\n"
|
| 540 |
+
"- **D**iameter โ 6mm ์ด์\n"
|
| 541 |
+
"- **E**volving โ ๋ณํ\n\n"
|
| 542 |
+
"์ฌ๋ฌ ํญ๋ชฉ ํด๋น ์ ํผ๋ถ๊ณผ ์ง๋ฃ๊ฐ ๋ต์ด์ธ๋ค."
|
| 543 |
+
)
|
| 544 |
+
with gr.Accordion("๐ฐ๐ท ํ๊ตญ์ธ์ด ์์์ผ ํ ๊ฒ"):
|
| 545 |
+
gr.Markdown(
|
| 546 |
+
"ํ๊ตญ์ธ ํ์์ข
์ **30~50%** ๋ **๋ง๋จํ์์ ํ์์ข
**(์๋ฐ๋ฅยท๋ฐ๋ฐ๋ฅยท์ํฑ) "
|
| 547 |
+
"ํํ๋ผ๋ค. ์์ธ์ ๊ณผ ๋ฌด๊ดํ๋ฉฐ ๋จ์ ์ ์ฒ๋ผ ๋ณด์ฌ ๋ฆ๊ฒ ๋ฐ๊ฒฌ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์."
|
| 548 |
+
)
|
| 549 |
+
with gr.Accordion("๐ฅ ๋ณ์์ ๊ฐ์ผ ํ ๋"):
|
| 550 |
+
gr.Markdown(
|
| 551 |
+
"- ์ ์ด ๋ณํ ๋ (ํฌ๊ธฐยท์ยท๋ชจ์)\n"
|
| 552 |
+
"- ์ ์์ ์ถํ\n"
|
| 553 |
+
"- ๊ฐ๋ ต๊ฑฐ๋ ํต์ฆ\n"
|
| 554 |
+
"- ์ยท๋ฐ๋ฐ๋ฅ์ ์๋ก์ด ์ \n"
|
| 555 |
+
"- ๊ฐ์กฑ ์ค ํ์์ข
ํ์ ์์\n\n"
|
| 556 |
+
"โ **ํผ๋ถ๊ณผ ์ ๋ฌธ์** ์ง๋ฃ๊ฐ ์ ๋ต์ด์ค."
|
| 557 |
+
)
|
| 558 |
+
info_back = gr.Button("โ ์ ์ง์ผ๋ก", elem_classes="secondary-btn")
|
| 559 |
+
|
| 560 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 561 |
+
# 7. ํ๋ฉด ์ ํ ๋ก์ง
|
| 562 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 563 |
+
all_screens = [home_screen, questionnaire_screen, photo_screen,
|
| 564 |
+
result_screen, info_screen]
|
| 565 |
+
|
| 566 |
+
def goto(screen_name):
|
| 567 |
+
order = ["home", "questionnaire", "photo", "result", "info"]
|
| 568 |
+
return [gr.update(visible=(s == screen_name)) for s in order]
|
| 569 |
+
|
| 570 |
+
start_btn.click( fn=lambda: goto("questionnaire"), outputs=all_screens)
|
| 571 |
+
info_from_home.click( fn=lambda: goto("info"), outputs=all_screens)
|
| 572 |
+
q_back.click( fn=lambda: goto("home"), outputs=all_screens)
|
| 573 |
+
q_next.click( fn=lambda: goto("photo"), outputs=all_screens)
|
| 574 |
+
p_back.click( fn=lambda: goto("questionnaire"), outputs=all_screens)
|
| 575 |
+
again_btn.click( fn=lambda: goto("home"), outputs=all_screens)
|
| 576 |
+
info_from_result.click(fn=lambda: goto("info"), outputs=all_screens)
|
| 577 |
+
info_back.click( fn=lambda: goto("home"), outputs=all_screens)
|
| 578 |
+
|
| 579 |
+
# AI ์ ๊ด โ ๊ฒฐ๊ณผ ํ๋ฉด
|
| 580 |
+
def analyze_and_show(image, age_v, family_v, mole_change_v):
|
| 581 |
+
if image is None:
|
| 582 |
+
return goto("photo") + [None, "<div class='disclaimer'>๋จผ์ ์ ์ฌ์ง์ ๋ณด์ฌ์ฃผ์ค</div>"]
|
| 583 |
+
label, detail = predict_with_questionnaire(image, age_v, family_v, mole_change_v)
|
| 584 |
+
return goto("result") + [label, detail]
|
| 585 |
+
|
| 586 |
+
analyze.click(
|
| 587 |
+
fn=analyze_and_show,
|
| 588 |
+
inputs=[image_input, age, family, mole_change],
|
| 589 |
+
outputs=all_screens + [result_label, result_detail],
|
| 590 |
+
)
|
| 591 |
+
|
| 592 |
+
demo.launch()
|