DarthSoluke commited on
Commit
aaa753d
·
verified ·
1 Parent(s): 84afe80

Add EgoRenderer synthetic data, public progress, version history and reader

Browse files
.gitattributes CHANGED
@@ -104,3 +104,6 @@ media/figures/show3d_0_0.webp filter=lfs diff=lfs merge=lfs -text
104
  media/figures/show3d_1_0.webp filter=lfs diff=lfs merge=lfs -text
105
  media/figures/taco_0_0.webp filter=lfs diff=lfs merge=lfs -text
106
  media/figures/xperience_0_0.webp filter=lfs diff=lfs merge=lfs -text
 
 
 
 
104
  media/figures/show3d_1_0.webp filter=lfs diff=lfs merge=lfs -text
105
  media/figures/taco_0_0.webp filter=lfs diff=lfs merge=lfs -text
106
  media/figures/xperience_0_0.webp filter=lfs diff=lfs merge=lfs -text
107
+ media/egorender/bimanual.png filter=lfs diff=lfs merge=lfs -text
108
+ media/egorender/mirror.png filter=lfs diff=lfs merge=lfs -text
109
+ media/egorender/multiple.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -37,6 +37,31 @@ pinned: false
37
  | 标注方法 | `#methods` | 8 种标注方法的流程、可信度、常见问题、SOMA 路线 |
38
  | SOMA 转换 | `#soma` | 从各类真值到 SOMA 原生 shape + pose 的路线 R0 到 R7 |
39
  | 样例素材 | `#samples` | 抽样分类、类别计数和素材覆盖 |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  ## 目录
42
 
 
37
  | 标注方法 | `#methods` | 8 种标注方法的流程、可信度、常见问题、SOMA 路线 |
38
  | SOMA 转换 | `#soma` | 从各类真值到 SOMA 原生 shape + pose 的路线 R0 到 R7 |
39
  | 样例素材 | `#samples` | 抽样分类、类别计数和素材覆盖 |
40
+ | 合成数据 | `#synthetic/progress` | EgoRenderer只读进度、队列、主机资源与明确的更新时间 |
41
+ | 合成版本 | `#synthetic/versions` | V001–V006特性、V006关键修订、可搜索完整执行登记簿 |
42
+ | 数据读取 | `#synthetic/reading` | 双深度、相机、2-bit可见分割、原生手参数和历史路径读取 |
43
+ | 合成样例 | `#synthetic/samples` | 三种Hard Case的实际历史预览与标签语义 |
44
+
45
+ ## EgoRenderer进度与文档
46
+
47
+ `data/egorender.json`保存审定的版本/数据语义;`data/egorender-registry.json`
48
+ 仅包含执行登记簿的公开字段,不能把方法状态当作当前队列状态。
49
+ `downloads/egorender_reader.py`是只依赖NumPy的逐帧读取器,缺少任一深度
50
+ 通道时明确报错,不伪造GT。本文档不分发原始数据、模型权重或整套合成包。
51
+
52
+ 公共进度来自独立只读数据仓库`DarthSoluke/EgoRendererStatus/status.json`。
53
+ 页面每60秒读取;超过3分钟没有更新就标记过期。发布器只读取当前SQLite、
54
+ 少量控制记录和主机统计,不提供任务启动、停止、删除等API。
55
+ 公开快照不含主机地址、PID、SSH凭据、内部文件路径或原始错误堆栈。
56
+
57
+ 内部工作区的`tools/publish_progress.py`运行在操作员本机,复用项目唯一SSH master,
58
+ Hugging Face凭据只从本机授权缓存读取,不传到PRO或浏览器。
59
+ 一次前台快照用`--once`;有限后台服务用`--background --hours 168 --interval 60`。
60
+ 它不创建cron或聊天自动任务;本机睡眠/断线/服务结束后,网站保留旧快照并显示过期。
61
+ 发布器有singleton锁,已有服务运行时不要再启动第二个。
62
+
63
+ 采集、发布、登记簿导出和内部测试脚本保留在团队BEDLAM2工作区,不随此公开Space部署。
64
+ 公开Space只包含静态页面、脱敏摘要、样例和独立读取器。页面源码更新后运行`tools/build.py`。
65
 
66
  ## 目录
67
 
data/egorender-progress.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"schema": "egorenderer.public_progress.v1", "collected_at": 1793560254.9093049, "refresh_seconds": 60, "dataset_release": "V006", "execution_revision": "350", "queue_alive": true, "admission_held": false, "counts": {"complete": 153, "running": 9, "ready": 735, "failed": 6, "total": 903}, "scope": {"sources": 301, "source_frames": 218273, "variants": 3, "expected_variant_frames": 654819}, "coverage": {"completed_variant_frames": 112294, "sources_with_any_variant": 151, "sources_with_all_variants": 1}, "throughput": {"packages_last_hour": 14, "queued_eta_hours": 53.1}, "training_ready": null, "known_gt_missing": [{"source": "arctic_s01_capsulemachine_grab_01", "variant": 1}, {"source": "arctic_s09_laptop_use_03", "variant": 1}], "auxiliary_fits": "PENDING_UPSTREAM_HANDOFF", "jobs": [{"source": "arctic_s01_laptop_use_04", "variant": 1, "state": "FAILED", "frames": 1012, "phase": "FAILED", "storage": null}, {"source": "arctic_s01_mixer_grab_01", "variant": 1, "state": "FAILED", "frames": 593, "phase": "FAILED", "storage": null}, {"source": "arctic_s01_mixer_use_01", "variant": 1, "state": "FAILED", "frames": 638, "phase": "FAILED", "storage": null}, {"source": "arctic_s01_waffleiron_grab_01", "variant": 1, "state": "FAILED", "frames": 600, "phase": "FAILED", "storage": null}, {"source": "arctic_s02_waffleiron_use_02", "variant": 1, "state": "FAILED", "frames": 652, "phase": "FAILED", "storage": null}, {"source": "arctic_s05_scissors_use_02", "variant": 1, "state": "FAILED", "frames": 687, "phase": "FAILED", "storage": "SSD"}, {"source": "arctic_s06_microwave_use_02", "variant": 1, "state": "RUNNING", "frames": 965, "phase": "CYCLES_OPTIX_16SPP_GPU_OIDN", "storage": "NAS"}, {"source": "arctic_s06_mixer_grab_01", "variant": 1, "state": "RUNNING", "frames": 734, "phase": "CYCLES_OPTIX_16SPP_GPU_OIDN", "storage": "NAS"}, {"source": "arctic_s06_mixer_use_01", "variant": 1, "state": "RUNNING", "frames": 836, "phase": "VIDEO_POST_AND_DEPTH_OVERLAPPED", "storage": "SSD"}, {"source": "arctic_s06_notebook_grab_01", "variant": 1, "state": "RUNNING", "frames": 589, "phase": "VIDEO_POST_AND_DEPTH_OVERLAPPED", "storage": "NAS"}, {"source": "arctic_s06_notebook_use_01", "variant": 1, "state": "RUNNING", "frames": 681, "phase": "VIDEO_POST_AND_DEPTH_OVERLAPPED", "storage": "SSD"}, {"source": "arctic_s06_notebook_use_02", "variant": 1, "state": "RUNNING", "frames": 624, "phase": "CYCLES_OPTIX_16SPP_GPU_OIDN", "storage": "NAS"}, {"source": "arctic_s06_phone_grab_01", "variant": 1, "state": "RUNNING", "frames": 637, "phase": "VIDEO_POST_AND_DEPTH_OVERLAPPED", "storage": "SSD"}, {"source": "arctic_s06_phone_use_01", "variant": 1, "state": "RUNNING", "frames": 753, "phase": "CYCLES_OPTIX_16SPP_GPU_OIDN", "storage": "SSD"}, {"source": "arctic_s06_phone_use_02", "variant": 1, "state": "RUNNING", "frames": 701, "phase": "V5_THREE_MOUNT_INDEPENDENT_CAMERA", "storage": "SSD"}], "recent": [{"at": 1793560245.401714, "source": "arctic_s06_mixer_use_02", "variant": 1}, {"at": 1793560036.1191676, "source": "arctic_s06_laptop_use_02_retake", "variant": 1}, {"at": 1793559677.3499768, "source": "arctic_s06_espressomachine_use_04", "variant": 1}, {"at": 1793559110.8940485, "source": "arctic_s06_espressomachine_use_01", "variant": 1}, {"at": 1793559050.4718637, "source": "arctic_s06_laptop_use_01_retake", "variant": 1}, {"at": 1793559007.6832476, "source": "arctic_s06_microwave_use_01", "variant": 1}, {"at": 1793558635.1213675, "source": "arctic_s06_microwave_grab_01", "variant": 1}, {"at": 1793558334.3597572, "source": "arctic_s06_ketchup_use_02", "variant": 1}], "hardware": {"gpus": [{"index": 0, "utilization": 1, "used_mib": 7090, "total_mib": 97887}, {"index": 1, "utilization": 10, "used_mib": 10023, "total_mib": 97887}, {"index": 2, "utilization": 24, "used_mib": 16369, "total_mib": 97887}, {"index": 3, "utilization": 98, "used_mib": 15712, "total_mib": 97887}], "cpu_percent": 27.6, "cpu_logical": 72}, "storage": {"ssd_available_gib": 1242.7, "nas_available_gib": 3879.1, "ssd_budget_gib": 896.0, "ssd_reserved_gib": 790.9, "nas_active": 4, "nas_limit": 4}, "hardcases": {"complete": 15, "expected": 15, "frames": 10838, "training_ready": false}, "oakink_assets": {"counts": {"COMPLETE": 571, "FAILED": 13, "READY": 38}, "total": 622, "final_render_count": null}, "history": [{"at": 1793560194.9028523, "complete": 152}, {"at": 1793560254.9093049, "complete": 153}]}
data/egorender-registry.json ADDED
The diff for this file is too large to render. See raw diff
 
