open-standards/wai/tools/wai_int_splat_export.py
Transaction Science fb103ac5ae wai: one registry table, candidate capabilities, public-text checks
Every registered string now lives in one table (codecs/registry.rs) with
its kind, status, media class, registering SPEC section, edition and
registration date. `wai registry export` writes registry/capabilities.json,
its JSON Schema, the generated table in REGISTRY.md and vocab v2; --check
fails on drift. vocab v1 stays stable.

A candidate capability (a registered string whose payload format no
revision has pinned) must not be emitted. The C packers, the browser
writers and `wai wrap` refuse one, and `codecs::is_dispatchable` is the
single dispatch and selection predicate (SPEC §4): candidates,
companion-only capabilities, records and derivations are not
dispatchable, and an unregistered string is decoded only where the sink
advertises it.

The codec glossary is linked to the registry through a reviewed table and
held to its schema by a library test; `org` becomes `body`, from a closed
allowlist. The SPEC gains §5 sections on kinds and candidates, a §8
edition rule, corrected citations and rewritten appendices. The
quantum-ops vectors are regenerated with neutral device ids.

CI declares the feature-gated test targets with required-features, checks
the registry export, builds wai-web for wasm32, and runs two public-text
checks (comparative prose, and private name patterns) with a self-test.
The specification, extensions, research notes, READMEs, glossary and
source comments are rewritten to name no company or product and to make
no comparisons.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 22:38:09 -04:00

178 lines
7.8 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.

