"""R3-3 — rug-replace certification (CI-safe, no models). Two contracts: 1. Class routing: 'rug' is a REPLACEABLE class (RUG_CLASSES), not an occluder — the benchmark behaviour. Verified against the real class sets from app.py. 2. Shade neutrality: with the rug replaced, the heuristic shade map must NOT carry the rug's albedo (a dark persian rug would otherwise transfer as a giant stain onto the new floor). Inside the rug the shade flattens to neutral; outside the feather band the shading is untouched. """ import cv2 import numpy as np # --- real implementations / class sets from app.py --------------------------- src = open("app.py").read() ns = {"np": np, "cv2": cv2} sets_start = src.index("PRIMARY_FLOOR_CLASSES") sets_end = src.index("\n\n", src.index("OCCLUDER_CLASSES")) exec(compile(src[sets_start:sets_end], "app.py", "exec"), ns) for fn in [ "_adaptive_shade_range", "_encode_shade", "_dominant_period", "_suppress_periodic_shading", "build_shade_map", ]: start = src.index(f"def {fn}") end = src.index("\ndef ", start + 10) exec(compile(src[start:end], "app.py", "exec"), ns) build_shade_map = ns["build_shade_map"] H, W = 480, 640 def scene(): """Bright floor (lower half) with a gentle vertical light gradient and a DARK rug rectangle; the mask covers floor incl. rug (R3-3 union).""" yy = np.mgrid[0:H, 0:W][0].astype(np.float64) lum = 150.0 + 40.0 * (yy - H / 2) / (H / 2) img = np.clip(lum, 0, 255) mask = np.zeros((H, W), np.uint8) mask[H // 2 :, :] = 1 rug = np.zeros((H, W), np.uint8) rug[int(H * 0.65) : int(H * 0.85), int(W * 0.3) : int(W * 0.7)] = 1 img = np.where(rug > 0, 60.0, img) img3 = np.stack([img] * 3, axis=2).astype(np.uint8) return img3, mask, rug def decode(enc, rng): lo, hi = rng return lo + enc.astype(np.float64) / 255.0 * (hi - lo) def main(): ok = True # 1 — class routing rug_ok = ( "rug" in ns["RUG_CLASSES"] and "rug" not in ns["OCCLUDER_CLASSES"] and "rug" not in ns["FLOOR_SURFACE_CLASSES"] ) print(f" [{'PASS' if rug_ok else 'FAIL'}] class routing: rug replaceable " f"(RUG_CLASSES), not occluder, not silently merged into floor classes") ok &= rug_ok img, mask, rug = scene() enc_with, rng_with = build_shade_map(img, mask, rug) enc_wo, rng_wo = build_shade_map(img, mask, None) rel_with = decode(enc_with.reshape(H, W), rng_with) rel_wo = decode(enc_wo.reshape(H, W), rng_wo) rug_interior = cv2.erode(rug, np.ones((31, 31), np.uint8)) > 0 # 2 — without neutralisation the rug albedo IS a stain (documents the bug) stain = float(rel_wo[rug_interior].mean()) good = stain < 0.85 print(f" [{'PASS' if good else 'FAIL'}] un-neutralised rug reads as stain: " f"mean shade {stain:.3f} < 0.85") ok &= good # 3 — with the rug mask the region flattens to neutral flat = float(np.abs(rel_with[rug_interior] - 1.0).mean()) good = flat < 0.05 print(f" [{'PASS' if good else 'FAIL'}] neutralised: mean |shade-1| inside rug " f"{flat:.3f} < 0.05") ok &= good # --- R3-3 v2 — rug-region refinement (refine_rug_mask) ------------------- SOFT_ID = 7 rns = { "np": np, "cv2": cv2, "class_ids": lambda names: [SOFT_ID], "SOFT_FURNISHING_CLASSES": {"cushion"}, } start = src.index("def refine_rug_mask") end = src.index("\n# ---", start) exec(compile(src[start:end], "app.py", "exec"), rns) refine_rug_mask = rns["refine_rug_mask"] def striped(shape): yy, xx = np.indices(shape) return (90 + 60 * ((xx + yy // 4) % 2)).astype(np.uint8) img2 = np.full((H, W, 3), 170, np.uint8) seg2 = np.zeros((H, W), np.int64) rug2 = np.zeros((H, W), np.uint8) # main rug: striped pattern, 120x160 rug2[300:420, 200:360] = 1 img2[300:420, 200:360] = striped((120, 160))[..., None] # stray island: 12x12 far away (mislabelled accent tile) rug2[100:112, 500:512] = 1 # object on the rug: black 24x24 blob (slippers) img2[340:364, 260:284] = 15 # look-alike soft fragment adjacent to the rug (same stripes) seg2[300:420, 362:410] = SOFT_ID img2[300:420, 362:410] = striped((120, 48))[..., None] # genuine plain cushion adjacent on the other side (uniform, L matches # the stripe median -> only the texture-contrast gate can save it) seg2[300:420, 150:198] = SOFT_ID img2[300:420, 150:198] = 120 refined, protect = refine_rug_mask(rug2, seg2, img2) # 5 — stray island dropped, main rug kept good = refined[100:112, 500:512].sum() == 0 and refined[305:315, 205:215].all() print(f" [{'PASS' if good else 'FAIL'}] v2: stray island dropped, rug kept") ok &= good # 6 — object on the rug carved out and protected blob = np.zeros((H, W), bool) blob[342:362, 262:282] = True good = (protect[blob].mean() > 0.9) and (refined[blob].sum() == 0) print(f" [{'PASS' if good else 'FAIL'}] v2: object on rug carved out + protected " f"(protected {protect[blob].mean():.2f} of blob)") ok &= good # 7 — look-alike fragment absorbed into the rug good = refined[310:410, 370:400].mean() > 0.9 print(f" [{'PASS' if good else 'FAIL'}] v2: look-alike fragment absorbed " f"({refined[310:410, 370:400].mean():.2f} of fragment)") ok &= good # 8 — plain cushion NOT absorbed (texture-contrast gate abstains) good = refined[310:410, 160:190].sum() == 0 print(f" [{'PASS' if good else 'FAIL'}] v2: plain cushion stays an occluder") ok &= good # 9 — shading outside the feather band is untouched feather_zone = cv2.dilate(rug, np.ones((81, 81), np.uint8)) > 0 outside = (mask > 0) & ~feather_zone drift = float(np.abs(rel_with[outside] - rel_wo[outside]).mean()) good = drift < 0.02 print(f" [{'PASS' if good else 'FAIL'}] outside feather band untouched: " f"mean drift {drift:.4f} < 0.02") ok &= good print("\n" + ("ALL R3-3 SIM CHECKS PASSED" if ok else "R3-3 SIM CHECKS FAILED")) return 0 if ok else 1 if __name__ == "__main__": raise SystemExit(main())