"""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( """
Vision Lab x Hugging Face Space
上传一张图,直接对比 FCN、R-CNN 家族和 Mask R-CNN
这个页面把语义分割、目标检测、实例分割和综合性能对比收进同一个交互式实验站。 每个实验区都预载了示例图,你可以直接点,也可以现场换成自己的图片,看结果、看速度、看差异。
上传图片 / 剪贴板 / 摄像头
在线推理 + 可视化结果
综合性能对比页
PDF 实验报告导出
FCN 语义分割

整图像素级预测,适合看前景覆盖和语义区域叠加。

R-CNN 家族

同一张图里对比 proposal、共享特征和 RPN 的差别。

Mask R-CNN

除了框和类别,还能直接看到实例级 mask 结果。

性能总览

把前面几块方法放到一张总览图里,同时比较速度和公开指标。

""" ) with gr.Row(): with gr.Column(scale=3): gr.HTML( """
Quick Start
现在可以不用先研究页面结构了,直接从任意标签页开始。前面三个实验区负责“看结果”,最后一个实验区负责“把前面几种方法拉到一起比较”。
01 直接试跑每个标签页都预载了示例图,点击按钮就能出结果。
02 换你自己的图支持上传、剪贴板和摄像头输入,适合现场演示。
03 看综合比较在性能页同时比较五种方法的输出、公开指标和当前图片速度。
""" ) 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()