data/egorender.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "updated": "2026-11-02",
3
+ "status_url": "https://huggingface.co/datasets/DarthSoluke/EgoRendererStatus/resolve/main/status.json",
4
+ "release": "V006",
5
+ "versions": [
6
+ {"id":"001","title":"参数标注基线","state":"历史方法","scope":"GRAB个体化手表面 → 标准MANO辅助参数","features":["左右手独立;每手每序列共享10维beta,每帧48维轴角pose。","778个对应顶点均匀L2,原始逐帧translation固定;无joint loss、姿态prior或额外对齐。","沿用明确交付时间轴;拟合参数不是原始shape/pose GT。"],"read":"同时读取beta、拟合pose、原始translation和source_frame_indices;不能把拟合pose与原始pose混用。"},
7
+ {"id":"002","title":"主动双目深度试验","state":"有限试验,非全量发布","scope":"LingBot-Depth论文启发的主动双目","features":["Blender共享投影散斑,原可见光BSDF作为灰度近似,不是实测红外材料。","OpenCV StereoSGBM MODE_HH、8路径;左右一致性、散斑滤除。","试验配置768×432、基线0.05m;没有使用GT辅助填洞。"],"read":"输出为左相机轴向Z,米;0表示无效。只代表试验覆盖,不代表全部GRAB已经补齐此深度。"},
8
+ {"id":"003","title":"稀疏深度观测试验","state":"有限试验,非当前深度分支","scope":"PromptDA式稀疏锚点与插值","features":["192×256原生网格,保持比例letterbox;稀疏网格加入固定种子扰动。","RGB+XY特征空间中8近邻反距离平方插值,保留锚点。","GT只提供稀疏锚点;不是当前V006的主动双目模拟。"],"read":"区分原生低分辨率观测、上采样深度和有效掩码;不可与V002/V006的双目噪声语义混用。"},
9
+ {"id":"004","title":"左手基底修正试验","state":"单序列试验","scope":"GRAB s7/camera_browse_1左手,275个交付帧","features":["标准左MANO的shapedirs[:,0,:]在内存中翻转一次;不修改原模型文件。","右手复用V001;纯顶点L2、固定原始translation。","试验没有替换全部历史数据包,也没有重启RGB渲染。"],"read":"左手参数必须与修正后的基底配套解码;不得用未经修正的模型直接重建。"},
10
+ {"id":"005","title":"GRAB完整参数更新","state":"历史交付完成","scope":"1,314个源拟合组;2,425个含历史变体的数据包","features":["将V004左手基底修正扩展到完整已接收GRAB交付范围。","右手V001参数逐字节复用;RGB、原深度、mask、相机、接触标注沿用既有渲染。","REPLACEMENT_COMPLETE指参数附件替换完成,不是一次全新的像素渲染。","1,314源组与2,425包是不同计数口径;后续V006 GRAB新模拟深度尚未据此宣称完成。"],"read":"包内mano_fit_current.json指向mano_beta_pose_fit_only_v005.npz。左右beta/pose与相应基底成套读取;原MANO与接触label保持独立。"},
11
+ {"id":"006","title":"原生手部的强随机化合成","state":"当前数据版本","scope":"ARCTIC 301公开源动作 × 3变体;OakInk2 622源动作独立管理","features":["渲染使用数据集原生手,不使用拟合MANO/SOMA驱动像素;辅助拟合另存并标明非GT。","ARCTIC使用原始SMPL-X身体与原始MANO手的批准资产;OakInk使用当前275人体/276衣物及原生手部组装路线。","1536×864、30fps、Cycles OptiX 16 SPP、GPU OIDN、实际DLSS5 Feature18、时序history 0.9、最终CRF10。","固定种子随机环境、材质、光照与道具;头/胸/肩三部位,相机平移与旋转扰动解耦,保留原HOI运动与源帧。","当前深度为干净GT与带噪传感器两套;都采用毫米量化和快速level1压缩。","SSD工作集与NAS自适应并行;350等编号是执行修订,不是新的数据发布版本。"],"read":"以当前INDEX.json及每包metadata.json、depth_bundle.json为入口;按各包实际文件/schema读取,不能把整个V006假定为同一种深度历史格式。"}
12
+ ],
13
+ "milestones": [
14
+ ["279","原生表面","修复ARCTIC原始MANO与SMPL-X腕部表面;属于资产交付,不等同最终RGB合成完成。"],
15
+ ["287","主队列","301源动作、3个随机变体、903个计划包。"],
16
+ ["320","Hard Cases","双手交叉、真实镜面、多人各5条;15条、10,838主源帧,独立于903主队列。"],
17
+ ["330","分阶段调度","GPU阶段与CPU尾处理分离,最多12条在途,仍受共享物理槽位限制。"],
18
+ ["333–338","已废止深度策略","曾出现sensor-only输出及转换;不再作为当前双深度交付标准。"],
19
+ ["339–340","双深度","恢复干净GT;随后GT也毫米量化,按帧扩展整数位宽,不截断远背景。"],
20
+ ["341–344","SSD工作集","完整SSD工作集、多GPU试跑及仅补深度恢复;没有达到10分钟端到端目标。"],
21
+ ["345–348","容量与接管","修复容量等待/旧日志接管;896GiB总配额,200GiB空闲底线,主GPU优先。"],
22
+ ["349","CPU/I/O并行","同卡2个工作目录;双通道独立压缩写入、4帧预读、运动向量4路预���。"],
23
+ ["350","SSD+NAS","NAS并行1至4条自适应;保留完整父预留,双目临时缓冲占256MiB SSD。" ]
24
+ ],
25
+ "channels": [
26
+ ["RGB","rgb.mp4","1536×864 · 30fps","最终后处理图像,不是raw辐射度;以包内metadata为准。"],
27
+ ["干净GT深度","depth_gt_mm.npz","camera-Z · 1mm量化","无传感器噪声;逐帧uint16/32/64,远背景可超过65.535m,不静默截断。"],
28
+ ["带噪深度","depth_sensor_u16.npz","camera-Z · uint16毫米","主动双目模拟结果;带有效掩码,不以GT补洞,不是标定过的商用相机。"],
29
+ ["可见分割","masks_visible_2bit.npz","每像素2 bit","按classes和bitorder解码;只表示可见类别,不等同接触标注。"],
30
+ ["相机","camera_parameters.npz","K · world_to_camera · camera_to_world","同时保存frame_index、source_frame_index、image_size和坐标约定;不是只能从视频猜相机。"],
31
+ ["原生手参数","mano_parameters.npz","原始MANO参数","逐手读取原始旋转、pose、translation、shape及源帧;不要混入辅助拟合参数。"],
32
+ ["场景与追溯","metadata.json / scene_randomization.json","版本 · 种子 · SHA","按包追踪生成身份;嵌套旧SSD路径需显式artifact_locations解析。"],
33
+ ["辅助拟合","按上游handoff登记","MANO/SOMA · 非GT","不参与渲染;缺失时不可宣称完整训练包就绪。" ]
34
+ ],
35
+ "quality": [
36
+ "完成计数表示渲染与深度流程终态,不代表逐帧视觉验收、全部GT缺口已恢复或辅助标签齐全。",
37
+ "ARCTIC覆盖301条公开完整动作;官方38条隐藏test GT不在此标注覆盖中。随机合成相机不是全部官方原始相机图像的重渲染承诺。",
38
+ "V006历史包存在深度格式差异;必须检查每包文件、有效掩码和时钟。两个已知旧包曾缺GT,实时页单独显示当前仍缺失项。",
39
+ "训练/验证划分应按原始source分组,同一动作的不同随机变体不能分到训练与验证两侧。",
40
+ "模拟深度采用可见光材质灰度代理与调用方选择的噪声参数;不是LingBot作者原始模拟器,也不是某型号相机的物理标定。",
41
+ "原始数据、模型权重与派生数据使用仍受各上游许可约束;此页提供文档和状态,不分发整套数据。"
42
+ ],
43
+ "samples": [
44
+ {"title":"双手交叉","image":"media/egorender/bimanual.png","source":"ARCTIC · Hard Case参考帧","note":"真实源动作筛选;同源动作的历史交叉参考帧。"},
45
+ {"title":"镜面反射","image":"media/egorender/mirror.png","source":"ARCTIC · Hard Case参考帧","note":"实际镜面几何;镜中人不是额外的人体实例。镜面GT为首交点深度。"},
46
+ {"title":"多人","image":"media/egorender/multiple.png","source":"ARCTIC · Hard Case参考帧","note":"两套源人体与对应衣物;每个人保留独立源帧映射。"}
47
+ ],
48
+ "references": [
49
+ ["LingBot-Depth §3.1","https://arxiv.org/html/2601.17895v1#S3.SS1"],
50
+ ["LingBot官方代码","https://github.com/Robbyant/lingbot-depth"],
51
+ ["ARCTIC","https://arctic.is.tue.mpg.de/"],
52
+ ["只读进度数据","https://huggingface.co/datasets/DarthSoluke/EgoRendererStatus"]
53
+ ]
54
+ }
deployment_manifest.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
  "repo_id": "DarthSoluke/EgoNetDatasetResearch",
