open-standards/wai/live-conformance/_wai_mark.py
Transaction Science 2016ec1805
All checks were successful
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 5s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 4s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m46s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m30s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 6s
WAI energy receipts / does this change touch it (pull_request) Successful in 7s
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 6s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 6s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 9s
wai-web wasm32 / does this change touch it (pull_request) Successful in 9s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m10s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m13s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m14s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 34s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 9m9s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 2m21s
wai-pack / wai-pack · linux (pull_request) Successful in 1m24s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m11s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 16s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 11s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 29s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 8m25s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 2m55s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 19m10s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m55s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m10s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 12m16s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m35s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 23m44s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 1m17s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m46s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 6m5s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 44m56s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 8m6s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 13m36s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 18s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 44s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 4m58s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 2m39s
wai-pack / wai-pack · macos (pull_request) Successful in 1m15s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 2m15s
tent64s corpus: each detect vector states stage 2's trace
Every candidate stage 2 keeps (its vectors, refined lattice, bins and phase) and the
hypotheses it decodes, for every detect vector, stored or derived. The vectors' payloads
alone did not tell the phase rule apart: choosing the phase by the tile's energy rather
than the tiles' agreement passed the second implementation (falsified); with the traces
the two implementations compare the whole pipeline, and a mutated phase is caught.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 20:55:47 -04:00

