room-visualizer / verify_p2_sim.py
GitHub Actions
Deploy from GitHub commit dca219cf6c8f03294b24d5ad5bc45d2a9d1b4017
8704d80
Raw
History Blame Contribute Delete
6.52 kB
"""
verify_p2_sim.py — synthetic certification for the P2 edge-quality fixes.
T7: the dilated occluder ring used to leave 2-5px of ORIGINAL floor around
furniture, and the confidence feather made the tile translucent there.
Expect: tile reclaimed up to a 1px margin, full opacity at depth edges,
feather kept only at the floor↔wall boundary.
T8: stair-stepped segmentation boundaries on occluders.
Expect: rounded edges, thin components (chair legs) never erased.
Runs the REAL functions extracted from app.py against a synthetic room,
asserts the geometry, and renders an old-vs-new composite.
Usage:
python verify_p2_sim.py
"""
import numpy as np
import cv2
from PIL import Image
# --- extract the real implementations from app.py -------------------------
src = open("app.py").read()
ns = {
"np": np,
"cv2": cv2,
"OCCLUDER_CLASSES": {"occ"},
"REJECT_SURFACE_CLASSES": {"rej"},
"class_ids": lambda names: [10] if "occ" in names else [20],
}
for fn in ["clean_floor_mask", "build_floor_surface_mask", "build_confidence_map"]:
start = src.index(f"def {fn}")
end = src.index("\ndef ", start + 10)
exec(compile(src[start:end], "app.py", "exec"), ns)
# --- synthetic room --------------------------------------------------------
H, W = 600, 800
FLOOR_Y = 320
seg = np.zeros((H, W), np.int32)
seg[:FLOOR_Y, :] = 20 # wall
# sofa with stair-stepped right edge (4px steps every 6 rows)
for y in range(250, 450):
step = 4 * ((y // 6) % 2)
seg[y, 100:300 + step] = 10
# thin 3px free-standing chair leg
seg[350:430, 500:503] = 10
# thin 3px leg ATTACHED to the sofa body (blur erases it, body survives —
# must be restored by the removed-chunk guard, not the component guard)
seg[450:480, 150:153] = 10
# curtain with zigzag hem
for x in range(600, 700):
hem = 380 - 10 * ((x // 8) % 2)
seg[0:hem, x] = 10
floor_mask = ((seg == 0) & (np.arange(H)[:, None] >= FLOOR_Y)).astype(np.uint8)
surface, occ_zone = ns["build_floor_surface_mask"](floor_mask, seg, None, None)
conf_new = ns["build_confidence_map"](surface, occ_zone)
conf_old = ns["build_confidence_map"](surface, None)
occ_raw = (seg == 10).astype(np.uint8)
# --- assertions ------------------------------------------------------------
def first_surface_right_of(y, x_edge):
row = surface[y, x_edge + 1:]
nz = np.flatnonzero(row)
return (x_edge + 1 + nz[0]) if len(nz) else None
print("== T7: fringe gap + opacity at depth edges ==")
for y in (380, 400, 420):
edge = 300 + 4 * ((y // 6) % 2) - 1 # sofa's true right edge
fx = first_surface_right_of(y, edge)
gap = fx - edge - 1
print(f" y={y}: gap={gap}px conf_new={conf_new[y, fx]} conf_old={conf_old[y, fx]}")
assert gap <= 3, "fringe gap should be <=3px"
assert conf_new[y, fx] == 255, "tile must be opaque at the depth edge"
assert conf_old[y, fx] < 200, "old feather should have been translucent here"
print("== T7: floor-wall boundary keeps its feather, no gap line ==")
col = 50
ys = np.flatnonzero(surface[:, col])
top = ys[0]
ramp = [int(conf_new[top + d, col]) for d in range(0, 9)]
print(f" top surface row at x={col}: y={top}, conf ramp: {ramp}")
assert conf_new[top, col] < 200, "outer boundary must still feather"
assert 0 not in ramp, "no untiled gap line inside the wall feather"
assert ramp[-1] == 255, "feather must finish within ~8px"
assert all(b >= a for a, b in zip(ramp, ramp[1:])), "ramp must be monotonic"
print("== T8: free-standing leg survives smoothing, no tile painted on it ==")
leg = surface[355:425, 500:503]
assert leg.sum() == 0, "tile must not cover the leg"
assert occ_zone[390, 501] > 0, "leg must remain in the occluder zone"
fx = first_surface_right_of(390, 502)
print(f" leg untouched, tile resumes {fx - 503}px right of it")
assert fx - 503 <= 3
print("== T8: ATTACHED leg survives via removed-chunk guard ==")
att = surface[455:478, 150:153]
assert att.sum() == 0, "tile must not cover the attached leg"
assert occ_zone[465, 151] > 0, "attached leg must remain in the occluder zone"
print(" attached leg untouched")
print("== T8: boundary roughness (perimeter ratio, lower = smoother) ==")
def perimeter(m):
cs, _ = cv2.findContours(m, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
return sum(cv2.arcLength(c, True) for c in cs)
# re-run just the T8 blur (without the guards) for the perimeter metric
smooth_k = max(13, min(H, W) // 100) | 1
occ_smooth = (cv2.GaussianBlur(occ_raw.astype(np.float32), (smooth_k, smooth_k), 0) >= 0.5).astype(np.uint8)
p_raw, p_smooth = perimeter(occ_raw), perimeter(occ_smooth)
print(f" k={smooth_k}: perimeter raw={p_raw:.0f} smoothed={p_smooth:.0f} ({p_smooth / p_raw:.2f}x)")
assert p_smooth < p_raw * 0.92, "staircase must actually collapse"
print("== T8: tile edge beside the sofa is straighter than the raw stairs ==")
tile_edge = [np.flatnonzero(surface[y, 250:340])[0] for y in range(370, 430)]
amp = max(tile_edge) - min(tile_edge)
print(f" tile-edge amplitude over 60 rows: {amp}px (raw stair amplitude: 4px)")
assert amp <= 2, "tile edge must be straighter than the raw staircase"
print("== T7: furniture feet sit flush (small gap below sofa bottom) ==")
below = np.flatnonzero(surface[450:, 200])
gap = below[0] if len(below) else 99
print(f" first tile row below sofa bottom: {gap}px")
assert gap <= 3, "feet must sit within 3px of the tile"
# --- composite render: old vs new ------------------------------------------
room = np.full((H, W, 3), (150, 110, 70), np.uint8) # warm original floor
room[:FLOOR_Y] = (210, 205, 195) # wall
room[occ_raw > 0] = (45, 40, 38) # dark furniture
yy, xx = np.mgrid[0:H, 0:W]
checker = (((yy // 24) + (xx // 24)) % 2).astype(bool)
tile = np.where(checker[..., None], (235, 235, 230), (200, 200, 195)).astype(np.uint8)
def composite(conf):
a = (conf.astype(np.float32) / 255.0)[..., None]
out = room.astype(np.float32) * (1 - a) + tile.astype(np.float32) * a
return out.astype(np.uint8)
old, new = composite(conf_old), composite(conf_new)
side = np.hstack([old, new])
Image.fromarray(side).save("verify_out/p2_compare.png")
crop = np.hstack([old[330:470, 250:560], new[330:470, 250:560]])
crop = cv2.resize(crop, None, fx=2.5, fy=2.5, interpolation=cv2.INTER_NEAREST)
Image.fromarray(crop).save("verify_out/p2_compare_crop.png")
print("saved verify_out/p2_compare.png + p2_compare_crop.png (left=old, right=new)")
print("ALL P2 CHECKS PASSED")