#!/usr/bin/env python3
"""Train a learned INTEGER-exact Gaussian-splat ATTRIBUTE codec — "the int_hyper of
3D". A Gaussian splat is N Gaussians × 14 attributes (pos[3], scale[3], rot[4],
color[3], opacity) — already a fixed-point (Fx i64) tensor in WAI. A decode that runs
float arithmetic can round differently on different machines, and then the
reconstructed cloud — and its hash — differ between them. Here the synthesis is integer (the
int_hyper machinery, 14 channels instead of RGB's 3): the decoded attribute tensor is
byte-identical on every machine — **splat-equivalence**, a portable cloud hash. The
float rasterizer is sink-supplied presentation; the *attributes* are the contract.
Pipeline (mirrors the audio/world tools): arrange the N Gaussians as a [14, S, S]
attribute image (Morton-sorted by position for locality), train a small transform
codec (Conv2d+GDN / ConvTranspose2d+IGDN), factorized-rANS the latent, PTQ g_s → WHM1,
run an INTEGER reference decode (numpy, mirroring the Rust int_hyper conv/IGDN exactly)
→ the byte-exact attribute tensor → FNV-1a. Reuses the int_hyper 2D integer ops.
python tools/wai_int_splat_export.py <out-dir>
"""
import sys, os, json, base64
import numpy as np, torch, torch.nn as nn, torch.nn.functional as F, warnings
warnings.filterwarnings('ignore')
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from wai_sota_int_synth_export import q_weight_pc, q_weight_conv, conv_int, conv_t_raw, igdn_int, run_int_net, AFRAC, GF, SHIFT
from wai_int_world_export import whm1, ptq_conv, ptq_convT, ptq_igdn, GDN2d
from compressai.ans import RansEncoder
S, C, ATTR = 32, 32, 14 # grid side (N=S²=1024 gaussians) / latent channels / attributes
ONE = 1 << AFRAC
NQ = 6 # attribute quantization step (coarser Fx → compressible, byte-exact)
def morton(xy):
"""Interleave low bits of (ix,iy) → a Z-order key for spatial locality."""
def part(n):
n &= 0xFFFF
n = (n | (n << 8)) & 0x00FF00FF
n = (n | (n << 4)) & 0x0F0F0F0F
n = (n | (n << 2)) & 0x33333333
n = (n | (n << 1)) & 0x55555555
return n
return part(xy[0]) | (part(xy[1]) << 1)
def make_cloud(n):
"""A synthetic but structured Gaussian cloud: points on a wavy shell, scales /
colors / opacity varying smoothly with position — real spatial structure the
transform can exploit. Returns [N, 14] float in natural ranges."""
rng = np.random.RandomState(3)
u, v = rng.rand(n), rng.rand(n)
th, ph = 2 * np.pi * u, np.arccos(2 * v - 1)
r = 1.0 + 0.18 * np.sin(5 * th) * np.sin(3 * ph)
pos = np.stack([r * np.sin(ph) * np.cos(th), r * np.sin(ph) * np.sin(th), r * np.cos(ph)], 1)
scale = 0.03 + 0.02 * (0.5 + 0.5 * np.sin(4 * th))[:, None] * np.ones((n, 3))
rot = np.stack([np.cos(th / 2), np.sin(th / 2) * 0, np.sin(th / 2) * 0, np.sin(th / 2)], 1)
rot /= np.linalg.norm(rot, axis=1, keepdims=True)
col = 0.5 + 0.5 * np.stack([np.sin(th), np.cos(ph), np.sin(th + ph)], 1)
opac = (0.6 + 0.4 * np.cos(2 * ph))[:, None]
return np.concatenate([pos, scale, rot, col, opac], 1).astype(np.float32) # [N,14]
def to_grid(cloud):
"""Sort gaussians by a Morton key over (x,y) → an [ATTR, S, S] image (spatial
locality), and normalize each attribute channel to [0,1] (carry the ranges)."""
n = cloud.shape[0]
p = cloud[:, :2]
q = ((p - p.min(0)) / (np.ptp(p, 0) + 1e-9) * 255).astype(np.int32)
order = np.argsort([morton((q[i, 0], q[i, 1])) for i in range(n)])
g = cloud[order] # [N,14]
lo, hi = g.min(0), g.max(0)
gn = (g - lo) / (hi - lo + 1e-9) # [N,14] in [0,1]
img = gn.T.reshape(ATTR, S, S) # [14,S,S]
return img.astype(np.float32), lo, hi, order
class Codec(nn.Module):
def __init__(self):
super().__init__()
self.g_a = nn.Sequential(nn.Conv2d(ATTR, C, 5, 2, 2), GDN2d(C), nn.Conv2d(C, C, 5, 2, 2))
self.g_s = nn.ModuleList([nn.ConvTranspose2d(C, C, 5, 2, 2, output_padding=1), GDN2d(C),
nn.ConvTranspose2d(C, ATTR, 5, 2, 2, output_padding=1)])
def synth(self, y):
x = y
for m in self.g_s:
x = m(x)
return x
def forward(self, img):
y = self.g_a(img)
y_hat = y + (torch.rand_like(y) - 0.5) # STE quantization
return self.synth(y_hat), y
def fnv1a(buf):
h = 14695981039346656037
for b in buf:
h = ((h ^ b) * 1099511628211) & 0xFFFFFFFFFFFFFFFF
return h
def fit_cdf(sym):
sym = np.asarray(sym, np.int64).reshape(-1)
lo, hi = int(sym.min()), int(sym.max())
n = hi - lo + 1
hist = np.bincount(sym - lo, minlength=n).astype(np.float64)
total = 1 << 16
f = np.maximum((hist / hist.sum() * (total - n)).astype(np.int64), 1)
f[f.argmax()] += (total - 1) - int(f.sum())
cdf = [0]
for v in f:
cdf.append(cdf[-1] + int(v))
cdf.append(total)
return cdf, lo
def gs_decode_attr(model, xi):
"""Integer g_s on the latent xi ([C,sh,sw]@2^afrac) → [ATTR,S,S] u8 attribute
image (round-half-up), the byte-exact cloud the Rust port reproduces."""
x = run_int_net(model, xi)
half = 1 << (AFRAC - 1)
x = np.clip(x, 0, ONE)
return ((x * 255 + half) >> AFRAC).clip(0, 255).astype(np.uint8) # [ATTR,S,S]
def main(outdir):
os.makedirs(outdir, exist_ok=True)
torch.manual_seed(0)
cloud = make_cloud(S * S)
img, lo, hi, order = to_grid(cloud)
x = torch.tensor(img[None]) # [1,14,S,S]
m = Codec()
opt = torch.optim.Adam(m.parameters(), 1.5e-3)
for step in range(3000):
xr, y = m(x)
loss = F.mse_loss(xr, x) + 1e-5 * y.abs().mean()
opt.zero_grad(); loss.backward(); opt.step()
m.eval()
# factorized latent: per-channel median, integer symbols
with torch.no_grad():
y = m.g_a(x)[0].numpy() # [C,sh,sw]
Cc, sh, sw = y.shape
median = np.round(np.median(y.reshape(Cc, -1), 1))
sym = np.round(y - median[:, None, None]).astype(np.int64)
median_q = np.round(median * ONE).astype(np.int64)
xi = (sym * ONE + median_q[:, None, None]).astype(np.int64)
# PTQ g_s → WHM1
gs_layers = [ptq_convT(m.g_s[0]), ptq_igdn(m.g_s[1]), ptq_convT(m.g_s[2])]
attr_int = gs_decode_attr({'afrac': AFRAC, 'gf': GF, 'shift': SHIFT, 'layers': gs_layers}, xi)
gt = (img * 255).astype(np.float64)
snr_i = 10 * np.log10((gt ** 2).sum() / ((attr_int.astype(np.float64) - gt) ** 2).sum() + 1e-12)
attr_hash = format(fnv1a(np.ascontiguousarray(attr_int).tobytes()), '016x')
# factorized rANS over the symbols (per-channel data-fit CDF)
cdfs, offs, flat, idx = [], [], [], []
for ch in range(Cc):
cdf, off = fit_cdf(sym[ch])
cdfs.append(cdf); offs.append(off)
flat.extend(sym[ch].reshape(-1).tolist()); idx.extend([ch] * sh * sw)
bits = RansEncoder().encode_with_indexes(flat, idx, cdfs, [len(c) for c in cdfs], offs)
open(f'{outdir}/splat_gs.bin', 'wb').write(whm1(gs_layers))
json.dump({'c': Cc, 'sh': sh, 'sw': sw, 's': S, 'attr': ATTR, 'afrac': AFRAC,
'cdfs': cdfs, 'offsets': offs, 'median_q': median_q.tolist(),
'rans_b64': base64.b64encode(bytes(bits)).decode(),
'attr_hash': attr_hash, 'n_gaussians': S * S,
'lo': lo.tolist(), 'hi': hi.tolist()}, # per-attr ranges → un-normalize for presentation
open(f'{outdir}/splat.json', 'w'))
np.ascontiguousarray(attr_int).tofile(f'{outdir}/splat_attr.u8')
print(f'int splat codec: {S*S} gaussians × {ATTR} attrs, g_s {len(whm1(gs_layers))}B, rANS {len(bits)}B '
f'-> integer-decode SNR {snr_i:.1f} dB, attr_hash {attr_hash}')
if __name__ == '__main__':
main(sys.argv[1] if len(sys.argv) > 1 else '/tmp/int_splat')