//! Calibration: my integer `jpegai_synthesis::decode_y` vs the float reference — //! run the SAME latent through `synthesis16.onnx` (ort) and through `decode_y`, //! compare the Y planes (max abs diff + PSNR). This is the PSNR pass that tells //! whether the whole-net integer executor is numerically faithful (it exposes the //! approximated gates). Needs `neural` (ort). Skipped if assets absent. //! //! env: WAI_JPEGAI_SYNTH16_Y = path to synthesis16.onnx (model_y), //! WAI_JPEGAI_Y_WEIGHTS = path to the exported 16-bit weights JSON. #![cfg(all(feature = "neural", feature = "legacy_int"))] use ort::session::{builder::GraphOptimizationLevel, Session}; use ort::value::Value; use wai::jpegai_synthesis::{decode_y, Weights}; fn ort_synth(path: &str, x: Vec) -> (Vec, Vec) { let mut s = Session::builder(wai::ort_environment().unwrap()).unwrap() .with_intra_threads(1).unwrap().with_inter_threads(1).unwrap() .with_optimization_level(GraphOptimizationLevel::Level3).unwrap() .commit_from_file(path).unwrap(); let name = s.inputs().first().unwrap().name().to_string(); let arr = ndarray::Array4::from_shape_vec((1, 160, 16, 16), x).unwrap(); let v = Value::from_array(arr).unwrap(); let out = s.run(ort::inputs![name.as_str() => v]).unwrap(); let view = out[0].try_extract_array::().unwrap(); (view.iter().copied().collect(), view.shape().to_vec()) } #[test] fn synthesis_int_vs_float_psnr() { let onnx = match std::env::var("WAI_JPEGAI_SYNTH16_Y") { Ok(p) if std::path::Path::new(&p).exists() => p, _ => { eprintln!("skip: set WAI_JPEGAI_SYNTH16_Y to synthesis16.onnx"); return; } }; let wjson = match std::env::var("WAI_JPEGAI_Y_WEIGHTS") { Ok(p) if std::path::Path::new(&p).exists() => p, _ => { eprintln!("skip: set WAI_JPEGAI_Y_WEIGHTS to the weights JSON"); return; } }; // deterministic small latent [160,16,16] (typical post-LSBS magnitude) let n = 160 * 16 * 16; let lf: Vec = (0..n).map(|i| (((i % 97) as f32) - 48.0) / 96.0).collect(); let (yf, shape) = ort_synth(&onnx, lf.clone()); let (fmn, fmx) = yf.iter().fold((f32::MAX, f32::MIN), |(a, b), &v| (a.min(v), b.max(v))); eprintln!("float synth out shape = {:?}, len {}, range [{:.3}, {:.3}]", shape, yf.len(), fmn, fmx); eprintln!("float sample [0..6] = {:?}", &yf[..6]); let li: Vec = lf.iter().map(|&x| (x * 65536.0).round() as i64).collect(); let v: serde_json::Value = serde_json::from_slice(&std::fs::read(&wjson).unwrap()).unwrap(); let wt = Weights::from_json(&v).unwrap(); let yi = decode_y(&li, &wt); assert_eq!(yi.len(), yf.len(), "output size mismatch"); let (mut maxd, mut mse) = (0f64, 0f64); for i in 0..yi.len() { let f = yf[i].clamp(0.0, 255.0) as f64; let d = (yi[i] as f64 - f).abs(); maxd = maxd.max(d); mse += d * d; } mse /= yi.len() as f64; let psnr = if mse > 0.0 { 20.0 * (255.0f64).log10() - 10.0 * mse.log10() } else { f64::INFINITY }; eprintln!("INT vs FLOAT synthesis: max|Δ| = {:.2} MSE = {:.2} PSNR = {:.2} dB", maxd, mse, psnr); // the integer decode reproduces the float ONNX synthesis to within the 16-bit // PTQ quantization (~1 LSB); PSNR ≈ 58 dB. Guard against regressions. assert!(psnr > 45.0, "synthesis PSNR {psnr:.2} dB regressed (expected ~58)"); }