room-visualizer / verify_r4_eval.py
GitHub Actions
Deploy from GitHub commit 12f3f8bcabf5ca76d0009b00142eea4562e462b6
6a75a66
Raw
History Blame Contribute Delete
4.83 kB
"""R4-1 — intrinsic vs heuristic shading evaluation on the reference bundles.
LOCAL evaluation tool (needs torch + the intrinsic package + model weights);
not part of `make verify` — the CI-safe integration harness is
verify_r4_intrinsic_sim.py.
For each committed reference bundle this runs the REAL build_shade_map
(heuristic) and build_intrinsic_shade_map (intrinsic) from app.py on the
bundle's photo + floor mask, then writes a panel to verify_out/:
original | heuristic shade applied to flat gray | intrinsic shade applied
Applying the decoded shade to a flat gray floor is the most direct artifact
view: any tile pattern, stain or halo visible in the gray region is shading
transfer that would contaminate every replacement floor.
Usage: python verify_r4_eval.py
"""
import base64
import io
import json
import os
import cv2
import numpy as np
from PIL import Image
HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "verify_out")
BUNDLES = [
("desk", os.path.join(HERE, "data", "current_bundle.vizbundle.json")),
("kitchen", os.path.join(HERE, "data", "ref_kitchen.vizbundle.json")),
]
# --- real implementations from app.py ----------------------------------------
src = open(os.path.join(HERE, "app.py")).read()
ns = {"np": np, "cv2": cv2}
for fn in [
"_adaptive_shade_range",
"_encode_shade",
"_dominant_period",
"_suppress_periodic_shading",
"build_shade_map",
"build_intrinsic_shade_map",
]:
start = src.index(f"def {fn}")
end = src.index("\ndef ", start + 10)
exec(compile(src[start:end], "app.py", "exec"), ns)
def load_bundle(path):
d = json.load(open(path))
img = np.asarray(
Image.open(io.BytesIO(base64.b64decode(d["pixels"]))).convert("RGB")
)
h, w = d["height"], d["width"]
mask = np.zeros(w * h, bool)
for s in d["segments"]:
idx = np.frombuffer(base64.b64decode(s["mask"]), dtype=np.uint32)
mask[idx] = True
return img, mask.reshape(h, w).astype(np.uint8)
def decode(enc, rng):
lo, hi = rng
return lo + enc.astype(np.float64) / 255.0 * (hi - lo)
def shade_on_gray(img, mask, rel):
"""Composite: flat gray floor x shade over the original photo."""
out = img.astype(np.float64).copy()
gray = 205.0 * np.clip(rel, 0.0, 2.0)
for c in range(3):
ch = out[:, :, c]
ch[mask > 0] = np.clip(gray[mask > 0], 0, 255)
return out.astype(np.uint8)
def main():
os.makedirs(OUT, exist_ok=True)
print("loading intrinsic model (v2)...", flush=True)
# same headless-trust shim as app._load_intrinsic_model
import torch.hub as _hub
os.makedirs(_hub.get_dir(), exist_ok=True)
tl = os.path.join(_hub.get_dir(), "trusted_list")
if "rwightman_gen-efficientnet-pytorch" not in (
open(tl).read() if os.path.exists(tl) else ""
):
with open(tl, "a") as f:
f.write("rwightman_gen-efficientnet-pytorch\n")
from intrinsic.pipeline import load_models
ns["device"] = "cpu"
ns["intrinsic_models"] = load_models("v2", device="cpu")
print("model loaded.", flush=True)
for name, path in BUNDLES:
img, mask = load_bundle(path)
h, w = mask.shape
enc_h, rng_h = ns["build_shade_map"](img, mask)
import time
t0 = time.perf_counter()
enc_i, rng_i = ns["build_intrinsic_shade_map"](img, mask)
dt = time.perf_counter() - t0
if enc_h is None or enc_i is None:
print(f" [{name}] FAILED: heuristic={enc_h is not None} intrinsic={enc_i is not None}")
continue
rel_h = decode(enc_h.reshape(h, w), rng_h)
rel_i = decode(enc_i.reshape(h, w), rng_i)
for label, rel in (("heuristic", rel_h), ("intrinsic", rel_i)):
v = rel[mask > 0]
print(
f" [{name}] {label:9s} p5={np.percentile(v,5):.3f} "
f"p50={np.percentile(v,50):.3f} p95={np.percentile(v,95):.3f} "
f"range=({min(rng_h if label=='heuristic' else rng_i):.2f},"
f"{max(rng_h if label=='heuristic' else rng_i):.2f})"
)
print(f" [{name}] intrinsic runtime: {dt:.1f}s on cpu", flush=True)
panel = np.concatenate(
[img, shade_on_gray(img, mask, rel_h), shade_on_gray(img, mask, rel_i)],
axis=1,
)
scale = min(2200 / panel.shape[1], 1.0)
if scale < 1.0:
panel = cv2.resize(
panel, (round(panel.shape[1] * scale), round(panel.shape[0] * scale))
)
out_path = os.path.join(OUT, f"r4_eval_{name}.png")
Image.fromarray(panel).save(out_path)
print(f" [{name}] panel: {out_path} (original | heuristic | intrinsic)")
return 0
if __name__ == "__main__":
raise SystemExit(main())