3
  "repo_type": "space",
4
- "base_revision": "1a36f9ff95d7302e23a4a59cb98a306c0cc8dc70",
5
  "sdk": "static",
6
  "app_file": "index.html",
7
  "original_archive_sha256": "9d437cf8f781dd25354e3de6bdccf4a3e63c5eeb01efab5943ecfee709769160",
@@ -22,14 +22,29 @@
22
  },
23
  {
24
  "path": "README.md",
25
- "bytes": 4173,
26
- "sha256": "f70d18ce15b54bf1370a7475196ead7f786bf850c524912da20ef0dd7d822a4b"
27
  },
28
  {
29
  "path": "data/datasets.json",
30
  "bytes": 44434,
31
  "sha256": "ba13d1302517047df8051e330c277afe704dabb17be38d3a7b57ce3017453992"
32
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  {
34
  "path": "data/figures.json",
35
  "bytes": 80223,
@@ -50,6 +65,11 @@
50
  "bytes": 335515,
51
  "sha256": "841f2aba69b09394f75c1645c49a8ac5f84f7dbc3439974afb24cdf7111c9fd5"
52
  },
 
 
 
 
 
53
  {
54
  "path": "data/sampling.json",
55
  "bytes": 27158,
@@ -60,10 +80,15 @@
60
  "bytes": 27441,
61
  "sha256": "13095d6debfc8df33353c3d1825115397af17f770d01b083972b84931db5251f"
62
  },
 
 
 
 
 
63
  {
64
  "path": "index.html",
65
- "bytes": 50613,
66
- "sha256": "c7bd2e30658926831fcbe3a5d529d4cdddc40ef66c6bd6fb7cba6a9d97fadf23"
67
  },
68
  {
69
  "path": "media/.gitkeep",
@@ -310,6 +335,21 @@
310
  "bytes": 42356,
311
  "sha256": "5308a14db33fef704c22d5fb9753dd32f96f58515c0e71ac50f9fa12f78cfff4"
312
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  {
314
  "path": "media/egorenderer/egorenderer_v005_s1_mug_drink_1_233_gt.jpg",
315
  "bytes": 28660,
@@ -1402,8 +1442,23 @@
1402
  },
1403
  {
1404
  "path": "src/page.html",
1405
- "bytes": 50264,
1406
- "sha256": "3c96450f8a9ace51b06ad88567a199d13be5add59dd668bc969020710d609a25"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1407
  },
1408
  {
1409
  "path": "tools/add_preview.py",
@@ -1434,14 +1489,20 @@
1434
  "path": "tools/server_sync.py",
1435
  "bytes": 6502,
1436
  "sha256": "710eee5a04f8a9e06d7101975a1fd8e2e89d55c07c6d9934265a7ca3fdb2699f"
1437
- },
1438
- {
1439
- "path": "data/publication.json",
1440
- "bytes": 652,
1441
- "sha256": "9e14b55fc17ef3b63053340170a9c5548418636a88b7d098c5cbd71ab01d9318"
1442
  }
1443
  ],
1444
  "host_managed_files": [
1445
  ".gitattributes"
1446
- ]
 
 
 
 
 
 
 
 
 
 
 
1447
  }
 
1
  {
2
  "repo_id": "DarthSoluke/EgoNetDatasetResearch",
3
  "repo_type": "space",
4
+ "base_revision": "84afe8026fdafe13d3bde52ff441f1a4e29e9781",
5
  "sdk": "static",
6
  "app_file": "index.html",
7
  "original_archive_sha256": "9d437cf8f781dd25354e3de6bdccf4a3e63c5eeb01efab5943ecfee709769160",
 
22
  },
23
  {
24
  "path": "README.md",
25
+ "bytes": 6107,
26
+ "sha256": "62f7e420397ae3278205de5eb65beea6ce24a5922a4fbb91d66a016d7f3ab3ca"
27
  },
28
  {
29
  "path": "data/datasets.json",
30
  "bytes": 44434,
31
  "sha256": "ba13d1302517047df8051e330c277afe704dabb17be38d3a7b57ce3017453992"
32
  },
33
+ {
34
+ "path": "data/egorender-progress.json",
35
+ "bytes": 4406,
36
+ "sha256": "c50578bd011e8675bc7d76216710c813b224aa90ec318ddd91bf1c2de7ba082d"
37
+ },
38
+ {
39
+ "path": "data/egorender-registry.json",
40
+ "bytes": 103691,
41
+ "sha256": "dbcfd6bc403aff85b29872d486ab53ac968268cdf2e31c7f914a8e61cc04b5b1"
42
+ },
43
+ {
44
+ "path": "data/egorender.json",
45
+ "bytes": 8318,
46
+ "sha256": "fc323c20af1c5c62448931390afa3ff1f29674f214ad82c5231467142751dd74"
47
+ },
48
  {
49
  "path": "data/figures.json",
50
  "bytes": 80223,
 
65
  "bytes": 335515,
66
  "sha256": "841f2aba69b09394f75c1645c49a8ac5f84f7dbc3439974afb24cdf7111c9fd5"
67
  },
68
+ {
69
+ "path": "data/publication.json",
70
+ "bytes": 652,
71
+ "sha256": "9e14b55fc17ef3b63053340170a9c5548418636a88b7d098c5cbd71ab01d9318"
72
+ },
73
  {
74
  "path": "data/sampling.json",
75
  "bytes": 27158,
 
80
  "bytes": 27441,
81
  "sha256": "13095d6debfc8df33353c3d1825115397af17f770d01b083972b84931db5251f"
82
  },
83
+ {
84
+ "path": "downloads/egorender_reader.py",
85
+ "bytes": 5205,
86
+ "sha256": "14e89b041942ac37d3bd199b7b320968d76cebec1893ad117e50f3f6c405435e"
87
+ },
88
  {
89
  "path": "index.html",
90
+ "bytes": 51650,
91
+ "sha256": "384511ae956b1b66db2caac15dfaf6e83acc34fbaa2f3098f58b44aef4b34e22"
92
  },
93
  {
94
  "path": "media/.gitkeep",
 
335
  "bytes": 42356,
336
  "sha256": "5308a14db33fef704c22d5fb9753dd32f96f58515c0e71ac50f9fa12f78cfff4"
337
  },
338
+ {
339
+ "path": "media/egorender/bimanual.png",
340
+ "bytes": 1535120,
341
+ "sha256": "9c7a584bc8f48d63afbd69532388f164aa65494f2689f49df7dd2235802a7b9a"
342
+ },
343
+ {
344
+ "path": "media/egorender/mirror.png",
345
+ "bytes": 1506794,
346
+ "sha256": "8e1804b8396766b25ca61d42d09b3cca3482d867cb83cb74897ba1f22a054a26"
347
+ },
348
+ {
349
+ "path": "media/egorender/multiple.png",
350
+ "bytes": 1843154,
351
+ "sha256": "2326bceaf25c4bea3aab582909f4d1b21b3de9952dd83b5b18c8d49114afb9c5"
352
+ },
353
  {
354
  "path": "media/egorenderer/egorenderer_v005_s1_mug_drink_1_233_gt.jpg",
355
  "bytes": 28660,
 
1442
  },
1443
  {
1444
  "path": "src/page.html",
1445
+ "bytes": 51301,
1446
+ "sha256": "93caabf3872a471989e9dbad5bce5ee81fea62811aff02312c04e67b5cd57809"
1447
+ },
1448
+ {
1449
+ "path": "src/synthetic.css",
1450
+ "bytes": 7113,
1451
+ "sha256": "5ee57a96284232bb2a2b0d4c5edac56f271acbbf0e777af5e640f62947fc225f"
1452
+ },
1453
+ {
1454
+ "path": "src/synthetic.js",
1455
+ "bytes": 19884,
1456
+ "sha256": "a802e2b571af7606d6fff77b83e917bb43bd14c1c3f60b42525942ee6376f697"
1457
+ },
1458
+ {
1459
+ "path": "src/vendor/lucide.min.js",
1460
+ "bytes": 357796,
1461
+ "sha256": "3411692820cb8d47543f69496aa25fd603a358f4498046f41c508a5a3342210e"
1462
  },
1463
  {
1464
  "path": "tools/add_preview.py",
 
1489
  "path": "tools/server_sync.py",
1490
  "bytes": 6502,
1491
  "sha256": "710eee5a04f8a9e06d7101975a1fd8e2e89d55c07c6d9934265a7ca3fdb2699f"
 
 
 
 
 
1492
  }
1493
  ],
1494
  "host_managed_files": [
1495
  ".gitattributes"
1496
+ ],
1497
+ "synthetic_module": {
1498
+ "data_release": "V006",
1499
+ "documented_releases": 6,
1500
+ "execution_entries": 350,
1501
+ "progress_repository": "DarthSoluke/EgoRendererStatus",
1502
+ "public_refresh_seconds": 60,
1503
+ "stale_after_seconds": 180,
1504
+ "read_only": true,
1505
+ "updated": "2026-11-02",
1506
+ "original_catalog_entries_preserved": 46
1507
+ }
1508
  }
