"""Four more Image-to-Scene cases (user, 2026-10-04: "然后image2scene多找几个case放上去"). Picked from the 75 public cases for a clean reference image and a perfect (1.000) repair whose saved scene is the one the scored run reloaded, so it can be shown in 3D. Each card shows that repair; the case explorer adds the other exported agents. The scenes were staged and exported to GLB on serv6 (code4scene-video/scripts/stage_i2s_more.py, export_glb_more.sh), then compressed as website/compress_glb.sh does (meshopt geometry, 512 px WebP, Image-to-Scene nodes kept). Scores, judge views and reference images are the film kit's (kit/host/film/i2s.json, collected by kit/collect_i2s.py); targets and operations are the testset label.json corruption contracts; the reference cameras are the public cameras.json. Each step asserts it found what it edits. GLB=__.glb> python3 scripts/i2s_more_cases.py """ from __future__ import annotations import hashlib, json, os, re, shutil, sys from pathlib import Path import numpy as np from bs4 import BeautifulSoup ROOT = Path(__file__).resolve().parent.parent KIT = Path.home() / "Downloads/code4scene-video/kit/host/film/i2s.json" PUB = Path.home() / "Downloads/Code4Scene/benchmark/public/image-to-scene" GLB = Path(os.environ["GLB"]) V = "20261004i2smore" KEY = bytes.fromhex("5c2f9e71d3a84b06e1f7c29a38d05b64a7193ce8f2046db95e81c73a2df6904b") # website/pack_models.py # case, title, card agent, the target actors' GLB node names, the label.json corruption contract (authored_operations), a line # on what was done. The exporter names nodes by actor label; where that differs from the label.json label, the node is the # one whose transform differs between the ground truth and the input GLB: comp-72's StaticMeshActor_565 is SM_Umbrella_5, # comp-51's BP_Mailbox_01_C_443 is BP_Mailbox_01 (as build_site.py's GLB_ALIAS for comp-31). CASES = [ ("comp-72-suburban-umbrella-pair-repair-v2", "Suburban Umbrella Pair", "opus", ["SM_Umbrella_5"], [{"type": "duplication", "label": "StaticMeshActor_565", "new_label": "StaticMeshActor_565_CorruptDuplicate", "delta_cm": [-300.0, 220.0, 0.0], "yaw_deg": -28.0, "factor": 0.82}, {"type": "rotation", "label": "StaticMeshActor_565", "yaw_deg": 46.0}, {"type": "scaling", "label": "StaticMeshActor_565", "factor": [1.28, 1.28, 1.18]}], "The patio umbrella turned 46° and enlarged, plus an unintended smaller duplicate."), ("comp-17-rosies-service-bin-reset-v3", "Rosie's Service Bin", "astra", ["SM_Trashcan3"], [{"type": "translation", "label": "SM_Trashcan3", "delta_cm": [260.0, 180.0, 0.0]}, {"type": "rotation", "label": "SM_Trashcan3", "yaw_deg": 65.0}, {"type": "scaling", "label": "SM_Trashcan3", "factor": 1.45}], "A trash can moved 3.2 m, turned 65° and scaled up 1.45×."), ("comp-18-rosies-roadside-billboard-reset-v3", "Rosie's Roadside Billboard", "astra", ["SM_Billboard02"], [{"type": "translation", "label": "SM_Billboard02", "delta_cm": [650.0, 400.0, 0.0]}, {"type": "rotation", "label": "SM_Billboard02", "yaw_deg": 55.0}, {"type": "scaling", "label": "SM_Billboard02", "factor": 1.35}], "The billboard moved 7.6 m, turned 55° and scaled up 1.35×."), ("comp-51-suburban-driveway-mailbox-recovery-v2", "Suburban Driveway Mailbox", "fable", ["BP_Mailbox_01"], [{"type": "removal", "label": "BP_Mailbox_01_C_443"}], "The driveway mailbox removed."), ] def one(found, what): assert found, f"not found: {what}" return found def mid(glb_name): return hashlib.sha256(b"c4s-model:" + glb_name.encode()).hexdigest()[:20] def pack(): """website/pack_models.py for the new GLBs: models/.bin, XORed with the page's keystream""" table = {} for g in sorted(GLB.glob("*.glb")): assert any(g.name.startswith(c[0] + "__") for c in CASES), g.name a = np.frombuffer(g.read_bytes(), np.uint8); idx = np.arange(a.size, dtype=np.int64) ks = np.frombuffer(KEY, np.uint8)[idx % 32] ^ ((idx // 32) * 131 & 255).astype(np.uint8) dst = ROOT / "models" / f"{mid(g.name)}.bin"; dst.write_bytes((a ^ ks).tobytes()) table[g.name] = f"models/{dst.name}?v={int(dst.stat().st_mtime)}" print("packed", len(table), "models") return table def ref_cam(setting, case): v = next(v for v in json.loads((PUB / setting / case / "cameras.json").read_text())["views"] if v["name"] == "reference") ue = lambda p: [p[0] / 100, p[2] / 100, p[1] / 100] # UE (x, y, z) cm -> glTF (x, z, y) m, as build_site.py return {"pos": ue(v["location_cm"]), "target": ue(v["target_cm"]), "hfov": v["fov_deg"]} def site_data(models): p = ROOT / "data/site.js" S = json.loads(p.read_text().split("=", 1)[1].rstrip().rstrip(";")) assert not {c[0] for c in CASES} & {c["id"] for c in S["cases"]}, "already added" kit = {c["case"]: c for c in json.loads(KIT.read_text())} q = lambda rel: f"{rel}?v={V}" for case, title, card, targets, ops, note in CASES: c = kit[case]; d = f"media/i2s/{case}" assert all((ROOT / d / a["img"]).exists() for a in c["agents"] if a["img"]), case ags = [{"key": a["key"], "s": a["s"], "f1": a["f1"], "phys": a["phys"], "tp": a["tp"], "fp": a["fp"], "fn": a["fn"], "img": q(f"{d}/{a['img']}") if a["img"] else None, "glb": models.get(f"{case}__{a['key']}.glb")} for a in sorted(c["agents"], key=lambda a: -a["s"])] best = next(a for a in ags if a["key"] == card) assert best["s"] >= 0.999 and best["glb"], (case, card) S["cases"].append({"id": case, "track": "i2s", "title": title, "note": note, "prompt": c["task"]["prompt"], "targets": targets, "duplicates": [o["new_label"] for o in ops if o["type"] == "duplication"], "corruption": ops, "gt_glb": one(models.get(f"{case}__gt.glb"), f"{case} gt"), "input_glb": one(models.get(f"{case}__input.glb"), f"{case} input"), "ref_cam": ref_cam(c["setting"], case), "gt": q(f"{d}/{c['gt']}"), "input": q(f"{d}/{c['input']}") if c.get("input") else None, "refs": [q(f"{d}/{r}") for r in c["refs"]], "ops": c["ops"], "agents": ags}) p.write_text("window.SITE = " + json.dumps(S) + ";\n") n3d = sum(1 for c in S["cases"] for a in c["agents"] if a.get("glb")) + sum(1 for c in S["cases"] for k in ("gt_glb", "input_glb") if c.get(k)) cp = ROOT / "cases.html"; cs = cp.read_text() cs, n = re.subn(r"\d+ cases, \d+ scenes in 3D", f'{len(S["cases"])} cases, {n3d} scenes in 3D', cs); assert n == 1 cs, n = re.subn(r"(data/site\.js)\?v=\w+", lambda m: f"{m.group(1)}?v={V}", cs); assert n == 1 cp.write_text(cs) print("data/site.js:", len(S["cases"]), "cases ·", n3d, "scenes in 3D") return S def index(S): p = ROOT / "index.html" soup = BeautifulSoup(p.read_text(), "html.parser") grid = one(soup.find(id="cases").find(class_="case-grid", attrs={"data-view": "i2s"}), "Image-to-Scene case grid") name = {k: v["name"] for k, v in S["agents"].items()} for case, title, card, *_ in CASES: c = next(c for c in S["cases"] if c["id"] == case); a = next(a for a in c["agents"] if a["key"] == card) grid.append(BeautifulSoup( f'
' 'Loading 3D scene…Drag to orbit · Scroll to zoom
' f'Image-to-Scene

