"""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
', "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)