import { esc, number, icon, enhance, table as sharedTable } from "./ui.js"; import { versionsView, versionFromHash } from "./versions.js?v=history-20261103"; let content, versionHistory, registry = [], snapshot, root, timer, ageTimer, request, generation = 0; let selectedCampaign = null, selectedVersion = "006", selectedJobs = "active", registryQuery = "", registryPage = 0; 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"; const table = (heads, rows) => sharedTable(heads, rows, "synth-table"); 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: "待定点修复", WAIT_UPSTREAM_ASSET: "等待上游资产", }; const sections = [ ["progress", "进度"], ["versions", "版本"], ["reading", "数据读取"], ["samples", "样例"], ]; const tab = () => location.hash.split("/")[1] || "progress"; function section(title, body) { return `

${esc(title)}

${body}
`; } function code(title, text) { return `

${esc(title)}

${icon("copy", "复制代码", "data-copy")}
${esc(text)}
`; } function icons() { enhance(); } export function valid(value) { const c = value && value.counts; return ( value && value.schema === "egorenderer.public_progress.v1" && Number.isFinite(value.collected_at) && c && ["complete", "running", "ready", "failed", "total"].every( (k) => Number.isInteger(c[k]) && c[k] >= 0, ) && c.total > 0 && c.complete + c.running + c.ready + c.failed === c.total ); } function header() { return `

EgoRenderer