downloads/egorender_reader.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Read frame-indexed EgoRenderer V006 depth and visible masks with NumPy only.
2
+
3
+ No pickle loading, network access, fitting or rendering. Keep one reader open
4
+ per sequence/loader worker. These formats do not cover every historical V005 NPZ.
5
+ """
6
+ from pathlib import Path
7
+ import hashlib
8
+ import json
9
+ import numpy as np
10
+
11
+
12
+ class DepthReader:
13
+ def __init__(self, path):
14
+ self.archive = np.load(path, allow_pickle=False)
15
+ try:
16
+ self.frames = self.archive['source_frame_index']
17
+ self.width = int(self.archive['width'])
18
+ self.shape = tuple(self.archive['frame_shape'])
19
+ self.dtype = np.dtype(str(self.archive['depth_dtype']))
20
+ self.scale = None
21
+ self.frame_dtypes = (self.dtype,)
22
+ self.adaptive = False
23
+ if self.dtype == np.uint16:
24
+ if str(self.archive['quantization']) != 'uint16_mm_v1':
25
+ raise ValueError('Unknown quantized sensor format')
26
+ self.scale = float(self.archive['depth_scale_m'])
27
+ elif self.dtype == np.uint64:
28
+ if str(self.archive['quantization']) != 'adaptive_uint_mm_v1':
29
+ raise ValueError('Unknown adaptive GT format')
30
+ self.frame_dtypes = tuple(np.dtype(x) for x in self.archive['frame_dtypes'])
31
+ if self.frame_dtypes != (np.dtype('uint16'), np.dtype('uint32'), np.dtype('uint64')):
32
+ raise ValueError('Unexpected adaptive integer types')
33
+ self.scale = float(self.archive['depth_scale_m'])
34
+ self.adaptive = True
35
+ elif self.dtype not in (np.float16, np.float32):
36
+ raise ValueError('Unsupported stored depth type')
37
+ if self.scale is not None and self.scale != .001:
38
+ raise ValueError('Unknown depth scale')
39
+ except BaseException:
40
+ self.archive.close()
41
+ raise
42
+
43
+ def __len__(self):
44
+ return len(self.frames)
45
+
46
+ def frame(self, index):
47
+ if not 0 <= index < len(self.frames):
48
+ raise IndexError(index)
49
+ depth = self.archive[f'depth/{index:06d}']
50
+ bits = self.archive[f'valid_bits/{index:06d}']
51
+ if depth.shape != self.shape or depth.dtype not in self.frame_dtypes:
52
+ raise ValueError('Frame differs from archive metadata')
53
+ valid = np.unpackbits(bits, axis=1, bitorder='little')[:, :self.width].astype(bool)
54
+ if self.scale is not None:
55
+ if not np.array_equal(depth > 0, valid):
56
+ raise ValueError('Quantized depth validity differs')
57
+ depth = ((depth.astype(np.float64)*self.scale).astype(np.float32) if self.adaptive
58
+ else depth.astype(np.float32)*np.float32(self.scale))
59
+ return depth, valid, int(self.frames[index])
60
+
61
+ def close(self):
62
+ self.archive.close()
63
+
64
+ def __enter__(self):
65
+ return self
66
+
67
+ def __exit__(self, *_):
68
+ self.close()
69
+
70
+
71
+ def depth_pair(package):
72
+ package = Path(package)
73
+ gt = next((package/n for n in ('depth_gt_mm.npz', 'depth_gt.npz') if (package/n).is_file()), None)
74
+ sensor = next((package/n for n in ('depth_sensor_u16.npz', 'depth_sensor.npz') if (package/n).is_file()), None)
75
+ if gt is None or sensor is None:
76
+ raise FileNotFoundError('Both clean GT and simulated sensor depth are required')
77
+ return gt, sensor
78
+
79
+
80
+ def unpack_class_frame(packed, shape):
81
+ size = int(np.prod(shape))
82
+ bits = np.unpackbits(packed, bitorder='little')[:size*2].reshape(size, 2)
83
+ return (bits[:, 0] | (bits[:, 1] << 1)).reshape(shape)
84
+
85
+
86
+ class DatasetLocations:
87
+ """Explicitly resolve bound historical paths without editing immutable JSON."""
88
+ def __init__(self, manifest):
89
+ value = json.loads(Path(manifest).read_text())
90
+ if value['schema'] != 'egorenderer.dataset_relocations.v1' or not value['complete']:
91
+ raise ValueError('A complete location manifest is required')
92
+ self.root = Path(value['dataset_root'])
93
+ self.mappings = sorted([(Path(r['source']), Path(r['target'])) for r in value['mappings']],
94
+ key=lambda pair: len(pair[0].parts), reverse=True)
95
+ if any(not target.is_relative_to(self.root) for _, target in self.mappings):
96
+ raise ValueError('Cross-dataset relocation')
97
+
98
+ def path(self, value):
99
+ original = Path(value)
100
+ for source, target in self.mappings:
101
+ if original.is_relative_to(source):
102
+ return target / original.relative_to(source)
103
+ return original
104
+
105
+ def references(self, value):
106
+ if isinstance(value, str) and value.startswith('/'):
107
+ return str(self.path(value))
108
+ if isinstance(value, list):
109
+ return [self.references(v) for v in value]
110
+ if isinstance(value, dict):
111
+ return {self.references(k): self.references(v) for k, v in value.items()}
112
+ return value
113
+
114
+ def read_bound_json(self, binding):
115
+ raw = self.path(binding['path']).read_bytes()
116
+ if hashlib.sha256(raw).hexdigest() != binding['sha256']:
117
+ raise ValueError('Immutable JSON SHA mismatch')
118
+ return self.references(json.loads(raw))
index.html CHANGED
@@ -5,6 +5,7 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
  <style>:root{padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}img{max-width:100%}[hidden]{display:none!important}</style>
7
  <title>SOMA 手部数据图谱</title>
 
8
  <meta name="claude:viewport-fit-sides" content="cover">
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -274,6 +275,9 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
274
  </head>
275
  <body>
276
 
 
 
 
277
 
278
  <header class="top">
279
  <div class="top-in">
@@ -284,6 +288,7 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
284
  <a href="#methods" data-v="methods">标注方法</a>
285
  <a href="#soma" data-v="soma">转到 SOMA</a>
286
  <a href="#samples" data-v="samples">样例</a>
 
287
  </nav>
288
  </div>
289
  </header>
@@ -596,16 +601,23 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
596
  /* ---------- 路由 ---------- */
597
  var pendingJump = null;
598
  function render() {
 
599
  var hash = (location.hash || "#overview").slice(1), view = hash, html;
600
- if (hash.indexOf("ds-") === 0) { html = vDetail(hash.slice(3)); view = "catalog"; }
 
601
  else if (hash === "catalog") html = vCatalog();
602
  else if (hash === "methods") html = vMethods();
603
  else if (hash === "soma") html = vSoma();
604
  else if (hash === "samples") html = vSamples();
605
  else { html = vOverview(); view = "overview"; }
606
  document.querySelectorAll("video").forEach(function (v) { v.pause(); });
607
- app.innerHTML = html + '<footer>整理于 ' + esc(DATA.meta.updated) + "。数值来自论文、项目页和团队图册;标「待核」的字段尚未确认。</footer>";
 
 
 
608
  document.querySelectorAll("nav.tabs a").forEach(function (a) { if (a.getAttribute("data-v") === view) a.setAttribute("aria-current", "page"); else a.removeAttribute("aria-current"); });
 
 
609
  if (hash === "catalog") bindCatalog();
610
  if (hash.indexOf("ds-") === 0 && BYID[hash.slice(3)]) {
611
  var id = hash.slice(3);
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
  <style>:root{padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}img{max-width:100%}[hidden]{display:none!important}</style>
7
  <title>SOMA 手部数据图谱</title>
8
+ <link rel="icon" href="data:,">
9
  <meta name="claude:viewport-fit-sides" content="cover">
10
  <link rel="preconnect" href="https://fonts.googleapis.com">
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 
275
  </head>
276
  <body>
277
 
278
+ <link rel="stylesheet" href="src/synthetic.css">
279
+ <script src="src/vendor/lucide.min.js"></script>
280
+ <script src="src/synthetic.js"></script>
281
 
282
  <header class="top">
283
  <div class="top-in">
 
288
  <a href="#methods" data-v="methods">标注方法</a>
289
  <a href="#soma" data-v="soma">转到 SOMA</a>
290
  <a href="#samples" data-v="samples">样例</a>
291
+ <a href="#synthetic/progress" data-v="synthetic">合成数据</a>
292
  </nav>
293
  </div>
294
  </header>
 
601
  /* ---------- 路由 ---------- */
602
  var pendingJump = null;
603
  function render() {
604
+ if (window.EgoRendererPage) window.EgoRendererPage.unmount();
605
  var hash = (location.hash || "#overview").slice(1), view = hash, html;
606
+ if (hash.indexOf("synthetic") === 0) { html = window.EgoRendererPage ? window.EgoRendererPage.render() : '<p class="err">合成数据模块未加载,请刷新重试。</p>'; view = "synthetic"; }
607
+ else if (hash.indexOf("ds-") === 0) { html = vDetail(hash.slice(3)); view = "catalog"; }
608
  else if (hash === "catalog") html = vCatalog();
609
  else if (hash === "methods") html = vMethods();
610
  else if (hash === "soma") html = vSoma();
611
  else if (hash === "samples") html = vSamples();
612
  else { html = vOverview(); view = "overview"; }
613
  document.querySelectorAll("video").forEach(function (v) { v.pause(); });
614
+ app.innerHTML = html + (view === "synthetic" ? '' : '<footer>整理于 ' + esc(DATA.meta.updated) + "。数值来自论文、项目页和团队图册;标「待核」的字段尚未确认。</footer>");
615
+ document.getElementById("upd").textContent = view === "synthetic" ? "文档 2026-11-02" : "更新于 " + DATA.meta.updated;
616
+ document.title = view === "synthetic" ? "EgoRenderer · SOMA 手部数据图谱" : "SOMA 手部数据图谱";
617
+ if (view === "synthetic" && window.EgoRendererPage) window.EgoRendererPage.mount();
618
  document.querySelectorAll("nav.tabs a").forEach(function (a) { if (a.getAttribute("data-v") === view) a.setAttribute("aria-current", "page"); else a.removeAttribute("aria-current"); });
619
+ var activeTab = document.querySelector('nav.tabs a[aria-current="page"]');
620
+ if (activeTab) activeTab.scrollIntoView({block:"nearest", inline:"nearest"});
621
  if (hash === "catalog") bindCatalog();
622
  if (hash.indexOf("ds-") === 0 && BYID[hash.slice(3)]) {
623
  var id = hash.slice(3);
media/egorender/bimanual.png ADDED

Git LFS Details

  • SHA256: 9c7a584bc8f48d63afbd69532388f164aa65494f2689f49df7dd2235802a7b9a
  • Pointer size: 132 Bytes
  • Size of remote file: 1.54 MB
media/egorender/mirror.png ADDED

Git LFS Details

  • SHA256: 8e1804b8396766b25ca61d42d09b3cca3482d867cb83cb74897ba1f22a054a26
  • Pointer size: 132 Bytes
  • Size of remote file: 1.51 MB
media/egorender/multiple.png ADDED

Git LFS Details

  • SHA256: 2326bceaf25c4bea3aab582909f4d1b21b3de9952dd83b5b18c8d49114afb9c5
  • Pointer size: 132 Bytes
  • Size of remote file: 1.84 MB
src/page.html CHANGED
@@ -1,4 +1,5 @@
1
  <title>SOMA 手部数据图谱</title>
 
2
  <meta name="claude:viewport-fit-sides" content="cover">
3
  <link rel="preconnect" href="https://fonts.googleapis.com">
4
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -265,6 +266,9 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
265
  @media(max-width:520px) { .preview-pair { gap:5px; } .preview-pair figcaption { padding:6px; font-size:.7rem; } .preview-tools input[type="range"] { width:100px; } .preview-tools label { flex-wrap:wrap; } }
266
 
267
  </style>
 
 
 
268
 
269
  <header class="top">
270
  <div class="top-in">
@@ -275,6 +279,7 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
275
  <a href="#methods" data-v="methods">标注方法</a>
276
  <a href="#soma" data-v="soma">转到 SOMA</a>
277
  <a href="#samples" data-v="samples">样例</a>
 
278
  </nav>
279
  </div>
280
  </header>
@@ -587,16 +592,23 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
587
  /* ---------- 路由 ---------- */
588
  var pendingJump = null;
589
  function render() {
 
590
  var hash = (location.hash || "#overview").slice(1), view = hash, html;
591
- if (hash.indexOf("ds-") === 0) { html = vDetail(hash.slice(3)); view = "catalog"; }
 
592
  else if (hash === "catalog") html = vCatalog();
593
  else if (hash === "methods") html = vMethods();
594
  else if (hash === "soma") html = vSoma();
595
  else if (hash === "samples") html = vSamples();
596
  else { html = vOverview(); view = "overview"; }
597
  document.querySelectorAll("video").forEach(function (v) { v.pause(); });
598
- app.innerHTML = html + '<footer>整理于 ' + esc(DATA.meta.updated) + "。数值来自论文、项目页和团队图册;标「待核」的字段尚未确认。</footer>";
 
 
 
599
  document.querySelectorAll("nav.tabs a").forEach(function (a) { if (a.getAttribute("data-v") === view) a.setAttribute("aria-current", "page"); else a.removeAttribute("aria-current"); });
 
 
600
  if (hash === "catalog") bindCatalog();
601
  if (hash.indexOf("ds-") === 0 && BYID[hash.slice(3)]) {
602
  var id = hash.slice(3);
 
1
  <title>SOMA 手部数据图谱</title>
2
+ <link rel="icon" href="data:,">
3
  <meta name="claude:viewport-fit-sides" content="cover">
4
  <link rel="preconnect" href="https://fonts.googleapis.com">
5
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 
266
  @media(max-width:520px) { .preview-pair { gap:5px; } .preview-pair figcaption { padding:6px; font-size:.7rem; } .preview-tools input[type="range"] { width:100px; } .preview-tools label { flex-wrap:wrap; } }
267
 
268
  </style>
269
+ <link rel="stylesheet" href="src/synthetic.css">
270
+ <script src="src/vendor/lucide.min.js"></script>
271
+ <script src="src/synthetic.js"></script>
272
 
273
  <header class="top">
274
  <div class="top-in">
 
279
  <a href="#methods" data-v="methods">标注方法</a>
280
  <a href="#soma" data-v="soma">转到 SOMA</a>
281
  <a href="#samples" data-v="samples">样例</a>
282
+ <a href="#synthetic/progress" data-v="synthetic">合成数据</a>
283
  </nav>
284
  </div>
285
  </header>
 
592
  /* ---------- 路由 ---------- */
593
  var pendingJump = null;
594
  function render() {
595
+ if (window.EgoRendererPage) window.EgoRendererPage.unmount();
596
  var hash = (location.hash || "#overview").slice(1), view = hash, html;
597
+ if (hash.indexOf("synthetic") === 0) { html = window.EgoRendererPage ? window.EgoRendererPage.render() : '<p class="err">合成数据模块未加载,请刷新重试。</p>'; view = "synthetic"; }
598
+ else if (hash.indexOf("ds-") === 0) { html = vDetail(hash.slice(3)); view = "catalog"; }
599
  else if (hash === "catalog") html = vCatalog();
600
  else if (hash === "methods") html = vMethods();
601
  else if (hash === "soma") html = vSoma();
602
  else if (hash === "samples") html = vSamples();
603
  else { html = vOverview(); view = "overview"; }
604
  document.querySelectorAll("video").forEach(function (v) { v.pause(); });
605
+ app.innerHTML = html + (view === "synthetic" ? '' : '<footer>整理于 ' + esc(DATA.meta.updated) + "。数值来自论文、项目页和团队图册;标「待核」的字段尚未确认。</footer>");
606
+ document.getElementById("upd").textContent = view === "synthetic" ? "文档 2026-11-02" : "更新于 " + DATA.meta.updated;
607
+ document.title = view === "synthetic" ? "EgoRenderer · SOMA 手部数据图谱" : "SOMA 手部数据图谱";
608
+ if (view === "synthetic" && window.EgoRendererPage) window.EgoRendererPage.mount();
609
  document.querySelectorAll("nav.tabs a").forEach(function (a) { if (a.getAttribute("data-v") === view) a.setAttribute("aria-current", "page"); else a.removeAttribute("aria-current"); });
610
+ var activeTab = document.querySelector('nav.tabs a[aria-current="page"]');
611
+ if (activeTab) activeTab.scrollIntoView({block:"nearest", inline:"nearest"});
612
  if (hash === "catalog") bindCatalog();
613
  if (hash.indexOf("ds-") === 0 && BYID[hash.slice(3)]) {
614
  var id = hash.slice(3);
src/synthetic.css ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .synth { letter-spacing:0; }
2
+ .synth * { letter-spacing:0; }
3
+ .synth-head { display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap; }
4
+ .synth-head h1 { font-family:var(--f-body); font-size:26px; }
5
+ .synth-head .synth-identity { font:13px var(--f-mono); color:var(--muted); margin-top:5px; }
6
+ .synth-nav { display:flex; gap:22px; border-bottom:1px solid var(--line); overflow:auto; }
7
+ .synth-nav a { white-space:nowrap; padding:10px 0; text-decoration:none; color:var(--muted); border-bottom:2px solid transparent; }
8
+ .synth-nav a[aria-current=page] { color:var(--ink); border-color:var(--accent); }
9
+ .synth-status { display:flex; align-items:center; gap:10px; font-size:13px; flex-wrap:wrap; }
10
+ .synth-dot { width:7px; height:7px; display:inline-block; border-radius:50%; background:#137667; }
11
+ .synth-status[data-state=stale] .synth-dot,.synth-status[data-state=error] .synth-dot { background:#b86a15; }
12
+ .synth-status[data-state=stopped] .synth-dot { background:#7d8791; }
13
+ .synth-icon { width:34px; height:34px; border:1px solid var(--line); border-radius:4px; background:var(--surface); color:var(--ink); display:inline-grid; place-items:center; cursor:pointer; flex:none; }
14
+ .synth-icon svg { width:16px; height:16px; }
15
+ .synth-icon:disabled { opacity:.45; cursor:wait; }
16
+ .synth-kpis { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); border-top:1px solid var(--line); border-bottom:1px solid var(--line); }
17
+ .synth-kpi { padding:20px 16px; min-width:0; border-right:1px solid var(--line); }
18
+ .synth-kpi:first-child { padding-left:0; }
19
+ .synth-kpi:last-child { border-right:0; }
20
+ .synth-kpi strong { display:block; font:500 30px/1.4 var(--f-mono); }
21
+ .synth-kpi span { color:var(--muted); font-size:13px; }
22
+ .synth-progress { display:flex; height:9px; background:var(--sunk); width:100%; overflow:hidden; }
23
+ .synth-progress > span { height:100%; }
24
+ .synth-progress .done { background:#167d70; }.synth-progress .running { background:#488ab1; }.synth-progress .failed { background:#c66740; }
25
+ .synth-facts { display:flex; gap:12px 28px; flex-wrap:wrap; font-size:13px; color:var(--muted); }
26
+ .synth-facts b { color:var(--ink); font-family:var(--f-mono); font-weight:500; }
27
+ .synth-section { padding-top:20px; border-top:1px solid var(--line); display:grid; gap:14px; min-width:0; }
28
+ .synth-section:first-child { padding-top:0; border-top:0; }
29
+ .synth-section h2 { font-family:var(--f-body); font-size:18px; }
30
+ .synth-table { overflow:auto; min-width:0; }
31
+ .synth-table table { width:100%; border-collapse:collapse; font-size:13px; }
32
+ .synth-table th { text-align:left; font-weight:500; color:var(--muted); border-bottom:1px solid var(--line); padding:9px 12px 9px 0; }
33
+ .synth-table td { border-bottom:1px solid var(--line); padding:12px 12px 12px 0; vertical-align:top; overflow-wrap:anywhere; }
34
+ .synth-table td:last-child,.synth-table th:last-child { padding-right:0; }
35
+ .synth-table th { white-space:nowrap; }.synth-table .source { min-width:190px; font-family:var(--f-mono); font-size:12px; }
36
+ .synth-table .num { font-family:var(--f-mono); white-space:nowrap; }
37
+ .synth-table .status-text { white-space:nowrap; }
38
+ .synth-empty { color:var(--muted); padding:18px 0; }
39
+ .synth-toolbar { display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap; }
40
+ .synth-toolbar select,.synth-toolbar input { font:inherit; font-size:13px; color:var(--ink); background:var(--surface); border:1px solid var(--line); border-radius:4px; padding:7px 10px; min-width:0; }
41
+ .synth-toolbar input { width:220px; max-width:100%; }
42
+ .synth-resource { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:20px; }
43
+ .synth-resource dl { margin:0; display:grid; grid-template-columns:1fr auto; gap:7px; font-size:13px; }
44
+ .synth-resource dt { color:var(--muted); }.synth-resource dd { margin:0; font-family:var(--f-mono); }
45
+ .synth-resource progress { width:100%; height:5px; accent-color:#488ab1; grid-column:1/-1; }
46
+ .synth-callout { border-left:3px solid #b86a15; padding:4px 0 4px 14px; font-size:13px; line-height:1.8; }
47
+ .synth-version-picker { display:flex; gap:0; flex-wrap:wrap; width:fit-content; border:1px solid var(--line); border-radius:4px; overflow:hidden; }
48
+ .synth-version-picker button { border:0; border-right:1px solid var(--line); min-width:66px; padding:9px 12px; cursor:pointer; font:13px var(--f-mono); color:var(--ink); background:var(--surface); }
49
+ .synth-version-picker button:last-child { border-right:0; }
50
+ .synth-version-picker button[aria-pressed=true] { background:var(--ink); color:var(--surface); }
51
+ .synth-version-detail { display:grid; gap:16px; min-height:280px; }
52
+ .synth-version-detail ul { padding-left:20px; display:grid; gap:9px; font-size:14px; }
53
+ .synth-version-detail h2 { font:700 20px var(--f-body); }
54
+ .synth-meta { display:flex; gap:10px 22px; color:var(--muted); font-size:13px; flex-wrap:wrap; }
55
+ .synth-code { background:var(--sunk); border:1px solid var(--line); border-radius:4px; padding:16px; position:relative; min-width:0; }
56
+ .synth-code pre { margin:0; padding-right:34px; overflow:auto; font:12px/1.8 var(--f-mono); white-space:pre; tab-size:4; }
57
+ .synth-code .synth-icon { position:absolute; right:8px; top:8px; }
58
+ .synth-code-title { font-size:14px; font-weight:500; }
59
+ .synth-steps { display:grid; gap:24px; }
60
+ .synth-steps section { display:grid; gap:12px; min-width:0; }
61
+ .synth-links { display:flex; gap:12px 22px; flex-wrap:wrap; font-size:13px; }
62
+ .synth details { border-bottom:1px solid var(--line); padding:12px 0; }
63
+ .synth summary { cursor:pointer; font-size:14px; }.synth details > p { padding-top:10px; font-size:13px; color:var(--muted); }
64
+ .synth-samples { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:22px; }
65
+ .synth-samples figure { margin:0; min-width:0; }
66
+ .synth-samples img { width:100%; aspect-ratio:16/9; object-fit:contain; background:#10161c; border:1px solid var(--line); }
67
+ .synth-samples figcaption { padding-top:12px; display:grid; gap:6px; font-size:13px; }
68
+ .synth-samples small { color:var(--muted); }
69
+ .synth-error { color:#b34b35; }.synth-good { color:#167d70; }
70
+ .synth-skeleton { min-height:350px; padding-top:30px; color:var(--muted); }
71
+ .synth .subtle { font-size:13px; color:var(--muted); }
72
+ .synth-pager { display:flex; align-items:center; justify-content:flex-end; gap:8px; padding-top:12px; font:12px var(--f-mono); }
73
+ .synth-pager span { margin-right:8px; }
74
+ @media(max-width:760px) {
75
+ .synth-kpis { grid-template-columns:repeat(2,minmax(0,1fr)); }
76
+ .synth-kpi { padding:14px 12px; }.synth-kpi:nth-child(2) { border-right:0; }.synth-kpi:nth-child(n+3) { border-top:1px solid var(--line); }
77
+ .synth-kpi:nth-child(3) { padding-left:0; }.synth-kpi strong { font-size:25px; }
78
+ .synth-resource { grid-template-columns:repeat(2,minmax(0,1fr)); gap:20px; }
79
+ .synth-samples { grid-template-columns:1fr; }.synth-samples img { max-height:330px; }
80
+ .synth-table table { min-width:600px; }.synth-nav { gap:20px; }
81
+ .synth-version-picker { width:100%; }.synth-version-picker button { flex:1; min-width:44px; padding:8px 5px; }
82
+ .synth-version-detail { min-height:0; }.synth-head h1 { font-size:24px; }
83
+ }
src/synthetic.js ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ 'use strict';
3
+ let content, registry = [], snapshot, root, timer, ageTimer, request, generation = 0;
4
+ let selectedVersion = '006', selectedJobs = 'active', registryQuery = '', registryPage = 0;
5
+ const esc = x => String(x == null ? '' : x).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
6
+ const number = x => Number.isFinite(x) ? x.toLocaleString('zh-CN', {maximumFractionDigits:1}) : '—';
7
+ const clock = t => new Date(t * 1000).toLocaleString('zh-CN', {timeZone:'Asia/Kuala_Lumpur',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',hour12:false}) + ' MYT';
8
+ const icon = (name, label, attr = '') => `<button class="synth-icon" title="${esc(label)}" aria-label="${esc(label)}" ${attr}><i data-lucide="${name}" aria-hidden="true"></i></button>`;
9
+ const table = (heads, rows) => `<div class="synth-table"><table><thead><tr>${heads.map(h=>`<th>${esc(h)}</th>`).join('')}</tr></thead><tbody>${rows.join('')}</tbody></table></div>`;
10
+ const phases = {WAIT_SSD_CAPACITY:'等待存储准入',WAIT_CAPACITY:'等待容量',WAIT_SHARED_GPU_FOR_CYCLES:'等待GPU',V5_APPEARANCE_CANONICAL_SURFACE:'表面与材质',V5_THREE_MOUNT_INDEPENDENT_CAMERA:'相机轨迹',V5_RANDOMIZED_SCENE_CANONICAL_SURFACE:'场景组装',CYCLES_OPTIX_16SPP_GPU_OIDN:'RGB渲染',VIDEO_POST_AND_DEPTH_OVERLAPPED:'视频后处理 / 深度',PACKAGE_NATIVE_RENDER_CHANNELS:'打包',VERIFIED_SEQUENTIAL_NAS_PUBLICATION:'NAS归档',INITIALIZING:'初始化',FAILED:'待定点修复'};
11
+ const sections = [['progress','进度'],['versions','版本'],['reading','数据读取'],['samples','样例']];
12
+ const tab = () => (location.hash.split('/')[1] || 'progress');
13
+ function section(title, body) { return `<section class="synth-section"><h2>${esc(title)}</h2>${body}</section>`; }
14
+ function code(title, text) { return `<section><h3 class="synth-code-title">${esc(title)}</h3><div class="synth-code">${icon('copy','复制代码','data-copy')}<pre><code>${esc(text)}</code></pre></div></section>`; }
15
+ function icons() { if (window.lucide) window.lucide.createIcons(); }
16
+ function valid(value) {
17
+ const c = value && value.counts;
18
+ return value && value.schema === 'egorenderer.public_progress.v1' && Number.isFinite(value.collected_at) && c &&
19
+ ['complete','running','ready','failed','total'].every(k=>Number.isInteger(c[k]) && c[k]>=0) &&
20
+ c.total>0 && c.complete+c.running+c.ready+c.failed===c.total;
21
+ }
22
+ function header() {
23
+ return `<div class="synth-head"><div><h1>EgoRenderer</h1><div class="synth-identity">V006 <span id="synth-execution"></span></div></div><div class="synth-status" id="synth-status" aria-live="polite"><span class="synth-dot"></span><span id="synth-freshness">读取快照</span>${icon('refresh-cw','刷新进度','id="synth-refresh"')}</div></div>
24
+ <nav class="synth-nav" aria-label="合成数据">${sections.map(([id,label])=>`<a href="#synthetic/${id}" ${tab()===id?'aria-current="page"':''}>${label}</a>`).join('')}</nav>`;
25
+ }
26
+ function progressView() {
27
+ return `<div id="synth-summary" class="synth-kpis" aria-live="polite"></div><div class="part"><div id="synth-progress" class="synth-progress" role="progressbar" aria-label="ARCTIC随机化包生成进度"></div><div id="synth-facts" class="synth-facts"></div></div>
28
+ <div id="synth-notices"></div>${section('数据范围','<div id="synth-cohorts"></div>')}
29
+ <section class="synth-section"><div class="synth-toolbar"><h2>当前任务</h2><select id="synth-job-filter" aria-label="任务状态"><option value="active">运行中</option><option value="failed">失败</option><option value="all">运行与失败</option></select></div><div id="synth-jobs"></div></section>
30
+ ${section('主机资源','<div id="synth-resources" class="synth-resource"></div><div id="synth-storage" class="synth-facts"></div>')}
31
+ ${section('最近归档','<div id="synth-recent"></div>')}`;
32
+ }
33
+ function updateFreshness(error) {
34
+ if (!root) return;
35
+ const status=root.querySelector('#synth-status'), label=root.querySelector('#synth-freshness');
36
+ if (!snapshot) { status.dataset.state='error';label.textContent=error?'状态暂不可用':'读取快照';return; }
37
+ const stale=Date.now()/1000-snapshot.collected_at>180;
38
+ status.dataset.state=error||stale?'stale':snapshot.queue_alive&&!snapshot.admission_held?'live':'stopped';
39
+ label.textContent=(error?'连接中断 · ':stale?'更新已过期 · ':!snapshot.queue_alive?'队列已停 · ':snapshot.admission_held?'暂停新增 · ':'')+clock(snapshot.collected_at);
40
+ root.querySelector('#synth-execution').textContent=' / 执行 '+snapshot.execution_revision;
41
+ }
42
+ function updateProgress() {
43
+ if (!root || tab()!=='progress')return;
44
+ if (!snapshot) { root.querySelector('#synth-summary').innerHTML='<p class="synth-empty">状态暂不可用。不会以零值替代上次进度。</p>';return; }
45
+ const s=snapshot,c=s.counts;
46
+ root.querySelector('#synth-summary').innerHTML=[['渲染与深度完成',`${number(c.complete)} / ${number(c.total)}`],['运行中',number(c.running)],['待领取',number(c.ready)],['失败',number(c.failed)]].map(([k,v])=>`<div class="synth-kpi"><strong>${v}</strong><span>${k}</span></div>`).join('');
47
+ const progress=root.querySelector('#synth-progress');progress.setAttribute('aria-valuemin','0');progress.setAttribute('aria-valuemax',c.total);progress.setAttribute('aria-valuenow',c.complete);
48
+ progress.innerHTML=[['done',c.complete],['running',c.running],['failed',c.failed]].map(([cl,n])=>`<span class="${cl}" style="width:${100*n/c.total}%"></span>`).join('');
49
+ root.querySelector('#synth-facts').innerHTML=`<span>完成 <b>${(100*c.complete/c.total).toFixed(1)}%</b></span><span>变体帧 <b>${number(s.coverage.completed_variant_frames)} / ${number(s.scope.expected_variant_frames)}</b></span><span>最近1小时 <b>${number(s.throughput.packages_last_hour)}包</b></span><span>队列预计(不含失败) <b>${s.throughput.queued_eta_hours==null?'—':number(s.throughput.queued_eta_hours)+'小时'}</b></span>`;
50
+ root.querySelector('#synth-notices').innerHTML=`<div class="synth-callout">训练就绪状态未确认;辅助拟合待上游交付。${s.known_gt_missing.length?`另有 <b>${s.known_gt_missing.length}</b> 个已知历史包仍缺干净GT深度。`:''}</div>`;
51
+ const oak=s.oakink_assets, hc=s.hardcases;
52
+ root.querySelector('#synth-cohorts').innerHTML=table(['数据','规模','当前口径'],[
53
+ `<tr><td>ARCTIC · V006</td><td class="num">${number(s.scope.sources)}源 / ${number(c.total)}包</td><td>${number(s.scope.source_frames)}源帧;${number(s.coverage.sources_with_all_variants)}条动作完成全部3变体</td></tr>`,
54
+ `<tr><td>Hard Cases · V006</td><td class="num">${hc.complete} / ${hc.expected}条样例</td><td>${number(hc.frames)}主源帧;三类各5条,独立于主队列</td></tr>`,
55
+ `<tr><td>OakInk2 · V006</td><td class="num">${number(oak.total)}源</td><td>${number(oak.counts.COMPLETE||0)}条人体资产完成;不是最终合成包计数</td></tr>`,
56
+ '<tr><td>GRAB · V005</td><td class="num">1,314源组 / 2,425包</td><td>历史参数附件更新完成;V006新深度尚未据此宣称完成</td></tr>'
57
+ ]);
58
+ const selected=root.querySelector('#synth-job-filter');selected.value=selectedJobs;
59
+ const jobs=s.jobs.filter(j=>selectedJobs==='all'||(selectedJobs==='failed'?j.state==='FAILED':j.state!=='FAILED'));
60
+ root.querySelector('#synth-jobs').innerHTML=jobs.length?table(['源动作','变体','帧数','存储','阶段'],jobs.map(j=>`<tr><td class="source">${esc(j.source)}</td><td class="num">${j.variant}</td><td class="num">${number(j.frames)}</td><td>${esc(j.storage||'待准入')}</td><td class="status-text ${j.state==='FAILED'?'synth-error':''}">${esc(phases[j.phase]||j.phase)}</td></tr>`)):'<p class="synth-empty">当前没有此类任务。</p>';
61
+ const gpu=s.hardware.gpus||[];
62
+ root.querySelector('#synth-resources').innerHTML=gpu.map(g=>`<dl><dt>GPU ${g.index}</dt><dd>${g.utilization}%</dd><progress aria-label="GPU ${g.index}利用率" max="100" value="${g.utilization}"></progress><dt>显存 GiB</dt><dd>${number(g.used_mib/1024)} / ${number(g.total_mib/1024)}</dd></dl>`).join('');
63
+ root.querySelector('#synth-storage').innerHTML=`<span>CPU(主机) <b>${number(s.hardware.cpu_percent)}%</b></span><span>SSD预留 <b>${number(s.storage.ssd_reserved_gib)} / ${number(s.storage.ssd_budget_gib)} GiB</b></span><span>SSD可用 <b>${number(s.storage.ssd_available_gib)} GiB</b></span><span>NAS可用 <b>${number(s.storage.nas_available_gib/1024)} TiB</b></span><span>NAS通道 <b>${s.storage.nas_active} / ${s.storage.nas_limit}</b></span>`;
64
+ root.querySelector('#synth-recent').innerHTML=table(['源动作','变体','归档时间'],s.recent.map(j=>`<tr><td class="source">${esc(j.source)}</td><td class="num">${j.variant}</td><td>${clock(j.at)}</td></tr>`));
65
+ }
66
+ function versionDetail() {
67
+ const v=content.versions.find(x=>x.id===selectedVersion);
68
+ return `<h2>V${v.id} · ${esc(v.title)}</h2><div class="synth-meta"><span>${esc(v.state)}</span><span>${esc(v.scope)}</span></div><ul>${v.features.map(f=>`<li>${esc(f)}</li>`).join('')}</ul><div class="synth-callout">${esc(v.read)}</div>`;
69
+ }
70
+ function versionsView() {
71
+ return `<section class="synth-section"><h2>数据与方法版本</h2><div class="synth-version-picker" role="group" aria-label="版本">${content.versions.map(v=>`<button data-version="${v.id}" aria-pressed="${v.id===selectedVersion}">V${v.id}</button>`).join('')}</div><div id="synth-version-detail" class="synth-version-detail">${versionDetail()}</div></section>
72
+ ${section('V006关键执行修订',table(['修订','主题','变化'],content.milestones.map(r=>`<tr>${r.map(x=>`<td>${esc(x)}</td>`).join('')}</tr>`)))}
73
+ <section class="synth-section"><div class="synth-toolbar"><h2>执行登记簿</h2><input id="synth-registry-search" type="search" aria-label="搜索执行修订" placeholder="版本、模块或状态" value="${esc(registryQuery)}"></div><div class="subtle">登记状态���是实时任务状态;执行修订不等同数据发布版本。</div><div id="synth-registry"></div></section>`;
74
+ }
75
+ function updateRegistry() {
76
+ const target=root.querySelector('#synth-registry');if(!target)return;
77
+ const rows=registry.filter(r=>[r.id,r.title,r.status,r.scope].join(' ').toLowerCase().includes(registryQuery.toLowerCase())).slice().reverse();
78
+ registryPage=Math.max(0,Math.min(registryPage,Math.ceil(rows.length/25)-1));
79
+ const start=registryPage*25;
80
+ target.innerHTML=table(['修订','模块','登记状态 / 范围'],rows.slice(start,start+25).map(r=>`<tr><td class="num">${esc(r.id)}</td><td>${esc(r.title)}</td><td><details><summary>${esc(r.status)}</summary><p>${esc(r.scope||'范围见对应冻结合同。')}</p><p class="mono">SHA256 ${esc(r.sha256)}</p></details></td></tr>`))+`<div class="synth-pager"><span>${rows.length?start+1:0}–${Math.min(start+25,rows.length)} / ${rows.length}</span>${icon('chevron-left','上一页','data-registry-prev')}${icon('chevron-right','下一页','data-registry-next')}</div>`;
81
+ const prev=target.querySelector('[data-registry-prev]'),next=target.querySelector('[data-registry-next]');
82
+ prev.disabled=registryPage===0;next.disabled=start+25>=rows.length;
83
+ prev.onclick=()=>{registryPage--;updateRegistry();};next.onclick=()=>{registryPage++;updateRegistry();};icons();
84
+ }
85
+ function readingView() {
86
+ const index=`import json, os\nfrom pathlib import Path\n\nroot = Path(os.environ["EGORENDER_ROOT"])\nindex = json.loads((root / "ARCTIC/packages/"\n "final_surface279_v287_20261030/INDEX.json").read_text())\nfor item in index["complete"]:\n package = Path(item["package"])\n meta = json.loads((package / "metadata.json").read_text())\n print(meta["sequence_index"], meta["frames"], meta["dataset_release"])\n # complete 是渲染终态;另查深度缺口与辅助标注。`;
87
+ const depth=`from egorender_reader import DepthReader, depth_pair\nimport numpy as np\n\ngt_path, sensor_path = depth_pair(package)\nwith DepthReader(gt_path) as gt, DepthReader(sensor_path) as sensor:\n assert np.array_equal(gt.frames, sensor.frames)\n z_gt, valid_gt, source_frame = gt.frame(0)\n z_sensor, valid_sensor, other_frame = sensor.frame(0)\n assert source_frame == other_frame\n # z: float32 米,相机轴向Z;分别使用各自的valid。\n # 每个DataLoader worker长期持有reader,不逐帧重新打开整个NPZ。`;
88
+ const camera=`with np.load(package / "camera_parameters.npz", allow_pickle=False) as c:\n source_frames = c["source_frame_index"]\n K = c["K"]\n world_to_camera = c["world_to_camera"]\n camera_to_world = c["camera_to_world"]\n print(c["camera_coordinates"], c["world_coordinates"])\n # 先核对坐标约定和K是否逐帧,不额外套一次坐标轴翻转。\n\nfrom egorender_reader import unpack_class_frame\nwith np.load(package / "masks_visible_2bit.npz", allow_pickle=False) as m:\n width, height = map(int, m["image_size"])\n classes = unpack_class_frame(m["packed_classes"][0], (height, width))\n print(m["classes"], m["bitorder"])\n # 当前常见: 0背景 / 1人体及衣物 / 2交互物体 / 3桌面;以文件为准。`;
89
+ const native=`with np.load(package / "mano_parameters.npz", allow_pickle=False) as h:\n print(h.files)\n left_pose = h["left_pose"]\n left_translation = h["left_trans"]\n source_frames = h["source_frames"]\n # left/right_rot, pose, trans, shape来自原始数据集参数。\n # 参数化/手均值/坐标系遵循原始模型与包内来源,不把shape当拟合真值。\n\n# V005辅助附件不是render输入:\nfit_ref = json.loads((legacy_package / "mano_fit_current.json").read_text())\n# 按fit_ref和对应合同加载参数;左手必须搭配V005修正基底。`;
90
+ const relocation=`from egorender_reader import DatasetLocations\n\nloc = DatasetLocations(package / "artifact_locations.json")\n# 给定包内记录的 {"path": ..., "sha256": ...} 引用:\nresolved = loc.read_bound_json(binding)\n# 先检查原JSON字节SHA,再解析历史路径及path-keyed字典。\n# 不覆盖immutable报告,也不要通过字符串替换伪造新SHA。`;
91
+ return `${section('文件与语义',table(['通道','文件','表示','读取约束'],content.channels.map(r=>`<tr>${r.map(x=>`<td>${esc(x)}</td>`).join('')}</tr>`)))}
92
+ <section class="synth-section"><div class="synth-toolbar"><h2>Python读取</h2><a href="downloads/egorender_reader.py" download>下载读取器.py</a></div><div class="synth-steps">${code('1. 按包索引定位',index)}${code('2. 两套深度与有效掩码',depth)}${code('3. 相机与可见分割',camera)}${code('4. 原始手参数与辅助拟合分开',native)}${code('5. 历史路径显式解析',relocation)}</div></section>
93
+ ${section('训练边界',content.quality.map((q,i)=>`<details ${i===0?'open':''}><summary>${['完成与训练资格','公开split与视角','历史深度格式','防止变体泄漏','模拟传感器边界','数据许可'][i]}</summary><p>${esc(q)}</p></details>`).join(''))}
94
+ ${section('来源',`<div class="synth-links">${content.references.map(([name,url])=>`<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${esc(name)}</a>`).join('')}</div>`)}`;
95
+ }
96
+ function samplesView() {
97
+ return `${section('Hard Cases · V006',`<div class="synth-facts"><span>三类各 <b>5</b> 条</span><span><b>10,838</b> 主源帧</span><span>执行修订 <b>320</b></span></div><div class="synth-samples">${content.samples.map(s=>`<figure><a href="${esc(s.image)}" target="_blank" rel="noopener"><img src="${esc(s.image)}" alt="${esc(s.title)}的实际合成场景预览" loading="lazy"></a><figcaption><b>${esc(s.title)}</b><small>${esc(s.source)}</small><p>${esc(s.note)}</p></figcaption></figure>`).join('')}</div>`)}
98
+ <div class="synth-callout">这些是同源动作的历史参考帧,不是当前全部随机变体的逐帧验收。镜面样例在完整生产前另外加入固定种子的污渍材质。</div>
99
+ ${section('样本组织',table(['类别','场景来源','读取差异'],[
100
+ '<tr><td>bimanual_crossing</td><td>公开源动作中的真实双手交叉</td><td>不改变原始手部运动来制造交叉</td></tr>',
101
+ '<tr><td>mirror_reflection</td><td>真实镜面几何与反射</td><td>镜面深度为物理首交点;反射的人不新增instance</td></tr>',
102
+ '<tr><td>multiple_people</td><td>多个源人体及各自衣物</td><td>检查scene_instances.json的每人源时钟;主源帧数不能直接当所有演员帧总数</td></tr>'
103
+ ]))}`;
104
+ }
105
+ function paint() {
106
+ if (!root || !content)return;
107
+ const body=tab()==='versions'?versionsView():tab()==='reading'?readingView():tab()==='samples'?samplesView():progressView();
108
+ root.innerHTML=header()+body;
109
+ root.querySelector('#synth-refresh').onclick=()=>refresh();
110
+ root.querySelectorAll('[data-version]').forEach(b=>b.onclick=()=>{selectedVersion=b.dataset.version;paint();});
111
+ const filter=root.querySelector('#synth-job-filter');if(filter)filter.onchange=()=>{selectedJobs=filter.value;updateProgress();};
112
+ const search=root.querySelector('#synth-registry-search');if(search)search.oninput=()=>{registryQuery=search.value;registryPage=0;updateRegistry();};
113
+ root.querySelectorAll('[data-copy]').forEach(b=>b.onclick=async()=>{try{await navigator.clipboard.writeText(b.parentNode.querySelector('code').textContent);b.title='已复制';b.setAttribute('aria-label','已复制');}catch(_){b.title='复制失败';}});
114
+ updateFreshness();updateProgress();updateRegistry();icons();
115
+ }
116
+ async function refresh() {
117
+ if (!content || !root || request)return;
118
+ const own=generation,button=root.querySelector('#synth-refresh');button.disabled=true;
119
+ const controller=new AbortController();request=controller;const timeout=setTimeout(()=>controller.abort(),10000);
120
+ try {
121
+ const response=await fetch(content.status_url+'?t='+Date.now(),{cache:'no-store',signal:controller.signal});
122
+ if(!response.ok)throw new Error('HTTP');const value=await response.json();if(!valid(value))throw new Error('schema');
123
+ if(own!==generation)return;
124
+ if(!snapshot || value.collected_at>=snapshot.collected_at)snapshot=value;
125
+ updateFreshness();updateProgress();
126
+ } catch(_) { if(own===generation)updateFreshness(true); }
127
+ finally { clearTimeout(timeout);if(request===controller)request=null;if(own===generation&&root)root.querySelector('#synth-refresh').disabled=false; }
128
+ }
129
+ function unmount() { generation++;clearInterval(timer);clearInterval(ageTimer);if(request)request.abort();request=null;root=null; }
130
+ async function mount() {
131
+ root=document.getElementById('synthetic-root');const own=generation;
132
+ try {
133
+ if(!content){
134
+ const responses=await Promise.all(['egorender','egorender-registry','egorender-progress'].map(n=>fetch('data/'+n+'.json')));
135
+ if(!responses[0].ok)throw new Error('content');
136
+ content=await responses[0].json();registry=responses[1].ok?await responses[1].json():[];
137
+ const cached=responses[2].ok?await responses[2].json():null;if(valid(cached)&&(!snapshot||cached.collected_at>snapshot.collected_at))snapshot=cached;
138
+ }
139
+ if(own!==generation || !root)return;
140
+ paint();refresh();timer=setInterval(refresh,60000);ageTimer=setInterval(()=>updateFreshness(),15000);
141
+ } catch(_) {if(root)root.innerHTML='<p class="synth-error">合成数据文档暂不可用,请刷新重试。</p>';}
142
+ }
143
+ window.EgoRendererPage={render:()=>'<section class="view synth" id="synthetic-root"><div class="synth-skeleton">正在读取合成数据…</div></section>',mount,unmount};
144
+ })();
src/vendor/lucide.min.js ADDED
The diff for this file is too large to render. See raw diff