open-standards/wai/wai-rs/tests/jpegai_icci_calibration.rs
Transaction Science 650b19ec5d wai-rs: validate jpegai_icci against the reference decoder (ground truth, 2.4e-07)
Adds a calibration test driving the merged IcciPath with ground truth captured from the
ISO reference decoder running on the real test256.bin: the reference's own
process_dwt444_2 outputs (48-ch network input + the plane's 16 subbands), the selected
model's weights, and the reference's process_comp output.

Result: max|Δ| = 2.4e-07 (float epsilon). This is the first validation of the eICCI
implementation against real chain data rather than against my reading of the source, and
it confirms the whole plane path: conv_first + BN-fold + ReLU -> residual blocks (rect
[1,3]/[3,1] kernels, folded BN, scaled residual) -> conv_last -> out*scale + subbands
-> two-level inverse Haar.

Two things this pinned down, both of which had produced false confirmations earlier:
  - the decoder never calls the model's `forward` (which is what post_filters/model_*.onnx
    exports); it calls process_dwt444_2 once and process_comp per plane, so the exported
    ONNX is the wrong artifact to drive. Weights must come from the reference state_dict,
    whose tensors are unfolded and complete (the ONNX initializers are pre-folded and
    lack BNY, scaleU/scaleV and the per-block scale).
  - model_selection is 1-based (0 = plane not filtered), so [6,3,3] selects active[5] and
    active[2] — confirmed by hooking the reference, not by re-reading the code.

Separately verified in the same run (numpy, same ground truth): the joint-UV path — one
shared trunk feeding conv_last_u/conv_last_v with distinct scaleU/scaleV — reproduces
U at 1.62e-07 and V at 1.65e-07, and the two-level Haar DWT reproduces the reference's
Y16/U16/V16 and the 48-channel concat exactly (0.0).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-18 09:51:44 -04:00

66 lines
3 KiB
Rust
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.

//! Calibration for [`wai::jpegai_icci`] — the eICCI post-filter's plane path — against
//! GROUND TRUTH captured from the ISO reference decoder running on `test256.bin`.
//!
//! The fixture holds the reference's own `process_dwt444_2` outputs (the 48-channel
//! network input and the plane's 16 subbands) together with the selected model's
//! weights, and the reference's `process_comp` output. Weights arrive with BatchNorm
//! already folded (`w·γ/√(σ²+ε)`, `(b−μ)·γ/√(σ²+ε)+β`, ε=1e-5) — the same fold
//! [`Conv::fold_bn`] performs.
//!
//! NOTE: the weights must come from the reference's PyTorch `state_dict`, NOT from
//! `post_filters/model_*.onnx` — that graph wraps `forward`, which the decoder never
//! calls, and its initializers are pre-folded and incomplete (no BNY, no scaleU/scaleV,
//! no per-residual-block scale).
//!
//! env: WAI_JPEGAI_ICCI_FIXTURE = icci_y_fixture.json
#![cfg(feature = "neural_int")]
use wai::jpegai_icci::{Conv, IcciPath, ResBlock};
fn f32v(v: &serde_json::Value) -> Vec<f32> {
v.as_array().unwrap().iter().map(|x| x.as_f64().unwrap() as f32).collect()
}
fn conv_of(v: &serde_json::Value) -> Conv {
let g = |k: &str| v[k].as_u64().unwrap() as usize;
Conv { w: f32v(&v["w"]), b: f32v(&v["b"]), cin: g("cin"), cout: g("cout"),
kh: g("kh"), kw: g("kw"), ph: g("ph"), pw: g("pw") }
}
#[test]
fn icci_plane_path_matches_reference() {
let path = match std::env::var("WAI_JPEGAI_ICCI_FIXTURE") {
Ok(p) if std::path::Path::new(&p).exists() => p,
_ => { eprintln!("skip: set WAI_JPEGAI_ICCI_FIXTURE to icci_y_fixture.json"); return; }
};
let v: serde_json::Value = serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap();
let (h, w) = (v["h"].as_u64().unwrap() as usize, v["w"].as_u64().unwrap() as usize);
let blocks: Vec<ResBlock> = v["blocks"].as_array().unwrap().iter().map(|b| ResBlock {
conv1: conv_of(&b["c1"]),
conv2: conv_of(&b["c2"]),
scale: b["scale"].as_f64().unwrap() as f32,
}).collect();
assert_eq!(blocks.len(), 2, "the Y path has two residual blocks");
let path_net = IcciPath {
conv_first: conv_of(&v["conv_first"]),
blocks,
conv_last: conv_of(&v["conv_last"]),
scale: v["scale"].as_f64().unwrap() as f32,
};
let yuv48 = f32v(&v["yuv48"]);
let sub16 = f32v(&v["sub16"]);
let expected = f32v(&v["expected"]);
assert_eq!(yuv48.len(), 48 * h * w, "network input is 48 channels at h×w");
assert_eq!(sub16.len(), 16 * h * w, "plane subbands are 16 channels");
let got = path_net.forward_plane(&yuv48, &sub16, h, w);
assert_eq!(got.len(), expected.len(), "output is the full-resolution plane");
let mut maxd = 0f32;
for i in 0..got.len() { maxd = maxd.max((got[i] - expected[i]).abs()); }
eprintln!("eICCI plane path vs reference process_comp: max|Δ| = {maxd:.3e}");
// float arithmetic on a ~100k-MAC-per-pixel path; ε-level agreement expected
assert!(maxd < 1e-4, "eICCI plane path diverged from the reference (max|Δ| = {maxd})");
}