"""
app.py - Huhb3D Synthetic Data Generator (HuggingFace Space Demo)
=================================================================
Demo-only version for HuggingFace Spaces.
Shows pre-generated sample data: RGB, Mask, Depth, 6DoF, BOP format.
Full data generation requires local deployment with C++ engine compiled.
"""
import streamlit as st
import streamlit.components.v1 as components
import json
import base64
from pathlib import Path
DEMO_DIR = Path(__file__).parent / "demo_data"
GITHUB_URL = "https://github.com/AIminminAI/Huhb3D-Viewer"
GITEE_URL = "https://gitee.com/aiminminai/Huhb3D-Viewer"
AFDIAN_URL = "https://afdian.com/a/aiminhu"
@st.cache_data(show_spinner=False)
def load_json(path: Path):
if not path.exists():
return None
return json.loads(path.read_text(encoding="utf-8"))
@st.cache_data(show_spinner=False)
def load_text(path: Path):
if not path.exists():
return None
return path.read_text(encoding="utf-8")
@st.cache_data(show_spinner=False)
def load_demo_images():
rgb_dir = DEMO_DIR / "rgb"
mask_dir = DEMO_DIR / "mask"
depth_dir = DEMO_DIR / "depth"
rgb_files = sorted(rgb_dir.glob("*.png")) if rgb_dir.exists() else []
mask_files = sorted(mask_dir.glob("*.png")) if mask_dir.exists() else []
depth_files = sorted(depth_dir.glob("*.png")) if depth_dir.exists() else []
return rgb_files, mask_files, depth_files
@st.cache_data(show_spinner=False)
def image_to_base64(path_str: str):
try:
with open(path_str, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
except (FileNotFoundError, IOError):
return None
@st.cache_data(show_spinner=False)
def build_full_viewer_html(rgb_files, mask_files, depth_files, manifest, has_6dof):
m = manifest or {}
rgb_val = m.get("rgb_count", len(rgb_files))
mask_val = m.get("mask_count", len(mask_files))
depth_val = m.get("depth_count", len(depth_files))
dof_val = "✅" if has_6dof else "❌"
num_images = min(len(rgb_files), len(mask_files))
if num_images == 0:
return """
暂无 Demo 数据。请在本地运行完整版。
"""
rgb_b64_list = []
for f in rgb_files[:num_images]:
b = image_to_base64(str(f))
rgb_b64_list.append(b if b else "")
mask_b64_list = []
for f in mask_files[:num_images]:
b = image_to_base64(str(f))
mask_b64_list.append(b if b else "")
depth_b64_list = []
for f in depth_files[:num_images]:
b = image_to_base64(str(f))
depth_b64_list.append(b if b else "")
has_depth = len(depth_files) > 0
js_rgb_array = "[" + ",".join(f'"{b}"' for b in rgb_b64_list) + "]"
js_mask_array = "[" + ",".join(f'"{b}"' for b in mask_b64_list) + "]"
js_depth_array = "[" + ",".join(f'"{b}"' for b in depth_b64_list) + "]"
return f"""
"""
def main():
st.set_page_config(
page_title="Huhb3D Synthetic Data Generator - Demo",
page_icon="🤖",
layout="wide",
)
st.title("🤖 Huhb3D Synthetic Data Generator")
st.markdown("**面向机器人视觉训练的合成数据生成器** — Demo 展示")
st.warning(
"⚠️ **在线 Demo 模式** — 当前展示预生成的样例数据。"
"要使用自己的 CAD 模型生成数据,请在本地部署并编译 C++ 渲染引擎。\n\n"
f"📦 GitHub: [{GITHUB_URL}]({GITHUB_URL}) | "
f"📦 Gitee: [{GITEE_URL}]({GITEE_URL})"
)
rgb_files, mask_files, depth_files = load_demo_images()
manifest = load_json(DEMO_DIR / "manifest.json")
scene_camera = load_json(DEMO_DIR / "scene_camera.json")
scene_gt = load_json(DEMO_DIR / "scene_gt.json")
gt_6dof = load_json(DEMO_DIR / "gt_6dof.json")
label_legend = load_text(DEMO_DIR / "label_legend.txt")
camera_poses = load_json(DEMO_DIR / "camera_poses.json")
st.markdown("---")
viewer_html = build_full_viewer_html(
rgb_files, mask_files, depth_files, manifest, gt_6dof is not None
)
components.html(viewer_html, height=560)
if label_legend:
with st.expander("🏷️ 语义标签分类", expanded=False):
st.text(label_legend)
st.markdown("---")
st.subheader("📐 6DoF & BOP 格式数据")
tab_6dof, tab_scene_cam, tab_scene_gt, tab_poses = st.tabs(
["🎯 6DoF Ground Truth", "📷 scene_camera.json", "📋 scene_gt.json", "🎥 camera_poses.json"]
)
with tab_6dof:
if gt_6dof:
st.json(gt_6dof)
else:
st.info("6DoF 数据需在本地编译 C++ 引擎后生成")
with tab_scene_cam:
if scene_camera:
st.json(scene_camera)
else:
st.info("BOP scene_camera 数据需在本地生成")
with tab_scene_gt:
if scene_gt:
st.json(scene_gt)
else:
st.info("BOP scene_gt 数据需在本地生成")
with tab_poses:
if camera_poses:
st.json(camera_poses)
else:
st.info("camera_poses 数据需在本地生成")
st.markdown("---")
st.subheader("📋 完整数据格式说明")
st.markdown("""
每次生成输出一个 ZIP 包,结构如下:
```
run_/
├── rgb/ # RGB 渲染图 (PNG)
│ ├── view_000.png
│ ├── view_001.png
│ └── ...
├── mask/ # 语义分割 Mask (PNG)
│ ├── view_000_mask.png
│ └── ...
├── depth/ # 深度图 (PNG可视化 + RAW float)
│ ├── view_000_depth.png
│ └── view_000_depth.raw
├── camera_poses.json # 6DoF 相机位姿
├── scene_camera.json # BOP 格式相机内参
├── scene_gt.json # BOP 格式 6DoF 位姿
├── gt_6dof.json # 完整 6DoF + 四元数
├── label_legend.txt # 语义标签对照表
└── manifest.json # 数据集元信息
```
""")
st.markdown("---")
st.subheader("🔧 本地部署(解锁完整功能)")
st.markdown(f"""
**要使用自己的 CAD 模型生成数据,请按以下步骤本地部署:**
1. 克隆仓库:`git clone {GITHUB_URL}`
2. 安装 Python 依赖:`pip install streamlit Pillow numpy opencv-python`
3. 编译 C++ 渲染引擎:需要 Visual Studio 2019+ (Windows) 或 GCC (Linux)
4. 启动 Web UI:`streamlit run app.py --server.port 8501`
**Docker 部署(最省心):**
```bash
docker build -t huhb3d-synthetic .
docker run -p 7860:7860 huhb3d-synthetic
```
详见 [GitHub README]({GITHUB_URL}) 获取完整部署指南。
""")
st.markdown("---")
st.caption(f"Huhb3D Synthetic Data Generator v2.0 | AGPL-3.0 License | [☕ 爱发电]({AFDIAN_URL})")
if __name__ == "__main__":
main()