bert_simpson / forgebench /code /eval /heldout /prepare_heldout.py
Ronaldo-GOAT's picture
FORGE3DBench: final eval protocol + eval_final.py, batched Ours inference, held-out view tars, missing-object lists, README
cca6827 verified
Raw History Blame Contribute Delete
7.03 kB
#!/usr/bin/env python3
"""Inventory + materialise HELD-OUT ground-truth views (never fed to any model).
Output layout mirrors an exp dir so the evaluator reads held-out views with the
same code path as input views (inputs/<obj>_<tag>.png RGBA crop whose alpha is
the GT mask; renders/<obj>_<tag>.npz with fx,fy,cx,cy,c2w_cv,bbox,res).
FB150 (reuses build_fb150.build_view -> identical crop/mask/camera code as the
4 input views):
held-out = every scene cam (of 24) NOT in views4 whose modal visibility
vis>0 and modal pixels >= MIN_PX. The alpha written by build_view is the
modal (visible) mask & depth>0 -> occluders and occluded object parts are
excluded. Tag = h<cam:02d>.
Omni300:
saved held-out = top, top2, bottom, bottom2 (already in the exp, used in place).
pool held-out = every other official OmniObject3D render (100/obj) with
>=400 valid px (same threshold as build_omni8v_rand) and not one of the 8
saved views. Materialised with omni_adapter.load_transforms/read_view/
crop_bbox + the selection's (center_r, scale_r), exactly as
build_omni8v_rand.build. Tag = p<idx:03d>. (depth_mm is not stored: the
evaluator never reads it.)
"""
import argparse, json, os, sys
from pathlib import Path
from concurrent.futures import ProcessPoolExecutor
import numpy as np
HERE = Path("/lp-dev/jonghoon/mv-mesh/.debug/eval_heldout_nv")
FB150 = Path("/lp-dev/jonghoon/mv-mesh/.debug/forgebench_eval/fb150")
VIEWS4 = Path("/home/nvidia/jonghoon/mv-mesh/.debug/forgebench150/views_4v.json")
OMNI = Path("/lp-dev/jonghoon/mv-mesh/exp_faithfulness/omni3d300_rand")
MIN_PX = 400
OMNI_IN4 = ["front", "side", "back", "oside"]
OMNI_SAVED_HO = ["top", "top2", "bottom", "bottom2"]
# ------------------------------------------------------------------ FB150
def _fb_work(a):
s, v4, out = a
sys.path.insert(0, str(FB150))
import build_fb150 as BF # build_view (same code path as input views)
from PIL import Image
vis = v4["all_cam_visibility"]; ins = set(v4["cams"])
recs = []
for ci, vv in enumerate(vis):
if ci in ins or vv <= 0:
continue
tag = f"h{ci:02d}"
try:
BF.build_view(s, ci, tag, Path(out))
except RuntimeError: # empty modal mask after depth>0
continue
png = Path(out) / "inputs" / f"{s['object']}_{tag}.png"
px = int((np.asarray(Image.open(png))[..., 3] > 0).sum())
if px < MIN_PX:
png.unlink(); (Path(out) / "renders" / f"{s['object']}_{tag}.npz").unlink()
continue
recs.append(dict(tag=tag, cam=ci, vis=float(vv), px=px, root=str(out)))
return s["object"], recs
def prep_fb(workers):
out = HERE / "fb150_heldout"
for d in ("inputs", "renders"):
(out / d).mkdir(parents=True, exist_ok=True)
sel = json.load(open(FB150 / "exp_4v/selection.json"))["selections"]
v4 = json.load(open(VIEWS4))["views"]
jobs = [(s, v4[s["object"]], str(out)) for s in sel]
idx = {}
with ProcessPoolExecutor(workers) as ex:
for obj, recs in ex.map(_fb_work, jobs):
idx[obj] = recs
return idx
# ------------------------------------------------------------------ Omni300
def _omni_work(a):
rec, out, pool = a
os.chdir("/lp-dev/jonghoon/mv-mesh") # omni_adapter.RAW is relative
sys.path.insert(0, "/lp-dev/jonghoon/mv-mesh/scratchpad_1k")
import imageio.v3 as iio
from omni_adapter import load_transforms, read_view, crop_bbox
obj = rec["object"]
recs = []
for t in OMNI_SAVED_HO:
png = OMNI / "inputs" / f"{obj}_{t}.png"; npz = OMNI / "renders" / f"{obj}_{t}.npz"
if png.exists() and npz.exists():
recs.append(dict(tag=t, cam=int(rec["view_idx"][t]), px=int(rec["valid_px"][t]),
root=str(OMNI), kind="saved"))
if not pool:
return obj, recs, None
B, K, frames, c2w_raw = load_transforms(rec.get("category"), obj)
c2w = c2w_raw.copy()
c2w[:, :3, 3] = (c2w_raw[:, :3, 3] - np.array(rec["center_r"])) / rec["scale_r"]
# frame check: recomputed canonical c2w of the 8 saved views == stored npz
err = max(float(np.abs(np.load(OMNI / "renders" / f"{obj}_{t}.npz")["c2w_cv"] - c2w[i]).max())
for t, i in rec["view_idx"].items())
used = set(int(i) for i in rec["view_idx"].values())
for i in range(len(frames)):
if i in used:
continue
rgba, dep, valid = read_view(B, i)
if valid.sum() < MIN_PX:
continue
bb = crop_bbox(valid); y0, y1, x0, x1 = bb
tag = f"p{i:03d}"
iio.imwrite(Path(out) / "inputs" / f"{obj}_{tag}.png", rgba[y0:y1, x0:x1])
np.savez_compressed(Path(out) / "renders" / f"{obj}_{tag}.npz", fx=K["fx"], fy=K["fy"],
cx=K["cx"], cy=K["cy"], c2w_cv=c2w[i], bbox=np.array(bb), res=800)
recs.append(dict(tag=tag, cam=i, px=int(valid.sum()), root=str(out), kind="pool"))
return obj, recs, err
def prep_omni(workers, pool):
out = HERE / "omni300_heldout"
for d in ("inputs", "renders"):
(out / d).mkdir(parents=True, exist_ok=True)
sel = json.load(open(OMNI / "selection.json"))["selections"]
idx, errs = {}, {}
with ProcessPoolExecutor(workers) as ex:
for obj, recs, err in ex.map(_omni_work, [(r, str(out), pool) for r in sel]):
idx[obj] = recs; errs[obj] = err
return idx, errs
def summ(idx, key=None):
n = [len([r for r in v if key is None or key(r)]) for v in idx.values()]
return dict(n_obj=len(n), total=int(sum(n)), min=int(min(n)), median=float(np.median(n)),
max=int(max(n)), mean=round(float(np.mean(n)), 2), n_obj_zero=int(sum(x == 0 for x in n)))
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--workers", type=int, default=32)
ap.add_argument("--no-omni-pool", dest="pool", action="store_false", default=True)
a = ap.parse_args()
fb = prep_fb(a.workers)
json.dump(fb, open(HERE / "heldout_fb150.json", "w"), indent=1)
om, errs = prep_omni(a.workers, a.pool)
json.dump(om, open(HERE / "heldout_omni300.json", "w"), indent=1)
inv = {
"fb150": {"rule": f"cams not in views4, vis>0, modal px>={MIN_PX}; mask=modal&depth>0 (build_fb150.build_view)",
"all": summ(fb), "vis>=0.40": summ(fb, lambda r: r["vis"] >= 0.40),
"vis>=0.10": summ(fb, lambda r: r["vis"] >= 0.10)},
"omni300": {"rule": "saved top/top2/bottom/bottom2 + pool (official 100-view renders, >=400 valid px, not in 8 saved)",
"saved": summ(om, lambda r: r["kind"] == "saved"),
"pool": summ(om, lambda r: r["kind"] == "pool"), "all": summ(om),
"max_c2w_err_saved_vs_recomputed": max((e for e in errs.values() if e is not None), default=None)},
}
json.dump(inv, open(HERE / "inventory.json", "w"), indent=1)
print(json.dumps(inv, indent=1))
if __name__ == "__main__":
main()