Spaces:
Sleeping
Sleeping
File size: 6,518 Bytes
8704d80 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | """
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")
|