Download forgebench/code/eval/appeval/selfcheck.py from Ronaldo-GOAT/bert_simpson: direct link, hf CLI and curl.
- Browser
- Download file 6.78 kB
-
https://huggingface.co/Ronaldo-GOAT/bert_simpson/resolve/main/forgebench/code/eval/appeval/selfcheck.py
- Command line
-
hf download hf://Ronaldo-GOAT/bert_simpson/forgebench/code/eval/appeval/selfcheck.py
-
curl -L -o selfcheck.py https://huggingface.co/Ronaldo-GOAT/bert_simpson/resolve/main/forgebench/code/eval/appeval/selfcheck.py
6.78 kB
| """Self-test for the appearance+geometry harness. MUST pass before use. | |
| Checks: | |
| (1) ORACLE : GT mesh vs itself -> CD~0, F@all~1.0, VolIoU~1.0, | |
| LPIPS~0, SSIM~1, CLIP~1, PSNR very high. | |
| (2) RENDER : render a VERTEX-COLORED glb (primary path for our methods, | |
| and toys4k GT) from the input view; confirm alpha mask is | |
| sane, mask IoU vs stored depth mask is high, and rendered | |
| colours are NOT flat grey (real colour variance). Also | |
| render a UV-textured glb if available to exercise that path. | |
| Proof sheets saved to .debug/appeval/. | |
| (3) MONOTONIC : a noise-perturbed mesh scores WORSE than the oracle on | |
| EVERY metric (CD up, F down, IoU down, NC down; LPIPS up, | |
| SSIM down, CLIP down, PSNR down). | |
| Runs on 2 real toys4k objects using their GT canon.glb as the "prediction". | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import trimesh | |
| from PIL import Image | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| import render as R | |
| from geometry import geometry_metrics | |
| from appearance import appearance_metrics, composite_white | |
| EXP = Path("/lp-dev/jonghoon/mv-mesh/exp_faithfulness/toys4k") | |
| DEBUG = Path("/home/nvidia/jonghoon/mv-mesh/.debug/appeval") | |
| OBJECTS = ["apple_002", "car_006"] | |
| UV_GLB = EXP / "gen_2v" / "trellis" / "apple_002.glb" # optional UV path | |
| def perturb(mesh: trimesh.Trimesh, sigma=0.03, seed=0): | |
| m = mesh.copy() | |
| rng = np.random.default_rng(seed) | |
| m.vertices = m.vertices + rng.normal(0, sigma, size=m.vertices.shape) | |
| return m | |
| def mask_iou(a, b): | |
| a = a.astype(bool); b = b.astype(bool) | |
| return float((a & b).sum() / max((a | b).sum(), 1)) | |
| def check_object(obj): | |
| print(f"\n=== object: {obj} ===") | |
| gt_path = EXP / "renders" / f"{obj}_canon.glb" | |
| gt = trimesh.load(str(gt_path), force="mesh", process=False) | |
| gt_gl = R.prepare_mesh(gt) | |
| assert gt_gl.mode == "vertex", f"GT should be vertex-coloured, got {gt_gl.mode}" | |
| # ---- (2) render vertex-coloured mesh from input view ---- | |
| z = np.load(EXP / "renders" / f"{obj}_front.npz") | |
| K = {k: float(z[k]) for k in ("fx", "fy", "cx", "cy")} | |
| res = int(z["res"]) | |
| c2w = z["c2w_cv"] | |
| depth_mask = z["depth_mm"] > 0 | |
| rgba = R.render_input_view(gt_gl, K, c2w, res, res).cpu().numpy() | |
| alpha = rgba[..., 3] > 0.5 | |
| iou = mask_iou(alpha, depth_mask) | |
| obj_rgb = rgba[..., :3][alpha] | |
| col_std = float(obj_rgb.std(0).mean()) | |
| col_mean = obj_rgb.mean(0) | |
| print(f" [render] mask IoU vs depth = {iou:.4f} | " | |
| f"colour mean={np.round(col_mean,3).tolist()} std={col_std:.4f}") | |
| assert iou > 0.9, f"input-view mask IoU too low ({iou})" | |
| assert alpha.sum() > 1000, "empty render" | |
| assert col_std > 0.02, f"render looks flat grey (col_std={col_std}); " \ | |
| "vertex colours not applied!" | |
| assert not np.allclose(col_mean, [0.6, 0.6, 0.6], atol=0.02), \ | |
| "render is the flat-grey fallback colour" | |
| DEBUG.mkdir(parents=True, exist_ok=True) | |
| Image.fromarray((composite_white(rgba) * 255).astype(np.uint8)).save( | |
| DEBUG / f"selfcheck_{obj}_vertexcolor.png") | |
| # a couple orbit views for the proof sheet | |
| cams = R.orbit_cameras() | |
| orb = R.render_orbit(gt_gl, cams).cpu().numpy() | |
| sheet = np.concatenate([composite_white(orb[i]) for i in (0, 4, 8, 12, 16, 20)], axis=1) | |
| Image.fromarray((sheet * 255).astype(np.uint8)).save( | |
| DEBUG / f"selfcheck_{obj}_orbit.png") | |
| # ---- (1) ORACLE geometry ---- | |
| go = geometry_metrics(gt, gt) | |
| print(f" [oracle-geom] CD_L1={go['cd_l1']:.5f} CD_L2={go['cd_l2']:.6f} " | |
| f"F@.01={go['f01']:.4f} F@.02={go['f02']:.4f} F@.05={go['f05']:.4f} " | |
| f"NC={go['normal_consistency']:.4f} VolIoU={go['vol_iou']:.4f}") | |
| assert go["cd_l1"] < 5e-3, go["cd_l1"] | |
| assert go["f01"] > 0.99 and go["f02"] > 0.99 and go["f05"] > 0.99 | |
| assert go["normal_consistency"] > 0.99 | |
| assert go["vol_iou"] > 0.99 | |
| # ---- (1) ORACLE appearance (identical renders) ---- | |
| ao = appearance_metrics(rgba, rgba) | |
| print(f" [oracle-app ] LPIPS={ao['lpips']:.5f} SSIM={ao['ssim']:.4f} " | |
| f"CLIP={ao['clip']:.4f} PSNR={ao['psnr']:.2f}") | |
| assert ao["lpips"] < 1e-4 | |
| assert ao["ssim"] > 0.999 | |
| assert ao["clip"] > 0.999 | |
| # ---- (3) MONOTONICITY: perturbed mesh worse on every metric ---- | |
| pert = perturb(gt, sigma=0.03) | |
| pert_gl = R.prepare_mesh(pert) | |
| gp = geometry_metrics(pert, gt) | |
| pert_rgba = R.render_input_view(pert_gl, K, c2w, res, res).cpu().numpy() | |
| ap = appearance_metrics(pert_rgba, rgba) | |
| print(f" [perturbed ] CD_L1={gp['cd_l1']:.5f} F@.02={gp['f02']:.4f} " | |
| f"VolIoU={gp['vol_iou']:.4f} NC={gp['normal_consistency']:.4f} | " | |
| f"LPIPS={ap['lpips']:.4f} SSIM={ap['ssim']:.4f} CLIP={ap['clip']:.4f} " | |
| f"PSNR={ap['psnr']:.2f}") | |
| checks = { | |
| "CD_L1 up": gp["cd_l1"] > go["cd_l1"], | |
| "CD_L2 up": gp["cd_l2"] > go["cd_l2"], | |
| "F@.01 down": gp["f01"] < go["f01"], | |
| "F@.02 down": gp["f02"] < go["f02"], | |
| "F@.05 down": gp["f05"] < go["f05"], | |
| "NC down": gp["normal_consistency"] < go["normal_consistency"], | |
| "VolIoU down": gp["vol_iou"] < go["vol_iou"], | |
| "LPIPS up": ap["lpips"] > ao["lpips"], | |
| "SSIM down": ap["ssim"] < ao["ssim"], | |
| "CLIP down": ap["clip"] < ao["clip"], | |
| "PSNR down": ap["psnr"] < ao["psnr"], | |
| } | |
| for name, ok in checks.items(): | |
| assert ok, f"MONOTONICITY FAIL: {name}" | |
| print(f" [monotonic ] all {len(checks)} metrics degrade correctly: PASS") | |
| return go, ao | |
| def check_uv_path(): | |
| if not UV_GLB.exists(): | |
| print("\n[uv-path] no UV-textured glb available, skipping") | |
| return | |
| m = trimesh.load(str(UV_GLB), force="mesh", process=False) | |
| g = R.prepare_mesh(m) | |
| print(f"\n[uv-path] {UV_GLB.name} -> mode={g.mode}") | |
| cams = R.orbit_cameras() | |
| r = R.render_orbit(g, cams[:1]).cpu().numpy()[0] | |
| obj = r[..., 3] > 0.5 | |
| assert obj.sum() > 500, "UV render empty" | |
| col_std = float(r[..., :3][obj].std(0).mean()) | |
| print(f" UV render obj px={int(obj.sum())} colour std={col_std:.4f} " | |
| f"(mode={g.mode})") | |
| Image.fromarray((composite_white(r) * 255).astype(np.uint8)).save( | |
| DEBUG / "selfcheck_uvpath.png") | |
| def main(): | |
| print("APPEVAL SELF-CHECK") | |
| for obj in OBJECTS: | |
| check_object(obj) | |
| check_uv_path() | |
| print("\n==================================================") | |
| print("ALL SELF-CHECKS PASSED") | |
| print("proof sheets ->", DEBUG) | |
| print("==================================================") | |
| if __name__ == "__main__": | |
| main() | |