{title}

{name[card]} · score {a["s"]:.3f}

' f'Compare all 14 agents ↗
', "html.parser")) for t in soup.find_all("script", src=re.compile(r"^data/site\.js")): t["src"] = f"data/site.js?v={V}" p.write_text(str(soup)) print("index.html:", len(grid.find_all("article", recursive=False)), "Image-to-Scene cards") def texture_share(packed): """the share of a packed GLB's bytes that are embedded images""" a = np.frombuffer(packed.read_bytes(), np.uint8); idx = np.arange(a.size, dtype=np.int64) raw = (a ^ (np.frombuffer(KEY, np.uint8)[idx % 32] ^ ((idx // 32) * 131 & 255).astype(np.uint8))).tobytes() n = int.from_bytes(raw[12:16], "little"); g = json.loads(raw[20:20 + n]) views = {im["bufferView"] for im in g.get("images", []) if "bufferView" in im} return sum(g["bufferViews"][v]["byteLength"] for v in views) / len(raw) def progressive(S): """build_progressive.py's geometry-first derivative for each new card scene (repair, input, ground truth)""" sys.path.insert(0, str(ROOT / "scripts")) import build_progressive as bp urls = [] for case, _, card, *_ in CASES: c = next(c for c in S["cases"] if c["id"] == case) urls += [next(a for a in c["agents"] if a["key"] == card)["glb"], c["input_glb"], c["gt_glb"]] # a derivative pays off only when textures are a real share of the file: the suburban scenes are ~98% geometry, so theirs # would duplicate 18 MB for no earlier first frame; those cards load the packed model directly, as before progressive-v1 urls = [u for u in urls if texture_share(ROOT / u.split("?")[0]) >= 0.1] for u in urls: # the converter reads its cache before the live Space, which does not have these files yet shutil.copyfile(ROOT / u.split("?")[0], bp.CACHE / u.split("/")[-1].split("?")[0]) m = ROOT / "data/progressive-scenes.json"; d = json.loads(m.read_text()) d.update(dict(bp.convert(u) for u in urls)) m.write_text(json.dumps(d, indent=2) + "\n") cu = ROOT / "scripts/progressive-card-urls.json"; cl = json.loads(cu.read_text()); cu.write_text(json.dumps(cl + urls) + "\n") print("data/progressive-scenes.json:", len(d), "card scenes") if __name__ == "__main__": if os.environ.get("ONLY") == "progressive": # re-run the last step on an already-edited page progressive(json.loads((ROOT / "data/site.js").read_text().split("=", 1)[1].rstrip().rstrip(";"))) else: models = pack(); S = site_data(models); index(S); progressive(S)