open-standards/wai/pack-conformance/segment_diff.py
Transaction Science 82637436dc wai-pack: the segmenter's state is bounded by its lookahead and window
Fix round, item 9. The streaming segmenter kept every frame's score, so its
memory grew with the stream. Scores are now an opt-in batch output
(Segmenter::with_scores, which segment() uses); a streaming segmenter holds
at most 2L + 2 frame records and the window, and Segmenter::retained says
how much. A test pushes 60,000 frames and checks the bound at every one.

segment_diff.py (from the review's segdiff) compares the streaming
segmenter, through the wai-pack segment-json aid, with pack_verify.py's
on-demand form on seeded random clips, and checks the min and max bounds of
every piece; CI runs it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 09:20:07 -04:00

90 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""wai.pack.scenecut/1, differentially: wai-pack's streaming segmenter against
pack_verify.py's on-demand form.
Seeded random clips (flat and noisy shots, cuts, flashes, small perturbations, empty and
one-frame clips) under random parameters; every piece (start, length, flags and the
statistics classification reads) and every score from `wai-pack segment-json`, pushed
a frame at a time or in a batch, must equal pack_verify.py's, and every piece but the
last must lie between `min` and `max` frames (the last at most `max`).
python3 segment_diff.py <wai-pack binary> [seed] [count]
Exit status 0 only if every clip agrees.
"""
import json
import os
import random
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import pack_verify as PV # noqa: E402
def clip(rng):
w, h = rng.randint(1, 5), rng.randint(1, 4)
scenes = []
for _ in range(rng.randint(1, 5)):
if rng.random() < 0.5:
scenes.append([rng.randrange(256)] * (w * h * 3))
else:
scenes.append([rng.randrange(256) for _ in range(w * h * 3)])
n = rng.randint(0, 40) if rng.random() < 0.9 else rng.randint(0, 3)
frames, cur = [], 0
for _ in range(n):
if rng.random() < 0.15:
cur = rng.randrange(len(scenes))
f = scenes[cur]
if rng.random() < 0.1:
f = scenes[rng.randrange(len(scenes))]
if rng.random() < 0.2:
f = [min(255, max(0, x + rng.randint(-6, 6))) for x in f]
frames.append(list(f))
mn = rng.randint(1, 6)
tg = mn + rng.randint(0, 6)
mx = tg + rng.randint(0, 8)
p = {"min": mn, "target": tg, "max": mx, "cut_abs": rng.choice([1, 50, 100, 300, 500, 1000]),
"cut_rel": rng.choice([0, 1000, 3000, 1000000]), "window": rng.randint(1, 8), "lookahead": rng.randint(0, 7)}
return {"w": w, "h": h, "p": p, "frames": frames, "stream": rng.random() < 0.5}
def main(argv):
if len(argv) < 2:
print(__doc__)
return 2
binary = argv[1]
seed = int(argv[2]) if len(argv) > 2 else 1
count = int(argv[3]) if len(argv) > 3 else 2000
rng = random.Random(seed)
cases = [clip(rng) for _ in range(count)]
r = subprocess.run([binary, "segment-json"], input=("\n".join(json.dumps(c) for c in cases) + "\n").encode(),
capture_output=True)
outs = r.stdout.decode().splitlines()
if r.returncode != 0 or len(outs) != count:
print(r.stderr.decode(errors="replace")[-2000:])
return 1
bad = 0
for c, o in zip(cases, outs):
ro = json.loads(o)
if not c["frames"]:
ok = ro["pieces"] == []
else:
pp, ps = PV.segment(c["frames"], c["w"], c["h"], c["p"])
py = [[x["start"], x["len"], x["flags"], x["abs_diff"], x["changed"], x["compared"], x["occupancy"]] for x in pp]
ok = py == ro["pieces"] and ps == ro["scores"]
pcs = ro["pieces"]
for i, x in enumerate(pcs):
if x[1] > c["p"]["max"] or (i + 1 < len(pcs) and x[1] < c["p"]["min"]):
ok = False
if not ok:
bad += 1
if bad <= 5:
print("DIFF", json.dumps(c["p"]), "frames", len(c["frames"]), "stream", c["stream"])
print(f"segment_diff: seed {seed}, {count} clips, {bad} differences")
return 0 if bad == 0 else 1
if __name__ == "__main__":
sys.exit(main(sys.argv))