Spaces:
Sleeping
Sleeping
| """ | |
| 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") | |