`rvs_additive` summed `|i_s|` per 8×8 block. The quantity the reference sums is the gain-adjusted scale, `i_s + gain_per_ch[c]`, with no magnitude. This passed its calibration anyway, and would have kept passing: the two sums differ (12416/40960 agree on luma, 24320/24576 on chroma) but still select the same RVS band for every block in test256, so the additive came out right for the wrong reason. A single-image byte-exact result is not evidence the statistic is correct — only that it is correct enough to land in the same bucket here. The dequant is what distinguishes them: it indexes its Q16 multiplier table by this value directly rather than bucketing it, and is bit-exact only with the gain included — 40960/40960 luma and 24576/24576 chroma with `i_s + gain`, 12416 and 24320 without. Extracted as `rvs_likely` and shared by both callers so they cannot drift apart, and the dequant calibration now derives it from the scale surface instead of reading it from the fixture, so the derivation itself is under test. Co-Authored-By: Claude Opus 4.8 <[email protected]>
105 lines
5.4 KiB
Rust
105 lines
5.4 KiB
Rust
//! Calibration for the **dequant gain**, against the ISO reference on real
|
|
//! `test256.bin` data, for both the luma and chroma planes.
|
|
//!
|
|
//! This closes the last underived decoder input. Before this, the per-position gain
|
|
//! came from a dumped fixture; it is now built from normative assets only:
|
|
//!
|
|
//! ```text
|
|
//! scale[i] = round_ties_even(base[i] · (grfs_flag ? 1.06 : 0.98)) base = [115,125,146,160]
|
|
//! dequant[i] = round(2^7 · 2^16 / scale[i]) // RVS stage, Q16
|
|
//! additive[i] = round(ln(scale[i]/128) / (log_k / 2^7)) // == RVS_ADDITIVE
|
|
//! scaler_vec[c] = round(exp((gain_vector_log[c] + beta) · log_k / 2^7) · 2^10) / 2^10
|
|
//! ```
|
|
//!
|
|
//! `gain_vector_log` is the normative `gain_unit_mlog.csv`; `beta_displacement_log`
|
|
//! comes from the picture header.
|
|
//!
|
|
//! Two rounding hazards are asserted here rather than left to chance:
|
|
//! * the scale rounding is **half-to-even** — `125·1.06` is exactly `132.5` and
|
|
//! `125·0.98` exactly `122.5`, and half-away-from-zero picks the wrong scale;
|
|
//! * the reference's `dequantize_resi` has an integer `>>16` path and a float
|
|
//! `/2^16` path chosen by a *device comparison*. They disagree for negative
|
|
//! input (floor vs. exact). The float path is the one it executes.
|
|
//!
|
|
//! env: WAI_JPEGAI_DEQUANT_FIXTURE = dequant_fixture.json
|
|
//! { y|uv: { channels, h, w, y_q, likely, grfs, gain_vector_log,
|
|
//! beta_displacement_log, rvs_out_expected } }
|
|
#![cfg(feature = "neural_int")]
|
|
|
|
use wai::jpegai_pipeline::{gain_unit_scaler_vec, rvs_dequant, rvs_likely, rvs_tables};
|
|
|
|
/// The tables the reference's `buildTables()` produces, read off its own dumps.
|
|
#[test]
|
|
fn derived_tables_match_reference() {
|
|
// GRFS flag clear → base · 0.98; set → base · 1.06.
|
|
let (scales, dq, add) = rvs_tables(false);
|
|
assert_eq!(scales, [113, 122, 143, 157], "GRFS-clear scales");
|
|
assert_eq!(dq, [74235, 68759, 58662, 53431], "GRFS-clear dequant table (cwg2)");
|
|
assert_eq!(add, [-80, -31, 71, 130], "GRFS-clear additive == RVS_ADDITIVE[1]");
|
|
|
|
let (scales, dq, add) = rvs_tables(true);
|
|
assert_eq!(scales, [122, 132, 155, 170], "GRFS-set scales");
|
|
assert_eq!(dq, [68759, 63550, 54120, 49345], "GRFS-set dequant table (cwg1)");
|
|
assert_eq!(add, [-31, 20, 122, 181], "GRFS-set additive == RVS_ADDITIVE[0]");
|
|
|
|
// Guard the half-to-even hazard explicitly: half-away-from-zero would give
|
|
// 133 / 123 for the band-1 scale and silently mis-select its multiplier.
|
|
assert_eq!((125.0f64 * 1.06).round_ties_even() as i64, 132);
|
|
assert_eq!((125.0f64 * 0.98).round_ties_even() as i64, 122);
|
|
}
|
|
|
|
#[test]
|
|
fn dequant_matches_reference() {
|
|
let path = match std::env::var("WAI_JPEGAI_DEQUANT_FIXTURE") {
|
|
Ok(p) if std::path::Path::new(&p).exists() => p,
|
|
_ => { eprintln!("skip: set WAI_JPEGAI_DEQUANT_FIXTURE"); return; }
|
|
};
|
|
let v: serde_json::Value = serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap();
|
|
|
|
for plane in ["y", "uv"] {
|
|
let o = &v[plane];
|
|
let channels = o["channels"].as_u64().unwrap() as usize;
|
|
let i64v = |k: &str| o[k].as_array().unwrap().iter()
|
|
.map(|x| x.as_i64().unwrap()).collect::<Vec<i64>>();
|
|
|
|
let (h, w) = (o["h"].as_u64().unwrap() as usize, o["w"].as_u64().unwrap() as usize);
|
|
let y_q: Vec<i16> = i64v("y_q").iter().map(|&x| x as i16).collect();
|
|
let grfs: Vec<bool> = o["grfs"].as_array().unwrap().iter()
|
|
.map(|x| x.as_bool().unwrap()).collect();
|
|
let gain_vector_log = i64v("gain_vector_log");
|
|
let beta = o["beta_displacement_log"].as_i64().unwrap();
|
|
let expected: Vec<f32> = o["rvs_out_expected"].as_array().unwrap().iter()
|
|
.map(|x| x.as_f64().unwrap() as f32).collect();
|
|
|
|
// `likely` is DERIVED from the scale surface, not taken from the fixture: the
|
|
// summed quantity is the gain-adjusted scale (i_s + gain_per_ch), and the
|
|
// dequant is bit-exact only with the gain included. Summing |i_s| instead
|
|
// happens to select the same RVS band on this image but is not the same
|
|
// quantity — the fixture check below is what distinguishes them.
|
|
let i_s = i64v("i_s");
|
|
let likely = rvs_likely(&i_s, &gain_vector_log, channels, h, w);
|
|
let lk_expected = i64v("likely_expected");
|
|
let lk_exact = likely.iter().zip(&lk_expected).filter(|(a, b)| a == b).count();
|
|
assert_eq!(
|
|
lk_exact, lk_expected.len(),
|
|
"{plane}: derived likely mismatched ({lk_exact}/{})", lk_expected.len()
|
|
);
|
|
|
|
// The RVS stage must be bit-exact against the reference — it is pure integer
|
|
// table lookup plus an exact power-of-two divide.
|
|
let got = rvs_dequant(&y_q, &likely, &grfs, channels);
|
|
let exact = got.iter().zip(&expected).filter(|(a, b)| a == b).count();
|
|
assert_eq!(
|
|
exact, expected.len(),
|
|
"{plane}: RVS dequant not bit-exact ({exact}/{})", expected.len()
|
|
);
|
|
|
|
// The gain unit is well-formed and strictly positive for every channel, so
|
|
// the composed dequant never divides by ~0.
|
|
let sv = gain_unit_scaler_vec(&gain_vector_log, beta);
|
|
assert_eq!(sv.len(), channels, "{plane}: scaler_vec length");
|
|
assert!(sv.iter().all(|&s| s > 0.0 && s.is_finite()), "{plane}: scaler_vec sane");
|
|
|
|
eprintln!("{plane}: RVS dequant bit-exact {exact}/{}", expected.len());
|
|
}
|
|
}
|