V006
读取快照${icon("refresh-cw", "刷新进度", 'id="synth-refresh"')}
`; } function progressView() { return `
${section("数据范围", '
')}

当前任务

主机与存储
${section("最近归档", '
')}`; } export function progressState(value, error = false, now = Date.now() / 1000) { if (!value) return { state: "error", prefix: error ? "状态暂不可用" : "读取快照" }; if (error) return { state: "stale", prefix: "连接中断 · " }; if (now - value.collected_at > 180) return { state: "stale", prefix: "更新已过期 · " }; const c = value.counts; if ( c.complete === c.total && c.running === 0 && c.ready === 0 && c.failed === 0 ) return { state: "complete", prefix: "生成队列结束 · " }; if ( !value.queue_alive && c.waiting_assets > 0 && !c.running && !c.ready && !c.failed ) return { state: "waiting", prefix: "等待上游资产 · " }; if (!value.queue_alive) return { state: "stopped", prefix: "队列已停 · " }; if (value.admission_held) return { state: "stopped", prefix: "暂停新增 · " }; return { state: "live", prefix: "" }; } export function campaignSnapshot(value, selected = null) { if (!value) return null; const choice = selected || value.default_campaign || "ARCTIC"; const view = choice === "OakInk2" && value.oakink_render ? value.oakink_render : { dataset: "ARCTIC" }; return { ...value, ...view, arctic_render: { counts: value.counts, scope: value.scope, coverage: value.coverage, known_gt_missing: value.known_gt_missing, }, }; } function updateFreshness(error) { if (!root) return; const status = root.querySelector("#synth-status"), label = root.querySelector("#synth-freshness"); if (!snapshot) { status.dataset.state = "error"; label.textContent = error ? "状态暂不可用" : "读取快照"; return; } const current = campaignSnapshot(snapshot, selectedCampaign); const selector = root.querySelector("#synth-campaign"); selector.querySelector('option[value="OakInk2"]').disabled = !snapshot.oakink_render; selector.value = current.dataset; const display = progressState(current, error); status.dataset.state = display.state; label.textContent = display.prefix + clock(snapshot.collected_at); root.querySelector("#synth-execution").textContent = "执行 " + current.execution_revision; } function updateProgress() { if (!root || tab() !== "progress") return; if (!snapshot) { root.querySelector("#synth-summary").innerHTML = '

状态暂不可用。不会以零值替代上次进度。

'; return; } const s = campaignSnapshot(snapshot, selectedCampaign), c = s.counts; const metrics = [ ["渲染与深度完成", `${number(c.complete)} / ${number(c.total)}`], ["运行中", number(c.running)], ["待领取", number(c.ready)], ["失败", number(c.failed)], ]; if (c.waiting_assets) metrics.push(["待上游资产", number(c.waiting_assets)]); root.querySelector("#synth-summary").dataset.columns = metrics.length; root.querySelector("#synth-summary").innerHTML = metrics .map( ([k, v]) => `
${v}${k}
`, ) .join(""); const progress = root.querySelector("#synth-progress"); progress.setAttribute("aria-label", s.dataset + "包生成进度"); progress.setAttribute("aria-valuemin", "0"); progress.setAttribute("aria-valuemax", c.total); progress.setAttribute("aria-valuenow", c.complete); progress.innerHTML = [ ["done", c.complete], ["running", c.running], ["failed", c.failed], ] .map( ([cl, n]) => ``, ) .join(""); root.querySelector("#synth-facts").innerHTML = `完成 ${((100 * c.complete) / c.total).toFixed(1)}%已生成帧 ${number(s.coverage.completed_variant_frames)} / ${number(s.scope.expected_variant_frames)}最近1小时 ${number(s.throughput.packages_last_hour)}包队列预计(不含失败) ${s.throughput.queued_eta_hours == null ? "—" : number(s.throughput.queued_eta_hours) + "小时"}`; root.querySelector("#synth-notices").innerHTML = `
训练资格待确认 · 辅助拟合待交付${s.dataset === "OakInk2" ? " · 下半身为生成结果" : ""}${s.known_gt_missing.length ? ` · ${s.known_gt_missing.length} 个历史包缺GT深度` : ""}
`; const oak = s.oakink_assets, hc = s.hardcases; root.querySelector("#synth-cohorts").innerHTML = table( ["数据", "规模", "当前口径"], [ `ARCTIC · V006${number(s.arctic_render.counts.complete)} / ${number(s.arctic_render.counts.total)}包${number(s.arctic_render.scope.source_frames)}源帧;每条动作1份${s.arctic_render.known_gt_missing.length ? ";" + s.arctic_render.known_gt_missing.length + "个历史包缺GT深度" : ""}`, `Hard Cases · V006${hc.complete} / ${hc.expected}条样例${number(hc.frames)}主源帧;三类各5条,独立于主队列`, `OakInk2 · V006${snapshot.oakink_render ? number(snapshot.oakink_render.counts.complete) + " / " + number(snapshot.oakink_render.counts.total) + "包" : number(oak.total) + "源"}${number(oak.counts.COMPLETE || 0)}条人体资产就绪${snapshot.oakink_render ? ";" + number(snapshot.oakink_render.counts.waiting_assets) + "条待上游;每条动作1份" : ";尚无最终合成包计数"}`, 'GRAB · V0051,314源组 / 2,425包历史参数附件更新完成;V006新深度尚未据此宣称完成', ], ); const selected = root.querySelector("#synth-job-filter"); selected.value = selectedJobs; const jobs = s.jobs.filter( (j) => selectedJobs === "all" || (selectedJobs === "failed" ? j.state === "FAILED" : selectedJobs === "inputs" ? j.state === "WAIT_UPSTREAM_ASSET" : ["RUNNING", "RECOVERING_DEPTH"].includes(j.state)), ); const multi = s.scope.variants > 1; root.querySelector("#synth-jobs").innerHTML = jobs.length ? table( ["源动作", ...(multi ? ["变体"] : []), "帧数", "存储", "阶段"], jobs.map( (j) => `${esc(j.source)}${multi ? `${j.variant}` : ""}${number(j.frames)}${esc(j.storage || "待准入")}${esc(phases[j.phase] || j.phase)}`, ), ) : '

当前没有此类任务。

'; const gpu = s.hardware.gpus || []; root.querySelector("#synth-resources").innerHTML = gpu .map( (g) => `
GPU ${g.index}
${g.utilization}%
显存 GiB
${number(g.used_mib / 1024)} / ${number(g.total_mib / 1024)}
`, ) .join(""); root.querySelector("#synth-storage").innerHTML = `CPU(主机) ${number(s.hardware.cpu_percent)}%SSD预留 ${number(s.storage.ssd_reserved_gib)} / ${number(s.storage.ssd_budget_gib)} GiBSSD可用 ${number(s.storage.ssd_available_gib)} GiBNAS可用 ${number(s.storage.nas_available_gib / 1024)} TiBNAS通道 ${s.storage.nas_active} / ${s.storage.nas_limit}`; root.querySelector("#synth-recent").innerHTML = table( ["源动作", ...(multi ? ["变体"] : []), "归档时间"], s.recent.map( (j) => `${esc(j.source)}${multi ? `${j.variant}` : ""}${clock(j.at)}`, ), ); } function versionPage() { return `${versionsView(content, versionHistory, selectedVersion)} ${section( "V006关键执行修订", table( ["修订", "主题", "变化"], content.milestones.map( (r) => `${r.map((x) => `${esc(x)}`).join("")}`, ), ), )}

执行登记簿

登记状态不是实时任务状态;执行修订不等同数据发布版本。
`; } function updateRegistry() { const target = root.querySelector("#synth-registry"); if (!target) return; const rows = registry .filter((r) => [r.id, r.title, r.status, r.scope] .join(" ") .toLowerCase() .includes(registryQuery.toLowerCase()), ) .slice() .reverse(); registryPage = Math.max( 0, Math.min(registryPage, Math.ceil(rows.length / 25) - 1), ); const start = registryPage * 25; target.innerHTML = table( ["修订", "模块", "登记状态 / 范围"], rows .slice(start, start + 25) .map( (r) => `${esc(r.id)}${esc(r.title)}
${esc(r.status)}

${esc(r.scope || "范围见对应冻结合同。")}

SHA256 ${esc(r.sha256)}

`, ), ) + `
${rows.length ? start + 1 : 0}–${Math.min(start + 25, rows.length)} / ${rows.length}${icon("chevron-left", "上一页", "data-registry-prev")}${icon("chevron-right", "下一页", "data-registry-next")}
`; const prev = target.querySelector("[data-registry-prev]"), next = target.querySelector("[data-registry-next]"); prev.disabled = registryPage === 0; next.disabled = start + 25 >= rows.length; prev.onclick = () => { registryPage--; updateRegistry(); }; next.onclick = () => { registryPage++; updateRegistry(); }; icons(); } function readingView() { const active = campaignSnapshot(snapshot, selectedCampaign); const isOak = active?.dataset === "OakInk2"; const indexPath = isOak ? active.package_index : "ARCTIC/packages/final_surface279_v287_20261030/INDEX.json"; const index = `import json, os\nfrom pathlib import Path\n\nroot = Path(os.environ["EGORENDER_ROOT"])\nindex = json.loads((root / "${indexPath}").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 是渲染终态;另查深度缺口与辅助标注。`; 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。`; 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桌面;以文件为准。`; const native = isOak ? `descriptor = json.loads((package / "native_mano_descriptor.json").read_text())\nassert descriptor["quaternion_layout"] == "wxyz"\nwith np.load(package / "mano_parameters.npz", allow_pickle=False) as h:\n print(h.files) # 保留原始字段,不转成ARCTIC的axis-angle表示\nsource_offsets = np.load(package / "source_offsets.npy", allow_pickle=False)\nwith np.load(package / "native_object_poses.npz", allow_pickle=False) as p:\n object_ids = p["object_ids"]\n object_to_oakink = p["object_to_oakink"]\nwith np.load(package / "object_membership.npz", allow_pickle=False) as m:\n print(m.files)\n# 原始source_offsets与30fps动作索引分别保留。\n# 物体姿态在原始OakInk坐标;按descriptor.transform_chain及metadata.source_to_world变换一次。\nassert meta["lower_motion_is_gt"] is False` : `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修正基底。`; 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。`; return `${section( "文件与语义", table( ["通道", "文件", "表示", "读取约束"], content.channels.map( (r) => `${r.map((x) => `${esc(x)}`).join("")}`, ), ), )}

Python读取

下载读取器.py
${code("1. 按包索引定位", index)}${code("2. 两套深度与有效掩码", depth)}${code("3. 相机与可见分割", camera)}${code("4. 原始手参数与辅助拟合分开", native)}${code("5. 历史路径显式解析", relocation)}
${section("训练边界", content.quality.map((q, i) => `
${["完成与训练资格", "公开split与视角", "历史深度格式", "防止变体泄漏", "模拟传感器边界", "数据许可"][i]}

${esc(q)}

`).join(""))} ${section("来源", ``)}`; } function samplesView() { return `${section("Hard Cases · V006", `
三类各 5 条10,838 主源帧执行修订 320
${content.samples.map((s) => `
${esc(s.title)}的实际合成场景预览
${esc(s.title)}${esc(s.source)}

${esc(s.note)}

`).join("")}
`)}
这些是同源动作的历史参考帧,不是当前全部随机变体的逐帧验收。镜面样例在完整生产前另外加入固定种子的污渍材质。
${section( "样本组织", table( ["类别", "场景来源", "读取差异"], [ "bimanual_crossing公开源动作中的真实双手交叉不改变原始手部运动来制造交叉", "mirror_reflection真实镜面几何与反射镜面深度为物理首交点;反射的人不新增instance", "multiple_people多个源人体及各自衣物检查scene_instances.json的每人源时钟;主源帧数不能直接当所有演员帧总数", ], ), )}`; } function paint() { if (!root || !content) return; const body = tab() === "versions" ? versionPage() : tab() === "reading" ? readingView() : tab() === "samples" ? samplesView() : progressView(); root.innerHTML = header() + body; root.querySelector("#synth-refresh").onclick = () => refresh(); root.querySelector("#synth-campaign").onchange = (event) => { selectedCampaign = event.target.value; if (tab() === "reading") { paint(); return; } updateFreshness(); updateProgress(); }; const filter = root.querySelector("#synth-job-filter"); if (filter) filter.onchange = () => { selectedJobs = filter.value; updateProgress(); }; const search = root.querySelector("#synth-registry-search"); if (search) search.oninput = () => { registryQuery = search.value; registryPage = 0; updateRegistry(); }; 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 = "复制失败"; } }), ); updateFreshness(); updateProgress(); updateRegistry(); icons(); } async function refresh() { if (!content || !root || request) return; const own = generation, button = root.querySelector("#synth-refresh"); button.disabled = true; const controller = new AbortController(); request = controller; const timeout = setTimeout(() => controller.abort(), 10000); try { const response = await fetch(content.status_url + "?t=" + Date.now(), { cache: "no-store", signal: controller.signal, }); if (!response.ok) throw new Error("HTTP"); const value = await response.json(); if (!valid(value)) throw new Error("schema"); if (own !== generation) return; if (!snapshot || value.collected_at >= snapshot.collected_at) snapshot = value; updateFreshness(); updateProgress(); } catch (_) { if (own === generation) updateFreshness(true); } finally { clearTimeout(timeout); if (request === controller) request = null; if (own === generation && root) root.querySelector("#synth-refresh").disabled = false; } } function unmount() { generation++; clearInterval(timer); clearInterval(ageTimer); if (request) request.abort(); request = null; root = null; } async function mount() { root = document.getElementById("synthetic-root"); selectedVersion = versionFromHash(location.hash); const own = generation; try { if (!content) { const responses = await Promise.all( ["egorender", "egorender-registry", "egorender-progress", "egorender-versions"].map((n) => fetch("data/" + n + ".json"), ), ); if (!responses[0].ok || !responses[3].ok) throw new Error("content"); versionHistory = await responses[3].json(); content = await responses[0].json(); registry = responses[1].ok ? await responses[1].json() : []; const cached = responses[2].ok ? await responses[2].json() : null; if ( valid(cached) && (!snapshot || cached.collected_at > snapshot.collected_at) ) snapshot = cached; } if (own !== generation || !root) return; paint(); refresh(); timer = setInterval(refresh, 60000); ageTimer = setInterval(() => updateFreshness(), 15000); } catch (_) { if (root) root.innerHTML = '

合成数据文档暂不可用,请刷新重试。

'; } } export default { render: () => '
正在读取合成数据…
', mount, unmount, };