877 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""The registered watermark schemes `wai-mark:tent64` (extensions/synthesis.md §7.5) and
`wai-mark:tent64s` (§7.9), and the attacks of their battery (§7.5.7, §7.9.6), written from the
extension's text.
Shared by the two Python verifiers of the synthesis corpora (synthesis_verify.py and
synthesis_mark_verify.py); it shares no code with wai-rs. Standard library only.
Frames are `bytes` of RGB8, frame after frame. Every function is integer arithmetic, so its
results are the extension's to the byte.
"""
import hashlib
import math
SCHEME = "wai-mark:tent64"
GRID = 64
CELLS = GRID * GRID
LENGTH = 248 # the shortened code's length (§7.5.3)
PARITY = 184
T = 29 # the errors the code corrects
CELLS_PER_BIT = 16
AMPLITUDE = 12 # §7.5.4
MARGIN = (3, 2) # 2·σ·T ≥ 3·Q
MIN_SIDE = 128 # §7.5.1
MAX_FRAME_BYTES = 1 << 28
MAX_INT = (1 << 53) - 1
CLASS_MAX = 3
def sha256(b):
return hashlib.sha256(b).digest()
def stream(label, seed=None):
"""§7.5.3: 32-bit big-endian words, eight from each SHA-256(label ‖ [seed ‖] n)."""
n = 0
while True:
d = hashlib.sha256(label + (b"" if seed is None else seed.to_bytes(4, "big")) + n.to_bytes(4, "big")).digest()
for o in range(0, 32, 4):
yield int.from_bytes(d[o:o + 4], "big")
n += 1
def _layout():
r = stream(b"wai-mark:tent64 stream")
perm = list(range(CELLS))
for i in range(CELLS - 1, 0, -1):
j = next(r) % (i + 1)
perm[i], perm[j] = perm[j], perm[i]
signs = [1 if next(r) % 2 == 0 else -1 for _ in range(CELLS)]
mask = [next(r) & 1 for _ in range(LENGTH)]
return perm, signs, mask
PERM, SIGNS, MASK = _layout()
BIT_CELLS = [PERM[CELLS_PER_BIT * j: CELLS_PER_BIT * j + CELLS_PER_BIT] for j in range(LENGTH)]
# ---------------------------------------------------------------- the code, §7.5.3
EXP = [0] * 510
LOG = [0] * 256
_x = 1
for _i in range(255):
EXP[_i] = _x
LOG[_x] = _i
_x <<= 1
if _x & 0x100:
_x ^= 0x11D # x^8 + x^4 + x^3 + x^2 + 1
for _i in range(255, 510):
EXP[_i] = EXP[_i - 255]
def gmul(a, b):
return 0 if a == 0 or b == 0 else EXP[LOG[a] + LOG[b]]
def generator():
"""The least common multiple of the minimal polynomials of α^1 … α^58, as an integer whose
bit d is the coefficient of x^d."""
g, cosets = 1, []
for i in range(1, 2 * T + 1):
coset, j = [], i % 255
while j not in coset:
coset.append(j)
j = 2 * j % 255
if sorted(coset) in cosets:
continue
cosets.append(sorted(coset))
p = [1] # coefficients from x^0 up
for j in coset:
q = [0] * (len(p) + 1)
for d, c in enumerate(p):
q[d + 1] ^= c
q[d] ^= gmul(c, EXP[j])
p = q
assert all(c in (0, 1) for c in p), "a minimal polynomial is binary"
pm = sum(c << d for d, c in enumerate(p))
prod, a, b = 0, g, pm # product over GF(2)
while b:
if b & 1:
prod ^= a
a <<= 1
b >>= 1
g = prod
return g
G = generator()
def encode(payload):
"""The codeword of an 8-byte payload: bit j the coefficient of x^(247 − j)."""
m = int.from_bytes(payload, "big")
r = m << PARITY
for d in range(r.bit_length() - 1, PARITY - 1, -1):
if r >> d & 1:
r ^= G << (d - PARITY)
c = (m << PARITY) | r
return [c >> (LENGTH - 1 - j) & 1 for j in range(LENGTH)]
def _syndromes(c):
return [_eval(c, i) for i in range(1, 2 * T + 1)]
def _eval(c, i):
v = 0
e = 0
while c:
if c & 1:
v ^= EXP[i * e % 255]
c >>= 1
e += 1
return v
def decode(word):
"""Bounded-distance decoding within 29: (payload, bits corrected), or None where no codeword
is within 29 bits or the payload's first byte is above 3."""
c = 0
for b in word:
c = (c << 1) | b
s = _syndromes(c)
errors = 0
if any(s):
# The error locator: the shortest linear recurrence the syndromes satisfy, over GF(2^8).
lam, prev, L, m, bd = [1], [1], 0, 1, 1
for n in range(2 * T):
d = s[n]
for i in range(1, min(L, len(lam) - 1) + 1):
d ^= gmul(lam[i], s[n - i])
if d == 0:
m += 1
continue
coef = gmul(d, EXP[(255 - LOG[bd]) % 255])
old = lam[:]
lam = lam + [0] * max(0, len(prev) + m - len(lam))
for i, bv in enumerate(prev):
lam[i + m] ^= gmul(coef, bv)
if 2 * L <= n:
L, prev, bd, m = n + 1 - L, old, d, 1
else:
m += 1
while len(lam) > 1 and lam[-1] == 0:
lam.pop()
if len(lam) - 1 != L or L > T:
return None
roots = []
for e in range(255):
v = 0
for i, co in enumerate(lam):
if co:
v ^= gmul(co, EXP[(-e * i) % 255])
if v == 0:
roots.append(e)
if len(roots) != L or any(e >= LENGTH for e in roots):
return None
for e in roots:
c ^= 1 << e
if any(_syndromes(c)):
return None
errors = L
payload = (c >> PARITY).to_bytes(8, "big")
if payload[0] > CLASS_MAX:
return None
return payload, errors
# ---------------------------------------------------------------- frames, §7.5.1 and §7.5.2
def admits(w, h, n):
return w >= MIN_SIDE and h >= MIN_SIDE and n >= 1 and w * h * 3 <= MAX_FRAME_BYTES and w * h * 3 * n <= MAX_INT
def luma(r, g, b):
return (77 * r + 150 * g + 29 * b + 128) >> 8
def tent(u, length):
c = 2 * u + 1 - length
sg = (c > 0) - (c < 0)
return sg * (length - abs(2 * abs(c) - length))
def axis(n):
"""Per sample: (cell index, tent, cell length); and the edges."""
edges = [i * n // GRID for i in range(GRID + 1)]
cell, tents, lens = [], [], []
for i in range(GRID):
L = edges[i + 1] - edges[i]
for x in range(edges[i], edges[i + 1]):
cell.append(i)
tents.append(tent(x - edges[i], L))
lens.append(L)
return cell, tents, lens, edges
def luma_plane(frame):
return [luma(frame[p], frame[p + 1], frame[p + 2]) for p in range(0, len(frame), 3)]
def bit_statistics(y, w, h, layout=None):
"""T_j over one luma plane of w × h, under a layout (bit cells, signs) or tent64's."""
bit_cells, signs = (BIT_CELLS, SIGNS) if layout is None else (layout[0], layout[1])
cx, fx, _, _ = axis(w)
cy, fy, _, _ = axis(h)
s = [0] * CELLS
for row in range(h):
acc = [0] * GRID
base = row * w
for col in range(w):
acc[cx[col]] += fx[col] * y[base + col]
r0, f = cy[row] * GRID, fy[row]
for i in range(GRID):
s[r0 + i] += f * acc[i]
return [sum(signs[k] * s[k] for k in bit_cells[j]) for j in range(LENGTH)]
# ---------------------------------------------------------------- embedding, §7.5.4
def embed(unmarked, w, h, n, payload, layout=None):
"""The marked frames, and each frame's amplitudes, under a layout (bit cells, signs, mask)
or tent64's."""
bit_cells, signs, mask = (BIT_CELLS, SIGNS, MASK) if layout is None else layout
code = encode(payload)
sigma = [1 if code[j] ^ mask[j] else -1 for j in range(LENGTH)]
_, fx, lx, ex = axis(w)
_, fy, ly, ey = axis(h)
fb = w * h * 3
out = bytearray(unmarked)
amps = []
for f in range(n):
frame = unmarked[f * fb:(f + 1) * fb]
chosen = []
for j in range(LENGTH):
sg = sigma[j]
pixels = [] # (offset, ε·ω, z, ν)
q = 0
for k in bit_cells[j]:
i, jj = k % GRID, k // GRID
eps = signs[k]
for row in range(ey[jj], ey[jj + 1]):
for col in range(ex[i], ex[i + 1]):
om = fx[col] * fy[row]
q += abs(om)
pixels.append(((row * w + col) * 3, eps * om, sg * eps * om, lx[col] * ly[row]))
def at(a):
t, vals = 0, []
for p, eo, z, nu in pixels:
d = (z > 0) - (z < 0)
d *= (2 * a * abs(z) + nu) // (2 * nu)
r = min(255, max(0, frame[p] + d))
g = min(255, max(0, frame[p + 1] + d))
b = min(255, max(0, frame[p + 2] + d))
t += eo * luma(r, g, b)
vals.append((p, r, g, b))
return t, vals
pick = None
for a in range(AMPLITUDE + 1):
t, vals = at(a)
if MARGIN[1] * sg * t >= MARGIN[0] * q:
pick = (a, vals)
break
if pick is None:
pick = (AMPLITUDE, at(AMPLITUDE)[1])
a, vals = pick
for p, r, g, b in vals:
out[f * fb + p] = r
out[f * fb + p + 1] = g
out[f * fb + p + 2] = b
chosen.append(a)
amps.append(chosen)
return bytes(out), amps
# ---------------------------------------------------------------- detection, §7.5.5
def orient(y, w, h, o):
ow, oh = (h, w) if o & 4 else (w, h)
out = [0] * (w * h)
for yy in range(oh):
y1 = oh - 1 - yy if o & 2 else yy
for xx in range(ow):
x1 = ow - 1 - xx if o & 1 else xx
sx, sy = (y1, x1) if o & 4 else (x1, y1)
out[yy * ow + xx] = y[sy * w + sx]
return out, ow, oh
def read_word(samples, w, h, n, o, layout=None):
mask = MASK if layout is None else layout[2]
fb = w * h * 3
t = [0] * LENGTH
for f in range(n):
y, ow, oh = orient(luma_plane(samples[f * fb:(f + 1) * fb]), w, h, o)
for j, v in enumerate(bit_statistics(y, ow, oh, layout)):
t[j] += v
return [(1 if t[j] > 0 else 0) ^ mask[j] for j in range(LENGTH)]
def detect(samples, w, h, n):
"""(payload, orientation, errors) of the first orientation that decodes, or None."""
if not admits(w, h, n) or len(samples) != w * h * 3 * n:
return None
for o in range(8):
got = decode(read_word(samples, w, h, n, o))
if got is not None:
return got[0], o, got[1]
return None
# ---------------------------------------------------------------- the battery, §7.5.7
def rnd(n, d):
"""n / d rounded to the nearest integer, halves away from zero."""
sg = (n > 0) - (n < 0)
return sg * ((2 * abs(n) + d) // (2 * d))
def clamp(v):
return 0 if v < 0 else 255 if v > 255 else v
def int_motion(frame, q):
return bytes(clamp(128 + rnd(v - 128, q) * q) for v in frame)
def upscale2(px, w, h):
"""wai-synthesis:upscale2, §4.1."""
out = bytearray(4 * w * h * 3)
ow = 2 * w
for y in range(h):
y1 = min(y + 1, h - 1)
for x in range(w):
x1 = min(x + 1, w - 1)
for c in range(3):
a, b = px[(y * w + x) * 3 + c], px[(y * w + x1) * 3 + c]
cc, d = px[(y1 * w + x) * 3 + c], px[(y1 * w + x1) * 3 + c]
out[((2 * y) * ow + 2 * x) * 3 + c] = a
out[((2 * y) * ow + 2 * x + 1) * 3 + c] = (a + b + 1) >> 1
out[((2 * y + 1) * ow + 2 * x) * 3 + c] = (a + cc + 1) >> 1
out[((2 * y + 1) * ow + 2 * x + 1) * 3 + c] = (a + b + cc + d + 2) >> 2
return bytes(out)
def denoise3(px, w, h):
"""wai-synthesis:denoise3, §4.2."""
k = ((1, 2, 1), (2, 4, 2), (1, 2, 1))
out = bytearray(w * h * 3)
for y in range(h):
for x in range(w):
for c in range(3):
s = 8
for j in range(3):
yy = min(max(y + j - 1, 0), h - 1)
for i in range(3):
xx = min(max(x + i - 1, 0), w - 1)
s += k[j][i] * px[(yy * w + xx) * 3 + c]
out[(y * w + x) * 3 + c] = s >> 4
return bytes(out)
def _weights(n, m):
out = []
for d in range(m):
lo, hi = d * n, (d + 1) * n
row = []
for s in range(lo // m, -(-hi // m)):
o = min(hi, (s + 1) * m) - max(lo, s * m)
if o > 0:
row.append((s, o))
out.append(row)
return out
def area(px, w, h, nw, nh):
wx, wy = _weights(w, nw), _weights(h, nh)
den = w * h
out = bytearray(nw * nh * 3)
for dy, ry in enumerate(wy):
for dx, rx in enumerate(wx):
for c in range(3):
num = 0
for sy, oy in ry:
for sx, ox in rx:
num += oy * ox * px[(sy * w + sx) * 3 + c]
out[(dy * nw + dx) * 3 + c] = (2 * num + den) // (2 * den)
return bytes(out)
ROTATIONS = {"rotate-0.5": (65534, 572), "rotate-1": (65526, 1144), "rotate-2": (65496, 2287), "rotate-5": (65287, 5712)}
# §7.9.6: the simulated captures of a screen, (num, den) of the resampling and the rotation.
SCREENS = {"screen-a": ((5, 4), (65526, 1144)), "screen-b": ((4, 5), (65496, -2287))}
def rotate(px, w, h, cs, sn):
one = 1 << 16
def p(x, y, c):
return 0 if x < 0 or y < 0 or x >= w or y >= h else px[(y * w + x) * 3 + c]
out = bytearray(w * h * 3)
for y in range(h):
for x in range(w):
u, v = 2 * x + 1 - w, 2 * y + 1 - h
X = (cs * u + sn * v + (w - 1) * one) >> 1
Y = (-sn * u + cs * v + (h - 1) * one) >> 1
xi, fx, yi, fy = X >> 16, X & 0xFFFF, Y >> 16, Y & 0xFFFF
for c in range(3):
val = (p(xi, yi, c) * (one - fx) * (one - fy) + p(xi + 1, yi, c) * fx * (one - fy)
+ p(xi, yi + 1, c) * (one - fx) * fy + p(xi + 1, yi + 1, c) * fx * fy)
out[(y * w + x) * 3 + c] = (val + (1 << 31)) >> 32
return bytes(out)
def attack(name, px, w, h, seed):
"""One picture attack of §7.5.7 on a single frame: (result, width, height)."""
if name == "none":
return px, w, h
if name.startswith("int-motion-q"):
return int_motion(px, int(name[len("int-motion-q"):])), w, h
if name == "staged-base-q8":
hw, hh = w // 2, h // 2
half = bytearray(hw * hh * 3)
for y in range(hh):
for x in range(hw):
for c in range(3):
s = sum(px[((2 * y + dy) * w + 2 * x + dx) * 3 + c] for dy in (0, 1) for dx in (0, 1))
half[(y * hw + x) * 3 + c] = (s + 2) >> 2
return upscale2(int_motion(bytes(half), 8), hw, hh), 2 * hw, 2 * hh
if name == "denoise3":
return denoise3(px, w, h), w, h
if name == "denoise3-x4":
for _ in range(4):
px = denoise3(px, w, h)
return px, w, h
if name.startswith("scale-"):
n, d = map(int, name[len("scale-"):].split("/"))
nw, nh = w * n // d, h * n // d
return area(px, w, h, nw, nh), nw, nh
if name == "upscale2":
return upscale2(px, w, h), 2 * w, 2 * h
if name == "aspect-3/4":
nw = w * 3 // 4
return area(px, w, h, nw, h), nw, h
if name == "half-then-int-motion-q16":
nw, nh = w // 2, h // 2
return int_motion(area(px, w, h, nw, nh), 16), nw, nh
for side, prefix in enumerate(("crop-left-", "crop-right-", "crop-top-", "crop-bottom-")):
if name.startswith(prefix) and name.endswith("%"):
pc = int(name[len(prefix):-1])
cx, cy = w * pc // 100, h * pc // 100
x0, y0, nw, nh = [(cx, 0, w - cx, h), (0, 0, w - cx, h), (0, cy, w, h - cy), (0, 0, w, h - cy)][side]
out = b"".join(px[(y * w + x0) * 3:(y * w + x0 + nw) * 3] for y in range(y0, y0 + nh))
return out, nw, nh
if name in SCREENS:
(n, d), (cs, sn) = SCREENS[name]
nw, nh = w * n // d, h * n // d
shown = rotate(area(px, w, h, nw, nh), nw, nh, cs, sn)
shown = bytes((2 * v * (v + 255) + 510) // 1020 for v in shown)
r = stream(b"wai-mark:battery noise", seed)
return bytes(clamp(v + next(r) % 9 - 4) for v in shown), nw, nh
if name == "periodic":
qw, qh = w // 2, h // 2
return b"".join(px[((y % qh) * w + x % qw) * 3:((y % qh) * w + x % qw) * 3 + 3] for y in range(h) for x in range(w)), w, h
if name.startswith("crop-"):
pc = int(name[len("crop-"):-1])
dx, dy = w * pc // 200, h * pc // 200
nw, nh = w - 2 * dx, h - 2 * dy
out = b"".join(px[((y * w) + dx) * 3:((y * w) + dx + nw) * 3] for y in range(dy, dy + nh))
return out, nw, nh
if name == "mirror":
return b"".join(px[(y * w + x) * 3:(y * w + x) * 3 + 3] for y in range(h) for x in range(w - 1, -1, -1)), w, h
if name == "rotate-90":
return b"".join(px[((h - 1 - xp) * w + yp) * 3:((h - 1 - xp) * w + yp) * 3 + 3] for yp in range(w) for xp in range(h)), h, w
if name in ROTATIONS:
cs, sn = ROTATIONS[name]
return rotate(px, w, h, cs, sn), w, h
if name.startswith("noise-"):
s = int(name[len("noise-"):])
r = stream(b"wai-mark:battery noise", seed)
return bytes(clamp(v + next(r) % (2 * s + 1) - s) for v in px), w, h
if name.startswith("brightness"):
b = int(name[len("brightness"):])
return bytes(clamp(v + b) for v in px), w, h
if name.startswith("contrast-"):
n, d = map(int, name[len("contrast-"):].split("/"))
return bytes(clamp(128 + rnd((v - 128) * n, d)) for v in px), w, h
if name == "local-blur":
out = bytearray(px)
x0, x1, y0, y1 = w // 4, 3 * w // 4, h // 4, 3 * h // 4
for by in range(y0, y1, 16):
for bx in range(x0, x1, 16):
ex, ey = min(bx + 16, x1), min(by + 16, y1)
cnt = (ex - bx) * (ey - by)
for c in range(3):
tot = sum(px[(y * w + x) * 3 + c] for y in range(by, ey) for x in range(bx, ex))
mval = (2 * tot + cnt) // (2 * cnt)
for y in range(by, ey):
for x in range(bx, ex):
out[(y * w + x) * 3 + c] = mval
return bytes(out), w, h
raise ValueError("no picture attack named %r" % name)
def attack_frames(name, px, w, h, n, seed):
"""A geometric attack, or `periodic`, on each of n frames: (result, width, height)."""
fb = w * h * 3
outs = [attack(name, px[f * fb:(f + 1) * fb], w, h, seed) for f in range(n)]
return b"".join(o[0] for o in outs), outs[0][1], outs[0][2]
# ================================================================ wai-mark:tent64s, §7.9
SCHEME_S = "wai-mark:tent64s"
TILE = 32
POSITIONS = TILE * TILE
PER_TILE = 4
STAGE2_FRAMES = 8
WORKING_SIDE = 1024
ROBUST_CLAMP = 100
CANDIDATES = 8
DECODES = 4
SPAN = ((7, 20), (3, 4))
SLOPE = (3, 20)
SUB = 64
def _tile_layout():
"""§7.9.2: the shuffle of the tile's 1024 positions, their signs and the mask."""
r = stream(b"wai-mark:tent64s stream")
perm = list(range(POSITIONS))
for i in range(POSITIONS - 1, 0, -1):
j = next(r) % (i + 1)
perm[i], perm[j] = perm[j], perm[i]
signs = [1 if next(r) % 2 == 0 else -1 for _ in range(POSITIONS)]
mask = [next(r) & 1 for _ in range(LENGTH)]
return perm, signs, mask
T_PERM, T_SIGNS, T_MASK = _tile_layout()
def _cell_of(p, t):
"""The frame cell of tile position p in tile t (left to right, then top to bottom)."""
row, col = divmod(p, TILE)
return GRID * (TILE * (t // 2) + row) + TILE * (t % 2) + col
_S_SIGNS = [0] * CELLS
for _p in range(POSITIONS):
for _t in range(4):
_S_SIGNS[_cell_of(_p, _t)] = T_SIGNS[_p]
LAYOUT_S = ([[_cell_of(T_PERM[PER_TILE * j + k], t) for k in range(PER_TILE) for t in range(4)] for j in range(LENGTH)], _S_SIGNS, T_MASK)
def embed_s(unmarked, w, h, n, payload):
"""§7.9.3: §7.5.4's embedding under the tiled layout."""
return embed(unmarked, w, h, n, payload, LAYOUT_S)
def _rnd(n, d):
"""n / d to the nearest integer, halves away from zero, for any d ≠ 0."""
if d < 0:
n, d = -n, -d
sg = (n > 0) - (n < 0)
return sg * ((2 * abs(n) + d) // (2 * d))
def _working(frame, w, h, k):
y = luma_plane(frame)
if k == 1:
return y, w, h
ww, hh, kk = w // k, h // k, k * k
out = []
for oy in range(hh):
for ox in range(ww):
s = sum(y[(oy * k + dy) * w + ox * k + dx] for dy in range(k) for dx in range(k))
out.append((s + kk // 2) // kk)
return out, ww, hh
def _robust(y, w, h):
row = [0] * (w * h)
for r in range(h):
b = r * w
for x in range(w):
row[b + x] = sum(y[b + min(max(x + d, 0), w - 1)] for d in (-2, -1, 0, 1, 2))
out = [0] * (w * h)
for r in range(h):
rows = [min(max(r + d, 0), h - 1) * w for d in (-2, -1, 0, 1, 2)]
for x in range(w):
v = 25 * y[r * w + x] - sum(row[q + x] for q in rows)
out[r * w + x] = -ROBUST_CLAMP if v < -ROBUST_CLAMP else ROBUST_CLAMP if v > ROBUST_CLAMP else v
return out
def _sign_plane(r, w, h):
lx, ly = max(2, w // GRID), max(2, h // GRID)
wq, hq = w - lx + 1, h - ly + 1
fx = [tent(u, lx) for u in range(lx)]
fy = [tent(u, ly) for u in range(ly)]
horiz = [sum(fx[u] * r[row * w + x + u] for u in range(lx)) for row in range(h) for x in range(wq)]
q = []
for y in range(hq):
for x in range(wq):
s = sum(fy[v] * horiz[(y + v) * wq + x] for v in range(ly))
q.append((s > 0) - (s < 0))
return q, wq, hq
def _autocorrelation(planes, wq, hq):
"""§7.9.4 step 4, exactly: one product of two integers per frame (rows laid end to end at
a stride of 2·Wq, four bytes a value), summed, read back. Returns A(dx, dy) as a function."""
import sys as _sys
stride = 2 * wq
n = hq * stride
ones = int.from_bytes(b"\x01\x00\x00\x00" * n, "little")
total = 0
for q in planes:
flat = [0] * n
for y in range(hq):
flat[y * stride:y * stride + wq] = q[y * wq:(y + 1) * wq]
fwd = b"".join((v + 1).to_bytes(4, "little") for v in flat)
rev = b"".join((v + 1).to_bytes(4, "little") for v in reversed(flat))
a = int.from_bytes(fwd, "little") - ones
b = int.from_bytes(rev, "little") - ones
total += a * b
bias = int.from_bytes(b"\x00\x00\x00\x80" * (2 * n - 1), "little")
raw = (total + bias).to_bytes(4 * (2 * n - 1), "little")
import array
slots = array.array("I")
slots.frombytes(raw)
if _sys.byteorder == "big":
slots.byteswap()
def at(dx, dy):
if abs(dx) >= wq or abs(dy) >= hq:
return 0
return slots[n - 1 + dy * stride + dx] - (1 << 31)
return at
def _candidates(at, wq, hq):
"""§7.9.4 step 5: up to 8 lattice candidates (dx, dy, e, g)."""
(ln, ld), (hn, hd) = SPAN
def ov(x, y):
return 0 if abs(x) >= wq or abs(y) >= hq else (wq - abs(x)) * (hq - abs(y))
rows = []
idx = 0
for dx in range(-(-ln * wq // ld), hn * wq // hd + 1):
m = SLOPE[0] * dx // SLOPE[1]
a1, o1 = None, None
for dy in range(-m, m + 1):
a1, o1 = at(dx, dy), ov(dx, dy)
for e in range(-(-ln * hq // ld), hn * hq // hd + 1):
g = _rnd(-dy * e, dx)
a = a1 + at(g, e) + at(dx + g, dy + e) + at(dx - g, dy - e)
o = o1 + ov(g, e) + ov(dx + g, dy + e) + ov(dx - g, dy - e)
rows.append((-((65536 * a) // max(1, math.isqrt(o))), idx, dx, dy, e, g))
idx += 1
rows.sort()
out = []
for _, _, dx, dy, e, g in rows:
if all(max(abs(t[0] - dx), abs(t[1] - dy), abs(t[2] - e)) > 2 for t in out):
out.append((dx, dy, e, g))
if len(out) == CANDIDATES:
break
return out
def _vertex(l, c, r):
d = l - 2 * c + r
if d >= 0:
return 0
return max(-32, min(32, _rnd(32 * (r - l), -d)))
def _refine(at, dx, dy):
c = at(dx, dy)
return SUB * dx + _vertex(at(dx - 1, dy), c, at(dx + 1, dy)), SUB * dy + _vertex(at(dx, dy - 1), c, at(dx, dy + 1))
def _tile_stats(f, mx, my, px, py):
nx, ny = TILE * mx, TILE * my
tx = [tent(u, mx) for u in range(mx)]
ty = [tent(u, my) for u in range(my)]
rowsum = [] # per bin row, per cell column
for row in range(ny):
base = row * nx
rowsum.append([sum(tx[u] * f[base + (a * mx + px + u) % nx] for u in range(mx)) for a in range(TILE)])
s = [0] * POSITIONS
for b in range(TILE):
rs = [rowsum[(b * my + py + v) % ny] for v in range(my)]
for a in range(TILE):
s[b * TILE + a] = sum(ty[v] * rs[v][a] for v in range(my))
return s
def _oriented(s, o):
sg = -1 if ((o & 1) ^ ((o >> 1) & 1)) else 1
out = [0] * POSITIONS
for y in range(TILE):
for x in range(TILE):
x1 = TILE - 1 - x if o & 1 else x
y1 = TILE - 1 - y if o & 2 else y
x2, y2 = (y1, x1) if o & 4 else (x1, y1)
out[y * TILE + x] = sg * s[y2 * TILE + x2]
return out
_FULL = (1 << POSITIONS) - 1
_ROW_LO = [sum(((1 << (TILE - c)) - 1) << (TILE * r) for r in range(TILE)) for c in range(TILE)]
_ROW_HI = [_FULL ^ _ROW_LO[c] for c in range(TILE)]
def _roll(bm, r, c):
"""The bitmap over offsets s = 32·y0 + x0 whose bit s is bm's bit at position
32·((r + y0) mod 32) + (c + x0) mod 32."""
k = (TILE * r) % POSITIONS
v = ((bm >> k) | (bm << (POSITIONS - k))) & _FULL
if c:
v = ((v >> c) & _ROW_LO[c]) | ((v << (TILE - c)) & _ROW_HI[c])
return v
def _add_plane(counter, plane):
i = 0
while plane:
if i == len(counter):
counter.append(0)
counter[i], plane = counter[i] ^ plane, counter[i] & plane
i += 1
def _agreements(so):
"""For every offset, the agreement count of §7.9.4 step 8: over every bit, the pairs of its
four cells whose ε·S are both above 0 or both not. Bit-sliced over the 1024 offsets."""
pos = sum(1 << p for p in range(POSITIONS) if so[p] > 0)
neg = sum(1 << p for p in range(POSITIONS) if so[p] < 0)
odd_c, eq_c = [], []
for j in range(LENGTH):
planes = []
for k in range(PER_TILE):
p = T_PERM[PER_TILE * j + k]
planes.append(_roll(pos if T_SIGNS[p] > 0 else neg, p // TILE, p % TILE))
a, b, c, d = planes
_add_plane(odd_c, a ^ b ^ c ^ d)
_add_plane(eq_c, (a & b & c & d) | (_FULL & ~(a | b | c | d)))
out = []
for s in range(POSITIONS):
odd = sum(((odd_c[i] >> s) & 1) << i for i in range(len(odd_c)))
eq = sum(((eq_c[i] >> s) & 1) << i for i in range(len(eq_c)))
out.append(2 * LENGTH + odd + 4 * eq)
return out
def _word_s(so, x0, y0):
w = []
for j in range(LENGTH):
t = 0
for k in range(PER_TILE):
p = T_PERM[PER_TILE * j + k]
t += T_SIGNS[p] * so[((p // TILE + y0) % TILE) * TILE + (p % TILE + x0) % TILE]
w.append((1 if t > 0 else 0) ^ T_MASK[j])
return w
def stage2(samples, w, h, n, words_only=False, trace=False):
"""§7.9.4 stage 2: (payload, orientation, errors, resync) or None; with words_only, the
words it decodes, all of them; with trace, every candidate kept (its vectors, lattice, bins
and phase) and the hypotheses decoded, each [agreement, tile, orientation, x0, y0]."""
k = -(-max(w, h) // WORKING_SIDE)
fb = w * h * 3
robs, planes = [], []
for f in range(min(n, STAGE2_FRAMES)):
y, ww, hh = _working(samples[f * fb:(f + 1) * fb], w, h, k)
r = _robust(y, ww, hh)
q, wq, hq = _sign_plane(r, ww, hh)
robs.append(r)
planes.append(q)
at = _autocorrelation(planes, wq, hq)
tiles = []
for ci, (dx, dy, e, g) in enumerate(_candidates(at, wq, hq)):
x1, y1 = _refine(at, dx, dy)
x2, y2 = _refine(at, g, e)
det = x1 * y2 - x2 * y1
if det <= 0:
continue
mx, my = (min(8, max(4, v // (SUB * TILE))) for v in (x1, y2))
nx, ny = TILE * mx, TILE * my
sx, sy = SUB * nx, SUB * ny
folds = [[0] * (nx * ny) for _ in range(4)]
for r in robs:
for yy in range(hh):
for xx in range(ww):
u = sx * (y2 * xx - x2 * yy) // det
v = sy * (x1 * yy - y1 * xx) // det
folds[(u // nx) % 2 + 2 * ((v // ny) % 2)][(v % ny) * nx + u % nx] += r[yy * ww + xx]
best = None
for py in range(my):
for px in range(mx):
parts = [_tile_stats(fc, mx, my, px, py) for fc in folds]
s = [parts[0][i] + parts[1][i] + parts[2][i] + parts[3][i] for i in range(POSITIONS)]
en = sum(s[i] * s[i] - sum(p[i] * p[i] for p in parts) for i in range(POSITIONS))
if best is None or en > best[0]:
best = (en, (px, py), s)
tiles.append(({"lattice": [x1, y1, x2, y2], "bins": [mx, my], "phase": list(best[1]), "candidate": ci}, best[2], [dx, dy, g, e]))
hyps = []
oriented = {}
for ti, (_, s, _v) in enumerate(tiles):
for o in range(8):
so = _oriented(s, o)
oriented[(ti, o)] = so
for i, cnt in enumerate(_agreements(so)):
hyps.append((-cnt, len(hyps), ti, o, i % TILE, i // TILE))
hyps.sort()
if trace:
return {"candidates": [{"vectors": v, "lattice": r["lattice"], "bins": r["bins"], "phase": r["phase"]} for r, _, v in tiles],
"decoded": [[-h[0], h[2], h[3], h[4], h[5]] for h in hyps[:DECODES]]}
words = []
for rank, (ncnt, _, ti, o, x0, y0) in enumerate(hyps[:DECODES]):
wd = _word_s(oriented[(ti, o)], x0, y0)
words.append(wd)
if words_only:
continue
got = decode(wd)
if got is not None:
res = dict(tiles[ti][0], offset=[x0, y0], rank=rank, agreement=-ncnt)
return got[0], o, got[1], res
return words if words_only else None
def stage2_trace(samples, w, h, n):
"""Stage 2's trace (§7.9.4 steps 5 to 9), whatever stage 1 reads; None for frames the
scheme does not admit."""
if not admits(w, h, n) or len(samples) != w * h * 3 * n:
return None
return stage2(samples, w, h, n, trace=True)
def detect_s(samples, w, h, n):
"""§7.9.4: (payload, stage, orientation, errors, resync) or None."""
if not admits(w, h, n) or len(samples) != w * h * 3 * n:
return None
for o in range(8):
got = decode(read_word(samples, w, h, n, o, LAYOUT_S))
if got is not None:
return got[0], 1, o, got[1], None
got = stage2(samples, w, h, n)
if got is None:
return None
return got[0], 2, got[1], got[2], got[3]