File size: 6,262 Bytes
6a75a66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4458f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6a75a66
 
 
 
 
 
 
 
 
 
 
 
 
 
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""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())