Alicesong's picture
Refresh UI and expand comparison dashboard
616789d verified
Raw
History Blame Contribute Delete
18.8 kB
"""Interactive Hugging Face Space for detection and segmentation demos."""
from __future__ import annotations
import argparse
import os
import gradio as gr
from vibe_ml_lab.detection_segmentation_lab import (
FCN_FOCUS_CHOICES,
PROMPT_TEXT,
build_benchmark_sources_markdown,
build_delivery_markdown,
build_interface_mock,
generate_report_ui,
load_demo_image,
run_comparison_ui,
run_fcn_demo_ui,
run_mask_rcnn_demo_ui,
run_rcnn_family_ui,
)
CUSTOM_CSS = """
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Fraunces:opsz,wght@9..144,600;9..144,700&display=swap');
:root {
--paper: #f7f1e3;
--paper-2: #fffaf1;
--ink: #182333;
--muted: #536173;
--line: rgba(24, 35, 51, 0.12);
--coral: #f26b5b;
--sun: #f5b94c;
--mint: #44b89d;
--sky: #4e9cd3;
--berry: #c74c76;
--navy: #173047;
--shadow: 0 22px 70px rgba(24, 35, 51, 0.12);
}
body, .gradio-container {
background:
radial-gradient(circle at 8% 12%, rgba(242, 107, 91, 0.16), transparent 18%),
radial-gradient(circle at 86% 10%, rgba(78, 156, 211, 0.14), transparent 22%),
radial-gradient(circle at 80% 78%, rgba(68, 184, 157, 0.12), transparent 20%),
linear-gradient(180deg, #efe7d7 0%, #f7f1e3 45%, #fffaf1 100%);
color: var(--ink);
font-family: 'Space Grotesk', 'Segoe UI', sans-serif !important;
}
.gradio-container { max-width: 1440px !important; }
.app-shell {
border: 1px solid rgba(24, 35, 51, 0.08);
border-radius: 36px;
padding: 30px;
background:
linear-gradient(135deg, rgba(255,255,255,0.90), rgba(255,249,239,0.84)),
linear-gradient(180deg, rgba(255,255,255,0.40), rgba(255,255,255,0.08));
box-shadow: var(--shadow);
backdrop-filter: blur(6px);
margin-bottom: 18px;
}
.hero-layout {
display: grid;
grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.9fr);
gap: 18px;
align-items: stretch;
}
.hero-main {
padding: 6px 4px 6px 4px;
}
.eyebrow {
display: inline-flex;
gap: 10px;
align-items: center;
border-radius: 999px;
padding: 8px 14px;
border: 1px solid rgba(24, 35, 51, 0.08);
background: rgba(255, 255, 255, 0.72);
font-size: 0.82rem;
text-transform: uppercase;
letter-spacing: 0.14em;
font-weight: 700;
}
.hero-title {
font-family: 'Fraunces', Georgia, serif;
font-size: clamp(2.6rem, 4.4vw, 4.4rem);
line-height: 0.92;
margin: 14px 0 16px 0;
max-width: 760px;
}
.hero-copy {
max-width: 760px;
color: var(--muted);
font-size: 1.05rem;
line-height: 1.82;
}
.hero-badges {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 20px;
}
.badge-chip {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border-radius: 999px;
border: 1px solid rgba(24, 35, 51, 0.08);
background: rgba(255, 255, 255, 0.74);
color: var(--ink);
font-size: 0.92rem;
font-weight: 700;
}
.hero-side {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.spot-card {
min-height: 138px;
border-radius: 26px;
padding: 18px;
color: #ffffff;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.18);
}
.spot-card strong {
display: block;
font-family: 'Fraunces', Georgia, serif;
font-size: 1.28rem;
margin-bottom: 8px;
}
.spot-card p {
margin: 0;
line-height: 1.66;
font-size: 0.94rem;
}
.spot-card.coral { background: linear-gradient(145deg, #f26b5b, #db5948); }
.spot-card.sun { background: linear-gradient(145deg, #f5b94c, #d99429); color: #2b2b2b; }
.spot-card.sky { background: linear-gradient(145deg, #4e9cd3, #2d78ad); }
.spot-card.mint { background: linear-gradient(145deg, #44b89d, #2d8c76); }
.guide-shell {
border-radius: 30px;
padding: 24px;
border: 1px solid rgba(24, 35, 51, 0.08);
background: linear-gradient(180deg, rgba(255,255,255,0.90), rgba(250,244,232,0.94));
box-shadow: 0 16px 40px rgba(24, 35, 51, 0.06);
}
.guide-title {
font-family: 'Fraunces', Georgia, serif;
font-size: 2rem;
margin-bottom: 8px;
}
.guide-copy {
color: var(--muted);
line-height: 1.74;
margin-bottom: 18px;
}
.guide-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.guide-card {
border-radius: 22px;
padding: 16px;
background: rgba(255,255,255,0.82);
border: 1px solid rgba(24, 35, 51, 0.08);
}
.guide-card strong {
display: block;
font-size: 1rem;
margin-bottom: 6px;
}
.guide-card span {
color: var(--muted);
line-height: 1.6;
font-size: 0.92rem;
}
.note-card, .metric-card {
border-radius: 24px;
border: 1px solid rgba(24, 35, 51, 0.10);
background: rgba(255, 255, 255, 0.78);
box-shadow: 0 12px 28px rgba(24, 35, 51, 0.06);
}
.note-card { padding: 18px 20px; }
.metric-card {
padding: 12px 16px;
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(249,243,232,0.88));
}
.metric-card p { margin: 0; }
.tabs button {
font-weight: 700 !important;
border-radius: 999px !important;
}
button.primary {
background: linear-gradient(135deg, var(--coral), var(--sun)) !important;
border: none !important;
box-shadow: 0 8px 20px rgba(242, 107, 91, 0.25) !important;
}
.subtle {
color: var(--muted);
font-weight: 700;
}
.mock-wrap {
border-radius: 30px;
overflow: hidden;
border: 1px solid rgba(24, 35, 51, 0.08);
box-shadow: 0 18px 44px rgba(24, 35, 51, 0.08);
}
@media (max-width: 980px) {
.hero-layout { grid-template-columns: 1fr; }
.guide-grid, .hero-side { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
.app-shell { padding: 22px; border-radius: 26px; }
.guide-grid, .hero-side { grid-template-columns: repeat(1, minmax(0, 1fr)); }
}
"""
APP_THEME = gr.themes.Base(
primary_hue="orange",
secondary_hue="cyan",
neutral_hue="stone",
radius_size="lg",
)
def build_interface() -> gr.Blocks:
demo_image = load_demo_image()
with gr.Blocks(title="Detection + Segmentation Studio") as demo:
gr.HTML(
"""
<div class="app-shell">
<div class="hero-layout">
<div class="hero-main">
<div class="eyebrow">Vision Lab x Hugging Face Space</div>
<div class="hero-title">上传一张图,直接对比 FCN、R-CNN 家族和 Mask R-CNN</div>
<div class="hero-copy">
这个页面把语义分割、目标检测、实例分割和综合性能对比收进同一个交互式实验站。
每个实验区都预载了示例图,你可以直接点,也可以现场换成自己的图片,看结果、看速度、看差异。
</div>
<div class="hero-badges">
<div class="badge-chip">上传图片 / 剪贴板 / 摄像头</div>
<div class="badge-chip">在线推理 + 可视化结果</div>
<div class="badge-chip">综合性能对比页</div>
<div class="badge-chip">PDF 实验报告导出</div>
</div>
</div>
<div class="hero-side">
<div class="spot-card coral"><strong>FCN 语义分割</strong><p>整图像素级预测,适合看前景覆盖和语义区域叠加。</p></div>
<div class="spot-card sun"><strong>R-CNN 家族</strong><p>同一张图里对比 proposal、共享特征和 RPN 的差别。</p></div>
<div class="spot-card sky"><strong>Mask R-CNN</strong><p>除了框和类别,还能直接看到实例级 mask 结果。</p></div>
<div class="spot-card mint"><strong>性能总览</strong><p>把前面几块方法放到一张总览图里,同时比较速度和公开指标。</p></div>
</div>
</div>
</div>
"""
)
with gr.Row():
with gr.Column(scale=3):
gr.HTML(
"""
<div class="guide-shell">
<div class="guide-title">Quick Start</div>
<div class="guide-copy">
现在可以不用先研究页面结构了,直接从任意标签页开始。前面三个实验区负责“看结果”,最后一个实验区负责“把前面几种方法拉到一起比较”。
</div>
<div class="guide-grid">
<div class="guide-card"><strong>01 直接试跑</strong><span>每个标签页都预载了示例图,点击按钮就能出结果。</span></div>
<div class="guide-card"><strong>02 换你自己的图</strong><span>支持上传、剪贴板和摄像头输入,适合现场演示。</span></div>
<div class="guide-card"><strong>03 看综合比较</strong><span>在性能页同时比较五种方法的输出、公开指标和当前图片速度。</span></div>
</div>
</div>
"""
)
with gr.Column(scale=2):
gr.Image(
value=build_interface_mock(),
label="界面预览",
type="numpy",
elem_classes=["mock-wrap"],
)
with gr.Tabs():
with gr.Tab("FCN 语义分割"):
gr.Markdown(
"适合看整图像素级预测、语义区域叠加,以及不同类别在当前图片中的覆盖比例。",
elem_classes=["note-card"],
)
gr.Markdown("已预载示例图,可以直接点击运行;也可以换成你自己的图片。", elem_classes=["subtle"])
with gr.Row():
with gr.Column(scale=1):
fcn_input = gr.Image(
value=demo_image,
label="上传图片",
type="numpy",
sources=["upload", "clipboard", "webcam"],
)
fcn_alpha = gr.Slider(0.15, 0.85, value=0.50, step=0.05, label="Overlay 透明度")
fcn_focus = gr.Dropdown(choices=FCN_FOCUS_CHOICES, value=FCN_FOCUS_CHOICES[0], label="重点查看类别")
fcn_ratio = gr.Slider(0.0, 0.15, value=0.01, step=0.01, label="最小像素占比过滤")
fcn_button = gr.Button("运行 FCN 分割", variant="primary")
with gr.Column(scale=2):
fcn_summary = gr.Markdown(elem_classes=["metric-card"])
with gr.Row():
fcn_overlay = gr.Image(label="分割叠加结果", type="numpy")
fcn_legend = gr.Image(label="类别图例与分布", type="numpy")
fcn_table = gr.Dataframe(headers=["Class", "Pixel count", "Ratio", "Highlighted"], wrap=True, interactive=False)
fcn_button.click(
run_fcn_demo_ui,
[fcn_input, fcn_alpha, fcn_focus, fcn_ratio],
[fcn_summary, fcn_overlay, fcn_legend, fcn_table],
)
with gr.Tab("R-CNN / Fast / Faster R-CNN"):
gr.Markdown(
"这里把 `R-CNN`、`Fast R-CNN`、`Faster R-CNN` 放到同一个实验区里,方便你在同一张图上观察 proposal、共享特征和 RPN 的差别。",
elem_classes=["note-card"],
)
gr.Markdown("已预载示例图,可以直接点击运行;也可以换成你自己的图片。", elem_classes=["subtle"])
with gr.Row():
with gr.Column(scale=1):
det_input = gr.Image(
value=demo_image,
label="上传图片",
type="numpy",
sources=["upload", "clipboard", "webcam"],
)
det_method = gr.Radio(["R-CNN", "Fast R-CNN", "Faster R-CNN"], value="Faster R-CNN", label="选择方法")
det_proposals = gr.Slider(8, 40, value=18, step=2, label="候选区域数量")
det_threshold = gr.Slider(0.15, 0.85, value=0.45, step=0.05, label="检测分数阈值")
det_button = gr.Button("运行检测实验", variant="primary")
with gr.Column(scale=2):
det_summary = gr.Markdown(elem_classes=["metric-card"])
with gr.Row():
det_main = gr.Image(label="主结果图", type="numpy")
det_aux = gr.Image(label="流程 / 候选框辅助图", type="numpy")
det_table = gr.Dataframe(headers=["Item", "Label", "Score", "IoU/ref", "Area ratio"], wrap=True, interactive=False)
det_button.click(
run_rcnn_family_ui,
[det_input, det_method, det_proposals, det_threshold],
[det_summary, det_main, det_aux, det_table],
)
with gr.Tab("Mask R-CNN 实例分割"):
gr.Markdown(
"这个模块会直接给出实例级分割结果,展示每个实例的类别、分数、边框和 mask 轮廓。",
elem_classes=["note-card"],
)
gr.Markdown("已预载示例图,可以直接点击运行;也可以换成你自己的图片。", elem_classes=["subtle"])
with gr.Row():
with gr.Column(scale=1):
mask_input = gr.Image(
value=demo_image,
label="上传图片",
type="numpy",
sources=["upload", "clipboard", "webcam"],
)
mask_threshold = gr.Slider(0.20, 0.90, value=0.55, step=0.05, label="实例分数阈值")
mask_alpha = gr.Slider(0.15, 0.85, value=0.48, step=0.05, label="Mask 透明度")
mask_count = gr.Slider(1, 10, value=6, step=1, label="最多展示实例数")
mask_button = gr.Button("运行 Mask R-CNN", variant="primary")
with gr.Column(scale=2):
mask_summary = gr.Markdown(elem_classes=["metric-card"])
with gr.Row():
mask_overlay = gr.Image(label="实例分割叠加结果", type="numpy")
mask_gallery = gr.Image(label="实例裁剪与 mask 预览", type="numpy")
mask_table = gr.Dataframe(headers=["Instance", "Class", "Score", "Mask area", "BBox"], wrap=True, interactive=False)
mask_button.click(
run_mask_rcnn_demo_ui,
[mask_input, mask_threshold, mask_alpha, mask_count],
[mask_summary, mask_overlay, mask_gallery, mask_table],
)
with gr.Tab("性能对比 + 实验报告"):
gr.Markdown(
"这一页会把前面几块方法放到同一张总览图里,同时比较公开指标、结构差异,以及当前图片下的速度表现。",
elem_classes=["note-card"],
)
gr.Markdown("已预载示例图,可以直接点击生成性能对比;也可以上传你的图片重新测。", elem_classes=["subtle"])
with gr.Row():
with gr.Column(scale=1):
cmp_input = gr.Image(
value=demo_image,
label="上传图片(用于实时 benchmark,可选)",
type="numpy",
sources=["upload", "clipboard", "webcam"],
)
cmp_threshold = gr.Slider(0.15, 0.85, value=0.45, step=0.05, label="统一阈值")
cmp_proposals = gr.Slider(8, 40, value=18, step=2, label="R-CNN 模拟候选框数量")
cmp_button = gr.Button("生成性能对比", variant="primary")
with gr.Column(scale=2):
cmp_summary = gr.Markdown(elem_classes=["metric-card"])
with gr.Row():
cmp_plot = gr.Image(label="五种方法结果总览", type="numpy")
cmp_runtime = gr.Image(label="公开指标 + 当前图片速度", type="numpy")
cmp_table = gr.Dataframe(
headers=["Method", "Task", "Current image output", "Published metric", "Live runtime", "Runtime type", "Structure clue"],
wrap=True,
interactive=False,
)
cmp_button.click(
run_comparison_ui,
[cmp_input, cmp_threshold, cmp_proposals],
[cmp_summary, cmp_plot, cmp_runtime, cmp_table],
)
gr.Markdown(build_benchmark_sources_markdown(), elem_classes=["note-card"])
gr.Markdown("### Prompt 文本", elem_classes=["subtle"])
gr.Textbox(value=PROMPT_TEXT, label="本次 Vibe Coding 设计 Prompt", lines=9, max_lines=12)
gr.Markdown(build_delivery_markdown(), elem_classes=["note-card"])
with gr.Row():
report_button = gr.Button("生成 PDF 实验报告", variant="primary")
report_summary = gr.Markdown(elem_classes=["metric-card"])
report_file = gr.File(label="下载生成的 PDF / Prompt")
report_button.click(generate_report_ui, outputs=[report_summary, report_file])
return demo
def main() -> None:
parser = argparse.ArgumentParser(description="Launch the Detection + Segmentation Studio app.")
parser.add_argument("--share", action="store_true", help="Create a temporary Gradio sharing link.")
parser.add_argument("--port", type=int, default=int(os.environ.get("PORT", "7860")), help="Server port.")
parser.add_argument("--no-browser", action="store_true", help="Do not open a browser window.")
args = parser.parse_args()
demo = build_interface()
demo.queue(default_concurrency_limit=2)
demo.launch(
server_name=os.environ.get("HOST", "0.0.0.0"),
server_port=args.port,
share=args.share,
inbrowser=not args.no_browser,
css=CUSTOM_CSS,
theme=APP_THEME,
show_error=True,
)
if __name__ == "__main__":
main()