From 8d872a78434a508b4bce1f278ced3bef75fc5b95 Mon Sep 17 00:00:00 2001 From: huyan Date: Thu, 20 Aug 2026 19:51:34 +0800 Subject: [PATCH 1/9] feat(pixel-grid): add standalone detector core Pixel-style images need a reusable grid estimate before reconstruction can run. Port the fixed upstream consensus detector behind bounded PNG/JPEG bytes and a six-field JSON CLI. The detector now runs independently without importing the application or generation pipeline. --- backend/native/pixel_grid_detector/src/acf.rs | 322 ++++++++ .../pixel_grid_detector/src/autocorr.rs | 443 +++++++++++ .../native/pixel_grid_detector/src/core.rs | 438 +++++++++++ .../pixel_grid_detector/src/fusionchan.rs | 585 ++++++++++++++ .../native/pixel_grid_detector/src/gray.rs | 73 ++ .../native/pixel_grid_detector/src/kmeans.rs | 292 +++++++ backend/native/pixel_grid_detector/src/lib.rs | 141 ++++ .../native/pixel_grid_detector/src/main.rs | 34 + .../pixel_grid_detector/src/reconsearch.rs | 427 ++++++++++ .../pixel_grid_detector/src/runlengths.rs | 577 ++++++++++++++ .../native/pixel_grid_detector/src/selfsim.rs | 735 ++++++++++++++++++ .../native/pixel_grid_detector/src/sigproc.rs | 186 +++++ .../pixel_grid_detector/src/varcontrast.rs | 231 ++++++ 13 files changed, 4484 insertions(+) create mode 100644 backend/native/pixel_grid_detector/src/acf.rs create mode 100644 backend/native/pixel_grid_detector/src/autocorr.rs create mode 100644 backend/native/pixel_grid_detector/src/core.rs create mode 100644 backend/native/pixel_grid_detector/src/fusionchan.rs create mode 100644 backend/native/pixel_grid_detector/src/gray.rs create mode 100644 backend/native/pixel_grid_detector/src/kmeans.rs create mode 100644 backend/native/pixel_grid_detector/src/lib.rs create mode 100644 backend/native/pixel_grid_detector/src/main.rs create mode 100644 backend/native/pixel_grid_detector/src/reconsearch.rs create mode 100644 backend/native/pixel_grid_detector/src/runlengths.rs create mode 100644 backend/native/pixel_grid_detector/src/selfsim.rs create mode 100644 backend/native/pixel_grid_detector/src/sigproc.rs create mode 100644 backend/native/pixel_grid_detector/src/varcontrast.rs diff --git a/backend/native/pixel_grid_detector/src/acf.rs b/backend/native/pixel_grid_detector/src/acf.rs new file mode 100644 index 00000000..cb957f33 --- /dev/null +++ b/backend/native/pixel_grid_detector/src/acf.rs @@ -0,0 +1,322 @@ +//! Banded autocorrelation / cepstrum machinery. Mirrors detector/autocorr.py +//! (band_profiles, band_acf, band_cepstrum, comb scores, train_quality, +//! refine_step_acf, ceps_score). + +use rustfft::num_complex::Complex; +use rustfft::FftPlanner; + +pub const MIN_STEP: f64 = 2.0; +pub const MAX_STEP: f64 = 24.0; +pub const STEP_GRID: f64 = 0.02; +pub const BAND: usize = 24; + +/// Sum feature map (row-major, lines x extent) over groups of `band` lines. +/// Returns (n_bands, extent) profiles. +pub fn band_profiles(feat: &[f32], lines: usize, extent: usize, band: usize) -> Vec> { + let nb = std::cmp::max(1, lines / band); + let group = if lines >= band { band } else { lines }; + let mut out = vec![vec![0f64; extent]; nb]; + for b in 0..nb { + let dst = &mut out[b]; + for r in 0..group { + let row = (b * group + r) * extent; + for x in 0..extent { + dst[x] += feat[row + x] as f64; + } + } + // the reference accumulates in float32; round through f32 so the + // FFT sees the same profile values (razor-thin comb ties flip on + // less than this) + for v in dst.iter_mut() { + *v = *v as f32 as f64; + } + } + out +} + +fn next_pow2(n: usize) -> usize { + let mut p = 1; + while p < n { + p <<= 1; + } + p +} + +/// Mean unbiased ACF over band profiles, ac[0] == 1. `prof` rows share length n. +pub fn band_acf(prof: &[Vec], planner: &mut FftPlanner) -> Vec { + let n = prof[0].len(); + let nfft = next_pow2(2 * n); + let fft = planner.plan_fft_forward(nfft); + let ifft = planner.plan_fft_inverse(nfft); + let half = nfft / 2; + + // accumulate per-band power spectra (each normalised by its own + // half-spectrum power, matching numpy's rfft-bin normalisation) + let mut psum = vec![0f64; nfft]; + let mut buf = vec![Complex::new(0f64, 0f64); nfft]; + for row in prof { + let mean = (row.iter().sum::() / n as f64) as f32; + for c in buf.iter_mut() { + *c = Complex::new(0.0, 0.0); + } + for (i, &v) in row.iter().enumerate() { + buf[i] = Complex::new((v as f32 - mean) as f64, 0.0); + } + fft.process(&mut buf); + let mut ssum = 0f64; + for k in 0..=half { + ssum += buf[k].norm_sqr(); + } + let ssum = ssum.max(1e-12); + for k in 0..=half { + let p = buf[k].norm_sqr() / ssum; + psum[k] += p; + if k != 0 && k != half { + psum[nfft - k] += p; + } + } + } + // one inverse FFT of the summed (Hermitian, real) spectrum + for (k, c) in buf.iter_mut().enumerate() { + *c = Complex::new(psum[k], 0.0); + } + ifft.process(&mut buf); + let mut ac = vec![0f64; n]; + for lag in 0..n { + let unbias = n as f64 / ((n - lag).max(1) as f64); + ac[lag] = buf[lag].re / nfft as f64 * unbias; + } + let a0 = ac[0].max(1e-12); + for v in ac.iter_mut() { + *v /= a0; + } + ac +} + +fn median(v: &mut [f64]) -> f64 { + v.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let n = v.len(); + if n == 0 { + return 0.0; + } + if n % 2 == 1 { + v[n / 2] + } else { + 0.5 * (v[n / 2 - 1] + v[n / 2]) + } +} + +/// Cepstrum of the mean log power spectrum over band profiles (first n/2). +pub fn band_cepstrum(prof: &[Vec], planner: &mut FftPlanner) -> Vec { + let n = prof[0].len(); + let nfft = next_pow2(2 * n); + let fft = planner.plan_fft_forward(nfft); + let ifft = planner.plan_fft_inverse(nfft); + let half = nfft / 2; + + let mut pmean = vec![0f64; half + 1]; + let mut buf = vec![Complex::new(0f64, 0f64); nfft]; + for row in prof { + let mean = (row.iter().sum::() / n as f64) as f32; + for c in buf.iter_mut() { + *c = Complex::new(0.0, 0.0); + } + for (i, &v) in row.iter().enumerate() { + buf[i] = Complex::new((v as f32 - mean) as f64, 0.0); + } + fft.process(&mut buf); + for k in 0..=half { + pmean[k] += buf[k].norm_sqr(); + } + } + let nb = prof.len() as f64; + let mut logp: Vec = pmean.iter().map(|&p| (p / nb + 1e-6).ln()).collect(); + let lmean = logp.iter().sum::() / logp.len() as f64; + for v in logp.iter_mut() { + *v -= lmean; + } + for (k, c) in buf.iter_mut().enumerate() { + let v = if k <= half { logp[k] } else { logp[nfft - k] }; + *c = Complex::new(v, 0.0); + } + ifft.process(&mut buf); + let m = n / 2; + let mut c: Vec = (0..m).map(|i| buf[i].re / nfft as f64).collect(); + let mut tmp = c.clone(); + let med = median(&mut tmp); + let cm = c.iter().sum::() / m.max(1) as f64; + let var = c.iter().map(|&v| (v - cm) * (v - cm)).sum::() / m.max(1) as f64; + let std = var.sqrt(); + for v in c.iter_mut() { + *v = (*v - med) / (std + 1e-12); + } + c +} + +/// Linear interp with numpy-matching clipping: i clipped to [0, n-2], +/// fractional part NOT re-clamped (extrapolates past the end like the ref). +pub fn interp_at(arr: &[f64], pos: f64) -> f64 { + let n = arr.len(); + let i = (pos as i64).clamp(0, n as i64 - 2) as usize; + let f = pos - i as f64; + arr[i] * (1.0 - f) + arr[i + 1] * f +} + +/// Comb minus anti-comb on the ACF at multiples of s. +pub fn comb_score(ac: &[f64], s: f64, k_max: usize, k0: f64) -> f64 { + let n = ac.len(); + let kf = ((n as f64 - 2.0) / s).floor() as i64; + let kcap = std::cmp::min(kf.max(0) as usize, k_max); + if kcap < 2 { + return -1.0; + } + let mut num = 0f64; + let mut den = 0f64; + for k in 1..=kcap { + let kd = k as f64; + let w = (-(kd - 1.0) / k0).exp(); + let pk = interp_at(ac, kd * s); + let tr = 0.5 * (interp_at(ac, (kd - 0.5) * s) + interp_at(ac, (kd + 0.5) * s)); + num += w * (pk - tr); + den += w; + } + num / den +} + +/// Comb sum only (no anti-comb). +pub fn plain_comb(ac: &[f64], s: f64, k_max: usize, k0: f64) -> f64 { + let n = ac.len(); + let kf = ((n as f64 - 2.0) / s).floor() as i64; + let kcap = std::cmp::min(kf.max(0) as usize, k_max); + if kcap < 1 { + return 0.0; + } + let mut num = 0f64; + let mut den = 0f64; + for k in 1..=kcap { + let kd = k as f64; + let w = (-(kd - 1.0) / k0).exp(); + num += w * interp_at(ac, kd * s); + den += w; + } + num / den +} + +/// Peak-train fit quality: coverage-weighted inlier mass * exp(-4*rms/s0). +pub fn train_quality(ac: &[f64], s0: f64, k_max: usize) -> f64 { + let n = ac.len(); + let kf = ((n as f64 - 3.0) / s0).floor() as i64; + let kcap = std::cmp::min(kf.max(0) as usize, k_max); + if kcap < 2 { + return 0.0; + } + let mut res: Vec = Vec::new(); + let mut mass = 0f64; + let mut tot = 0usize; + for k in 1..=kcap { + let c0 = k as f64 * s0; + let r = ((0.35 * s0) as i64).max(2) as f64; + let lo = ((c0 - r) as i64).max(1) as usize; + let hi = std::cmp::min(n - 2, (c0 + r).ceil() as usize); + if hi <= lo { + continue; + } + tot += 1; + let mut i = lo; + for j in lo..=hi { + if ac[j] > ac[i] { + i = j; + } + } + if i <= lo || i >= hi || ac[i] <= 0.0 { + continue; + } + let d = (ac[i - 1] - ac[i + 1]) / (2.0 * (ac[i - 1] - 2.0 * ac[i] + ac[i + 1]) + 1e-12); + let p = i as f64 + d.clamp(-1.0, 1.0); + res.push((p - c0) / s0); + mass += ac[i]; + } + if res.is_empty() || tot == 0 { + return 0.0; + } + let rms = (res.iter().map(|r| r * r).sum::() / res.len() as f64).sqrt(); + (mass / tot as f64) * (-4.0 * rms).exp() +} + +/// Refine step by robust line fit through the ACF peak-train positions. +pub fn refine_step_acf(ac: &[f64], s0: f64, k_max: usize) -> f64 { + let n = ac.len(); + let kf = ((n as f64 - 3.0) / s0).floor() as i64; + let kcap = std::cmp::min(kf.max(0) as usize, k_max); + if kcap < 2 { + return s0; + } + let mut pos: Vec = Vec::new(); + let mut ks: Vec = Vec::new(); + let mut wts: Vec = Vec::new(); + for k in 1..=kcap { + let c0 = k as f64 * s0; + let r = ((0.3 * s0) as i64).max(2) as f64; + let lo = ((c0 - r) as i64).max(1) as usize; + let hi = std::cmp::min(n - 2, (c0 + r).ceil() as usize); + if hi <= lo { + continue; + } + let mut i = lo; + for j in lo..=hi { + if ac[j] > ac[i] { + i = j; + } + } + if i <= lo || i >= hi { + continue; + } + let d = (ac[i - 1] - ac[i + 1]) / (2.0 * (ac[i - 1] - 2.0 * ac[i] + ac[i + 1]) + 1e-12); + pos.push(i as f64 + d.clamp(-1.0, 1.0)); + ks.push(k as f64); + wts.push(ac[i].max(1e-6)); + } + if pos.len() < 2 { + return s0; + } + let mut s = s0; + for _ in 0..3 { + let tol = (0.3 * s0).max(1.5); + let mut num = 0f64; + let mut den = 0f64; + let mut kept = 0usize; + for j in 0..pos.len() { + if (pos[j] - ks[j] * s).abs() <= tol { + num += wts[j] * pos[j] * ks[j]; + den += wts[j] * ks[j] * ks[j]; + kept += 1; + } + } + if kept < 2 { + break; + } + s = num / den; + } + if !(0.8 * s0 <= s && s <= 1.25 * s0) { + return s0; + } + s +} + +pub fn ceps_score(c: &[f64], s: f64) -> f64 { + let n = c.len(); + let kf = ((n as f64 - 2.0) / s).floor() as i64; + let kcap = std::cmp::min(kf.max(0) as usize, 6); + if kcap < 1 { + return 0.0; + } + let mut num = 0f64; + let mut den = 0f64; + for k in 1..=kcap { + let kd = k as f64; + let w = (-(kd - 1.0) / 3.0).exp(); + num += w * interp_at(c, kd * s); + den += w; + } + num / den +} diff --git a/backend/native/pixel_grid_detector/src/autocorr.rs b/backend/native/pixel_grid_detector/src/autocorr.rs new file mode 100644 index 00000000..7b586fa6 --- /dev/null +++ b/backend/native/pixel_grid_detector/src/autocorr.rs @@ -0,0 +1,443 @@ +//! Grid detection via banded autocorrelation + cepstrum. Mirrors +//! detector/autocorr.py (axis_estimate, local_count, detect). +//! +//! Feature maps are pre-oriented: every map is row-major (lines x extent) +//! with the scan axis horizontal, so the y-axis maps arrive transposed. + +use crate::acf::*; +use crate::gray::*; +use rayon::prelude::*; +use rustfft::FftPlanner; + +/// One pre-oriented feature map with its fusion weight. +pub struct FeatMap { + pub data: Vec, + pub lines: usize, + pub extent: usize, + pub weight: f64, +} + +pub struct AxisEstimate { + pub step: f64, + pub cands: Vec<(f64, f64)>, + pub acf: Vec, +} + +fn arange(start: f64, stop: f64, step: f64) -> Vec { + let n = ((stop - start) / step).ceil().max(0.0) as usize; + (0..n).map(|i| start + i as f64 * step).collect() +} + +pub fn axis_estimate( + maps: &[FeatMap], + extent: usize, + planner: &mut FftPlanner, +) -> AxisEstimate { + let steps = arange(MIN_STEP, MAX_STEP.min(extent as f64 / 4.0), STEP_GRID); + let ns = steps.len(); + let mut raw = vec![0f64; ns]; + let mut ac_sum = vec![0f64; extent]; + + // evaluate every (map, band) task in parallel; accumulate in the + // reference order afterwards so float sums stay bit-identical + let mut tasks: Vec<(usize, usize, f64)> = Vec::new(); + for (mi, m) in maps.iter().enumerate() { + for (band, bw) in [(12usize, 0.5f64), (BAND, 1.0), (m.lines, 1.0)] { + tasks.push((mi, band, bw)); + } + } + let results: Vec<(Vec, Vec)> = tasks + .par_iter() + .map(|&(mi, band, _)| { + let m = &maps[mi]; + let mut local = FftPlanner::new(); + let prof = band_profiles(&m.data, m.lines, m.extent, band); + let ac = band_acf(&prof, &mut local); + let combs: Vec = steps + .par_iter() + .map(|&s| comb_score(&ac, s, 24, 12.0)) + .collect(); + (ac, combs) + }) + .collect(); + for (t, &(mi, _, bw)) in tasks.iter().enumerate() { + let wgt = maps[mi].weight; + let (ac, combs) = &results[t]; + for i in 0..extent { + ac_sum[i] += wgt * bw * ac[i]; + } + for i in 0..ns { + raw[i] += wgt * bw * combs[i]; + } + } + drop(results); + + // cepstrum vote on the primary feature map + let prof0 = band_profiles(&maps[0].data, maps[0].lines, maps[0].extent, BAND); + let c = band_cepstrum(&prof0, planner); + let raw_max = raw.iter().cloned().fold(f64::MIN, f64::max).max(1e-9); + for (i, &s) in steps.iter().enumerate() { + let cz = ceps_score(&c, s).max(0.0); + raw[i] += 0.1 * cz * raw[i].max(0.0) / raw_max; + } + + // subharmonic suppression (selection only) + let mut pen = vec![0f64; ns]; + for m in [2f64, 3.0, 4.0, 5.0] { + for i in 0..ns { + let s_sub = steps[i] / m; + if s_sub < steps[0] { + continue; + } + let pos = ((s_sub - steps[0]) / STEP_GRID).max(0.0); + let val = interp_at(&raw, pos).max(0.0); + if val > pen[i] { + pen[i] = val; + } + } + } + let sel: Vec = (0..ns).map(|i| raw[i] - 0.5 * pen[i]).collect(); + + // local maxima of sel; parabolic refine on raw + let mut loc: Vec = Vec::new(); + for i in 1..ns.saturating_sub(1) { + if sel[i] > sel[i - 1] && sel[i] >= sel[i + 1] { + loc.push(i); + } + } + loc.sort_by(|&a, &b| sel[b].partial_cmp(&sel[a]).unwrap()); + loc.truncate(8); + let mut cands: Vec<(f64, f64)> = Vec::new(); + for &i in &loc { + let s = if i > 0 && i < ns - 1 { + let d = (raw[i - 1] - raw[i + 1]) + / (2.0 * (raw[i - 1] - 2.0 * raw[i] + raw[i + 1]) + 1e-12); + steps[i] + d.clamp(-1.0, 1.0) * STEP_GRID + } else { + steps[i] + }; + cands.push((s, sel[i])); + } + if cands.is_empty() { + let mut i = 0; + for j in 0..ns { + if sel[j] > sel[i] { + i = j; + } + } + cands.push((steps[i], sel[i])); + } + + // near-tie disambiguation via peak-train quality on the pooled ACF + if cands.len() >= 2 && cands[1].1 >= 0.85 * cands[0].1 { + let a0 = ac_sum[0].max(1e-12); + let ac_norm: Vec = ac_sum.iter().map(|&v| v / a0).collect(); + let top: Vec<(f64, f64)> = cands + .iter() + .cloned() + .filter(|c| c.1 >= 0.85 * cands[0].1) + .take(3) + .collect(); + let mut best_j = 0; + let mut best_v = f64::MIN; + for (j, &(s, z)) in top.iter().enumerate() { + let q = train_quality(&ac_norm, s, 20); + let v = z * (0.1 + q); + if v > best_v { + best_v = v; + best_j = j; + } + } + if best_j != 0 { + let chosen = top[best_j]; + let mut rest: Vec<(f64, f64)> = + cands.iter().cloned().filter(|&c| c != chosen).collect(); + let mut new_cands = vec![chosen]; + new_cands.append(&mut rest); + cands = new_cands; + } + } + + // precision: fit the ACF peak train around each top candidate + for c in cands.iter_mut() { + c.0 = refine_step_acf(&ac_sum, c.0, 24); + } + AxisEstimate { + step: cands[0].0, + cands, + acf: ac_sum, + } +} + +/// Drift-aware cell count via windowed local ACF step integration. +pub fn local_count(maps: &[FeatMap], extent: usize, s0: f64, planner: &mut FftPlanner) -> f64 { + let uniform = extent as f64 / s0; + let nw = (extent as f64 / (14.0 * s0)).clamp(1.0, 8.0) as usize; + if nw < 2 || uniform < 96.0 { + return uniform; + } + // np.linspace(0, extent, nw+1).astype(int) + let edges: Vec = (0..=nw) + .map(|i| (extent as f64 * i as f64 / nw as f64) as usize) + .collect(); + let _ = planner; + let counts: Vec = (0..nw) + .into_par_iter() + .map(|wdw| { + let (a, b) = (edges[wdw], edges[wdw + 1]); + let wlen = b - a; + let mut local = FftPlanner::new(); + let mut ac_loc = vec![0f64; wlen]; + for m in maps { + // column slice [a, b) of the pre-oriented map + let mut sl = vec![0f32; m.lines * wlen]; + for y in 0..m.lines { + let src = y * m.extent + a; + sl[y * wlen..(y + 1) * wlen].copy_from_slice(&m.data[src..src + wlen]); + } + for (band, bw) in [(BAND, 1.0f64), (m.lines, 1.0)] { + let prof = band_profiles(&sl, m.lines, wlen, band); + let ac = band_acf(&prof, &mut local); + for i in 0..wlen { + ac_loc[i] += m.weight * bw * ac[i]; + } + } + } + let scan: Vec = arange(0.85, 1.18, 0.01).iter().map(|f| s0 * f).collect(); + let sc: Vec = scan + .iter() + .map(|&s| comb_score(&ac_loc, s, 12, 8.0)) + .collect(); + let s_glob = comb_score(&ac_loc, s0, 12, 8.0); + let sc_max = sc.iter().cloned().fold(f64::MIN, f64::max); + let s_i = if sc_max > (1.6 * s_glob.max(0.0)).max(0.02) { + let mut i = 0; + for j in 0..sc.len() { + if sc[j] > sc[i] { + i = j; + } + } + refine_step_acf(&ac_loc, scan[i], 12) + } else { + refine_step_acf(&ac_loc, s0, 10) + }; + let s_i = s_i.clamp(0.82 * s0, 1.2 * s0); + wlen as f64 / s_i + }) + .collect(); + let mut total = 0f64; + for c in counts { + total += c; + } + if (total - uniform).abs() >= 2.5 { + total + } else { + uniform + } +} + +pub struct Detection { + pub step_x: f64, + pub step_y: f64, + pub cols: usize, + pub rows: usize, + pub candidates: Vec<(f64, f64)>, +} + +/// Build the pre-oriented feature maps for one axis. +/// For axis=x the maps are used as-is; for axis=y they are transposed. +pub fn build_maps(g: &[f32], gq: &[f32], w: usize, h: usize, y_axis: bool) -> Vec { + if !y_axis { + vec![ + FeatMap { + data: d2x(g, w, h), + lines: h, + extent: w, + weight: 1.0, + }, + FeatMap { + data: d1x(gq, w, h), + lines: h, + extent: w, + weight: 0.7, + }, + ] + } else { + let gt = transpose(g, w, h); + let gqt = transpose(gq, w, h); + vec![ + FeatMap { + data: d2x(>, h, w), + lines: w, + extent: h, + weight: 1.0, + }, + FeatMap { + data: d1x(&gqt, h, w), + lines: w, + extent: h, + weight: 0.7, + }, + ] + } +} + +/// Feature maps + per-axis estimates, reusable by the full-mode arbiter. +pub struct Pre { + pub maps_x: Vec, + pub maps_y: Vec, + pub ex: AxisEstimate, + pub ey: AxisEstimate, +} + +pub fn prepare(rgba: &[u8], w: usize, h: usize) -> Pre { + let g = to_gray(rgba, w, h); + let gq = median_quant(&g, w, h); + let (maps_x, maps_y) = rayon::join( + || build_maps(&g, &gq, w, h, false), + || build_maps(&g, &gq, w, h, true), + ); + let (ex, ey) = rayon::join( + || axis_estimate(&maps_x, w, &mut FftPlanner::new()), + || axis_estimate(&maps_y, h, &mut FftPlanner::new()), + ); + Pre { + maps_x, + maps_y, + ex, + ey, + } +} + +pub fn detect_pre(pre: &Pre, w: usize, h: usize) -> Detection { + let mut planner = FftPlanner::new(); + detect_from_estimates( + &pre.maps_x, + &pre.maps_y, + w, + h, + &pre.ex, + &pre.ey, + &mut planner, + ) +} + +pub fn detect(rgba: &[u8], w: usize, h: usize) -> Detection { + let pre = prepare(rgba, w, h); + detect_pre(&pre, w, h) +} + +pub fn detect_from_estimates( + maps_x: &[FeatMap], + maps_y: &[FeatMap], + w: usize, + h: usize, + ex: &AxisEstimate, + ey: &AxisEstimate, + planner: &mut FftPlanner, +) -> Detection { + let mut sx = ex.step; + let mut sy = ey.step; + let (cx, cy) = (&ex.cands, &ey.cands); + + // cross-axis harmonic reconciliation (dither anti-comb suppression) + let reconcile = |s_small: f64, s_big: f64, ac_small: &[f64]| -> f64 { + for m in [2f64, 3.0] { + if (s_big / s_small - m).abs() <= 0.06 * m { + let pc_big = plain_comb(ac_small, s_big, 16, 8.0); + let pc_small = plain_comb(ac_small, s_small, 16, 8.0); + if pc_big >= 0.55 * pc_small && pc_big > 0.0 { + return refine_step_acf(ac_small, s_big, 24); + } + } + } + s_small + }; + if sx < sy { + sx = reconcile(sx, sy, &ex.acf); + } else if sy < sx { + sy = reconcile(sy, sx, &ey.acf); + } + + // joint square-ish pairing on strong runner-up agreement + if (sx / sy).ln().abs() > 0.08 { + let zx0 = cx.iter().map(|c| c.1).fold(f64::MIN, f64::max); + let zy0 = cy.iter().map(|c| c.1).fold(f64::MIN, f64::max); + let mut best_pair: Option<(f64, f64)> = None; + let mut best_sum = 0f64; + for &(s1, z1) in cx.iter().take(6) { + for &(s2, z2) in cy.iter().take(6) { + if (s1 / s2).ln().abs() <= 0.08 + && z1 >= 0.6 * zx0 + && z2 >= 0.6 * zy0 + && z1 + z2 > best_sum + { + best_sum = z1 + z2; + best_pair = Some((s1, s2)); + } + } + } + if let Some((p1, p2)) = best_pair { + if (p1 / sx).ln().abs() > 1e-6 || (p2 / sy).ln().abs() > 1e-6 { + sx = refine_step_acf(&ex.acf, p1, 24); + sy = refine_step_acf(&ey.acf, p2, 24); + } + } + } + + // looser second pass for mild disagreement (5-13%) + let dis = (sx / sy).ln().abs(); + if dis > 0.05 && dis <= 0.13 { + let zx0 = cx.iter().map(|c| c.1).fold(f64::MIN, f64::max); + let zy0 = cy.iter().map(|c| c.1).fold(f64::MIN, f64::max); + let pull = |cands: &[(f64, f64)], z0: f64, s_other: f64| -> Option<(f64, f64)> { + let mut best: Option<(f64, f64)> = None; + for &(s, z) in cands.iter().take(6) { + if (s / s_other).ln().abs() <= 0.08 && z >= 0.3 * z0 { + if best.map_or(true, |b| z > b.1) { + best = Some((s, z)); + } + } + } + best + }; + let move_y = pull(cy, zy0, sx); + let move_x = pull(cx, zx0, sy); + let score_a = zx0 + move_y.map_or(-1e9, |m| m.1); + let score_b = zy0 + move_x.map_or(-1e9, |m| m.1); + if let (Some(my), true) = (move_y, move_y.is_some() && score_a >= score_b) { + let s_new = refine_step_acf(&ey.acf, my.0, 24); + sy = if (s_new / my.0).ln().abs() < 0.04 { + s_new + } else { + my.0 + }; + } else if let Some(mx) = move_x { + let s_new = refine_step_acf(&ex.acf, mx.0, 24); + sx = if (s_new / mx.0).ln().abs() < 0.04 { + s_new + } else { + mx.0 + }; + } + } + + // drift-aware counting + let _ = planner; + let (n_cols, n_rows) = rayon::join( + || local_count(maps_x, w, sx, &mut FftPlanner::new()), + || local_count(maps_y, h, sy, &mut FftPlanner::new()), + ); + + let mut cands: Vec<(f64, f64)> = cx.iter().chain(cy.iter()).cloned().collect(); + cands.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + cands.truncate(8); + + Detection { + step_x: w as f64 / n_cols, + step_y: h as f64 / n_rows, + cols: n_cols.round_ties_even().max(1.0) as usize, + rows: n_rows.round_ties_even().max(1.0) as usize, + candidates: cands, + } +} diff --git a/backend/native/pixel_grid_detector/src/core.rs b/backend/native/pixel_grid_detector/src/core.rs new file mode 100644 index 00000000..b915ae30 --- /dev/null +++ b/backend/native/pixel_grid_detector/src/core.rs @@ -0,0 +1,438 @@ +//! Consensus core. Mirrors detector/core.py: the fast mode (ac + rl with +//! the calibrated early exit, selfsim on disagreement) and the full mode +//! (stage-1 supermajority consensus over four voters, stage-2 arbitration +//! over fused evidence + square-packer + distillability). + +use crate::autocorr; +use crate::fusionchan; +use crate::reconsearch; +use crate::runlengths; +use crate::selfsim; +use crate::varcontrast::{vc_z_of, CellVarContrast}; + +pub struct CoreResult { + pub step_x: f64, + pub step_y: f64, + pub cols: i64, + pub rows: i64, + pub consensus: String, +} + +struct Prop { + step_x: f64, + step_y: f64, + cols: i64, + rows: i64, +} + +fn size_close(a: &Prop, b: &Prop) -> bool { + let tol_c = std::cmp::max(1, (0.01 * b.cols as f64).round_ties_even() as i64); + let tol_r = std::cmp::max(1, (0.01 * b.rows as f64).round_ties_even() as i64); + (a.cols - b.cols).abs() <= tol_c && (a.rows - b.rows).abs() <= tol_r +} + +/// mode="fast": ac + rl with calibrated early exit; selfsim only on +/// disagreement; first agreeing pair wins, else autocorr flagged lowconf. +pub fn detect_fast(rgba: &[u8], w: usize, h: usize) -> CoreResult { + let (ac, rl) = rayon::join( + || autocorr::detect(rgba, w, h), + || runlengths::detect(rgba, w, h), + ); + let p_ac = Prop { + step_x: ac.step_x, + step_y: ac.step_y, + cols: ac.cols as i64, + rows: ac.rows as i64, + }; + let p_rl = Prop { + step_x: rl.step_x, + step_y: rl.step_y, + cols: rl.cols, + rows: rl.rows, + }; + + // calibrated early exit: rl comb peak S >= 0.30 was always correct on + // the benchmark; with ac agreeing on size, selfsim adds nothing + if size_close(&p_ac, &p_rl) && rl.score_x.min(rl.score_y) >= 0.30 { + let cols = ((p_ac.cols + p_rl.cols) as f64 / 2.0).round_ties_even() as i64; + let rows = ((p_ac.rows + p_rl.rows) as f64 / 2.0).round_ties_even() as i64; + return CoreResult { + step_x: w as f64 / cols as f64, + step_y: h as f64 / rows as f64, + cols, + rows, + consensus: "fast:ac+rl(S)".into(), + }; + } + + let ss = selfsim::detect(rgba, w, h); + let p_ss = Prop { + step_x: ss.step_x, + step_y: ss.step_y, + cols: ss.cols, + rows: ss.rows, + }; + + // first size-close pair in reference order wins (adopting the first + // member's steps), else autocorr flagged low-confidence + let named: [(&str, &Prop); 3] = [("ac", &p_ac), ("rl", &p_rl), ("ss", &p_ss)]; + for i in 0..named.len() { + for j in i + 1..named.len() { + let (na, a) = named[i]; + let (nb, b) = named[j]; + if size_close(a, b) { + return CoreResult { + step_x: a.step_x, + step_y: a.step_y, + cols: a.cols, + rows: a.rows, + consensus: format!("fastmode:{}+{}", na, nb), + }; + } + } + } + CoreResult { + step_x: p_ac.step_x, + step_y: p_ac.step_y, + cols: p_ac.cols, + rows: p_ac.rows, + consensus: "fastmode:lowconf".into(), + } +} + +// ---------------------------------------------------------------- full mode + +const AGREE_TOL: f64 = 0.02; +const AGREE_BONUS: f64 = 0.25; +const VC_W: f64 = 0.20; +const SMALLEST_QUALIFIED: f64 = 0.78; + +/// detail-scale bound from the pooled ACF central peak half-width +fn acf_width(ac: &[f64]) -> f64 { + let tail: &[f64] = if ac.len() > 40 { + &ac[32..ac.len().min(96)] + } else { + &ac[ac.len() / 2..] + }; + let base = if tail.is_empty() { + 0.0 + } else { + crate::sigproc::median(tail) + }; + let c0 = (ac[0] - base).max(1e-9); + for lag in 1..ac.len().min(64) { + if ac[lag] - base < 0.30 * c0 { + return lag as f64; + } + } + 64.0 +} + +/// int(np.median(ints)): middle for odd, truncated mean of middles for even +fn int_median(vals: &[i64]) -> i64 { + let mut v = vals.to_vec(); + v.sort_unstable(); + let n = v.len(); + if n % 2 == 1 { + v[n / 2] + } else { + ((v[n / 2 - 1] + v[n / 2]) as f64 / 2.0) as i64 + } +} + +/// mode="full": consensus + arbitration (best accuracy). +pub fn detect_full(rgba: &[u8], w: usize, h: usize) -> CoreResult { + // ---- cheap trio (keep the autocorr internals for arbitration) + let (pre, rl) = rayon::join( + || autocorr::prepare(rgba, w, h), + || runlengths::detect(rgba, w, h), + ); + let ac = autocorr::detect_pre(&pre, w, h); + let p_ac = Prop { + step_x: ac.step_x, + step_y: ac.step_y, + cols: ac.cols as i64, + rows: ac.rows as i64, + }; + let p_rl = Prop { + step_x: rl.step_x, + step_y: rl.step_y, + cols: rl.cols, + rows: rl.rows, + }; + if size_close(&p_ac, &p_rl) && rl.score_x.min(rl.score_y) >= 0.30 { + let cols = ((p_ac.cols + p_rl.cols) as f64 / 2.0).round_ties_even() as i64; + let rows = ((p_ac.rows + p_rl.rows) as f64 / 2.0).round_ties_even() as i64; + return CoreResult { + step_x: w as f64 / cols as f64, + step_y: h as f64 / rows as f64, + cols, + rows, + consensus: "fast:ac+rl(S)".into(), + }; + } + let ss = selfsim::detect(rgba, w, h); + let p_ss = Prop { + step_x: ss.step_x, + step_y: ss.step_y, + cols: ss.cols, + rows: ss.rows, + }; + + let aspect_ok = |p: &Prop| -> bool { + let r = (p.cols as f64 / (p.rows.max(1)) as f64) / (w as f64 / h as f64); + r.ln().abs() < 0.35 + }; + + // ---- fast path: three cheap detectors already agree on the size + { + let trio = [("ac", &p_ac), ("rl", &p_rl), ("ss", &p_ss)]; + let agree: Vec<&str> = trio + .iter() + .filter(|(_, p)| size_close(p, &p_ac)) + .map(|(n, _)| *n) + .collect(); + if agree.len() >= 3 && aspect_ok(&p_ac) { + let cols = int_median(&[p_ac.cols, p_rl.cols, p_ss.cols]); + let rows = int_median(&[p_ac.rows, p_rl.rows, p_ss.rows]); + return CoreResult { + step_x: w as f64 / cols as f64, + step_y: h as f64 / rows as f64, + cols, + rows, + consensus: format!("fast:{}", agree.join("+")), + }; + } + } + + // ---- heavy evidence: fused channels, square packer, distillability + let ((ev, vc_data), recon) = rayon::join( + || { + rayon::join( + || fusionchan::build_evidence(rgba, w, h), + || { + let vc = CellVarContrast::new(rgba, w, h); + vc.z_curve() + }, + ) + }, + || { + let (ch, nc) = reconsearch::prep(rgba, w, h); + let build = |y_axis: bool, extent: usize| { + let ad = reconsearch::AxisData::new(&ch, w, h, nc, y_axis); + let full = reconsearch::s_grid(extent); + let s_list: Vec = full.iter().cloned().step_by(3).collect(); + let (eb, _er) = reconsearch::coarse_curves(&ad, &s_list); + let trend = reconsearch::Trend::new(&s_list, &eb); + (ad, trend) + }; + let (rx, ry) = rayon::join(|| build(false, w), || build(true, h)); + (rx, ry) + }, + ); + let (vc_logs, vc_z) = vc_data; + let ((ad_x, trend_x), (ad_y, trend_y)) = recon; + + let steps = fusionchan::ladder(); + let log_steps: Vec = steps.iter().map(|&s| s.ln()).collect(); + let norm_curve = |c: Vec| -> Vec { + let m = c.iter().cloned().fold(f64::MIN, f64::max); + if m > 0.0 { + c.iter().map(|&v| v / m).collect() + } else { + c + } + }; + let (curve_x, curve_y) = rayon::join( + || norm_curve(fusionchan::fused_curve(&ev.x, &steps)), + || norm_curve(fusionchan::fused_curve(&ev.y, &steps)), + ); + + // fused-argmax fourth voter + let argmax_first = |c: &[f64]| -> usize { + let mut i = 0; + for j in 0..c.len() { + if c[j] > c[i] { + i = j; + } + } + i + }; + let fu_sx = steps[argmax_first(&curve_x)]; + let fu_sy = steps[argmax_first(&curve_y)]; + let p_fu = Prop { + step_x: fu_sx, + step_y: fu_sy, + cols: std::cmp::max(1, (w as f64 / fu_sx).round_ties_even() as i64), + rows: std::cmp::max(1, (h as f64 / fu_sy).round_ties_even() as i64), + }; + + // ---- stage 1: consensus on output size (supermajority >= 3 of 4) + let named: [(&str, &Prop); 4] = [("ac", &p_ac), ("rl", &p_rl), ("ss", &p_ss), ("fu", &p_fu)]; + { + let mut groups: Vec<(usize, usize, Vec)> = Vec::new(); + for (i1, (_, p1)) in named.iter().enumerate() { + let grp: Vec = named + .iter() + .enumerate() + .filter(|(_, (_, p2))| size_close(p2, p1)) + .map(|(i2, _)| i2) + .collect(); + groups.push((grp.len(), i1, grp)); + } + groups.sort_by(|a, b| b.0.cmp(&a.0)); // stable: ties keep order + let groups: Vec<_> = groups + .into_iter() + .filter(|(_, i1, _)| aspect_ok(named[*i1].1)) + .collect(); + if let Some((cnt, _, grp)) = groups.first() { + if *cnt >= 3 { + let cols = int_median(&grp.iter().map(|&i| named[i].1.cols).collect::>()); + let rows = int_median(&grp.iter().map(|&i| named[i].1.rows).collect::>()); + let names: Vec<&str> = grp.iter().map(|&i| named[i].0).collect(); + return CoreResult { + step_x: w as f64 / cols as f64, + step_y: h as f64 / rows as f64, + cols, + rows, + consensus: names.join("+"), + }; + } + } + } + + // ---- stage 2: arbitration + let detail_cap_x = 8.0 * acf_width(&pre.ex.acf); + let detail_cap_y = 8.0 * acf_width(&pre.ey.acf); + + let recon_at = |x_axis: bool, s: f64| -> f64 { + let (ad, trend, extent) = if x_axis { + (&ad_x, &trend_x, w) + } else { + (&ad_y, &trend_y, h) + }; + if s < 2.0 || s > extent as f64 / 8.0 { + return 0.0; + } + let (eb1, er1) = ad.eval_s(s, false); + reconsearch::score(eb1, er1, trend.at(s)) + }; + let fused_at = |x_axis: bool, s: f64| -> f64 { + if s < steps[0] || s > steps[steps.len() - 1] { + return 0.0; + } + let curve = if x_axis { &curve_x } else { &curve_y }; + crate::sigproc::interp(s.ln(), &log_steps, curve) + }; + let vc_at = |s: f64| -> f64 { + (vc_z_of(&vc_logs, &vc_z, s) / 10.0) + .clamp(0.0, 1.0) + .max(0.0) + }; + + let pick_axis = |x_axis: bool, ac_cands: &[(f64, f64)], extent: usize| -> f64 { + let sources: Vec = named + .iter() + .map(|(_, p)| if x_axis { p.step_x } else { p.step_y }) + .collect(); + let mut pool: Vec = sources.clone(); + pool.extend(ac_cands.iter().take(5).map(|c| c.0)); + + let mut cands: Vec = Vec::new(); + for &s in &pool { + if s <= 1.2 || s > extent as f64 / 3.0 { + continue; + } + if cands.iter().any(|&s2| (s / s2).ln().abs() < 0.01) { + continue; + } + cands.push(s); + } + if cands.is_empty() { + return if x_axis { p_ac.step_x } else { p_ac.step_y }; + } + let rn: Vec = cands.iter().map(|&s| recon_at(x_axis, s)).collect(); + let rmax = rn.iter().cloned().fold(f64::MIN, f64::max); + let recon_ok = rmax > 0.005; + let cap = if x_axis { detail_cap_x } else { detail_cap_y }; + let mut scored: Vec<(f64, f64)> = Vec::new(); + for (i, &s) in cands.iter().enumerate() { + let agree = sources + .iter() + .filter(|&&v| ((v.max(1e-9)) / s).ln().abs() < AGREE_TOL) + .count(); + let mut sc = + fused_at(x_axis, s) + VC_W * vc_at(s) + AGREE_BONUS * (agree as f64 - 1.0).max(0.0); + if recon_ok { + sc += 0.6 * (rn[i] / rmax); + } + if s > cap { + sc *= 0.35; + } + scored.push((s, sc)); + } + let best = scored.iter().map(|&(_, sc)| sc).fold(f64::MIN, f64::max); + scored + .iter() + .filter(|&&(_, sc)| sc >= SMALLEST_QUALIFIED * best) + .map(|&(s, _)| s) + .fold(f64::INFINITY, f64::min) + }; + + let (mut sx, mut sy) = rayon::join( + || pick_axis(true, &pre.ex.cands, w), + || pick_axis(false, &pre.ey.cands, h), + ); + + // aspect guard: prefer the finer step when it has real fused support + if (sx / sy).ln().abs() > 0.45 { + let (s_fine, ax_fine_x) = if sx < sy { (sx, true) } else { (sy, false) }; + if fused_at(ax_fine_x, s_fine) >= 0.35 { + sx = s_fine; + sy = s_fine; + } + } + if (sx / sy).ln().abs() > 0.45 { + let both = |s: f64| -> f64 { + let rx = recon_at(true, s); + let ry = recon_at(false, s); + let r_term = if rx.max(ry) > 0.005 { + 0.5 * (rx + ry) / rx.max(ry).max(1e-9) + } else { + 0.0 + }; + let mut v = r_term + fused_at(true, s) + fused_at(false, s) + 2.0 * VC_W * vc_at(s); + if s > detail_cap_x.min(detail_cap_y) { + v *= 0.35; + } + v + }; + if both(sx) >= both(sy) { + sy = sx; + } else { + sx = sy; + } + } + + sx = crate::acf::refine_step_acf(&pre.ex.acf, sx, 24); + sy = crate::acf::refine_step_acf(&pre.ey.acf, sy, 24); + + if (sx / sy).ln().abs() < 0.08 && (sx - sy).abs() > 1e-6 { + let s = 2.0 * sx * sy / (sx + sy); + sx = crate::acf::refine_step_acf(&pre.ex.acf, s, 24); + sy = crate::acf::refine_step_acf(&pre.ey.acf, s, 24); + } + + let (n_cols, n_rows) = rayon::join( + || autocorr::local_count(&pre.maps_x, w, sx, &mut rustfft::FftPlanner::new()), + || autocorr::local_count(&pre.maps_y, h, sy, &mut rustfft::FftPlanner::new()), + ); + + CoreResult { + step_x: w as f64 / n_cols, + step_y: h as f64 / n_rows, + cols: n_cols.round_ties_even().max(1.0) as i64, + rows: n_rows.round_ties_even().max(1.0) as i64, + consensus: "arbitrated".into(), + } +} diff --git a/backend/native/pixel_grid_detector/src/fusionchan.rs b/backend/native/pixel_grid_detector/src/fusionchan.rs new file mode 100644 index 00000000..28bb32da --- /dev/null +++ b/backend/native/pixel_grid_detector/src/fusionchan.rs @@ -0,0 +1,585 @@ +//! Lean fusion evidence + the four ACTIVE channels the full-mode arbiter +//! reads: ray_e1, tile_e1, tile_e2, spec_e1. Mirrors detector/fusion.py +//! build_evidence(lean=True) + channel_matrix(only=ACTIVE) + fused_curve, +//! with the supporting pieces from detector/channels.py. + +use crate::kmeans::kmeans_quantize_rgba; +use crate::sigproc::*; +use rayon::prelude::*; +use rustfft::num_complex::Complex; +use rustfft::FftPlanner; + +/// fusion.py ladder(): geometric steps 2.0 .. 64.0, ratio 1.03, +/// each value rounded to 4 decimals (running product unrounded). +pub fn ladder() -> Vec { + let mut steps = Vec::new(); + let mut s = 2.0f64; + while s <= 64.0 + 1e-9 { + steps.push((s * 1e4).round_ties_even() / 1e4); + s *= 1.03; + } + steps +} + +/// float32 (H, W, C) with alpha premultiplied and kept as a channel. +/// Returns flat channel-major-last data (h * w * 4). +fn flatten_channels(rgba: &[u8], w: usize, h: usize) -> Vec { + let mut img = vec![0f32; w * h * 4]; + for i in 0..w * h { + let p = i * 4; + let a = rgba[p + 3] as f32 / 255.0; + img[i * 4] = rgba[p] as f32 * a; + img[i * 4 + 1] = rgba[p + 1] as f32 * a; + img[i * 4 + 2] = rgba[p + 2] as f32 * a; + img[i * 4 + 3] = rgba[p + 3] as f32; + } + img +} + +/// E1 cut-energy profiles for both axes: e1x (W+1), e1y (H+1); borders set +/// to the top interior value (image borders are always cuts). +fn e1_profiles(img: &[f32], w: usize, h: usize) -> (Vec, Vec) { + let mut e1x = vec![0f64; w + 1]; + let mut e1y = vec![0f64; h + 1]; + // dx: sqrt(sum_c (img[y][x+1]-img[y][x])^2), summed over rows (f32 sums) + let mut accx = vec![0f64; w.saturating_sub(1)]; + for y in 0..h { + let r = y * w * 4; + for x in 0..w - 1 { + let mut s = 0f32; + for c in 0..4 { + let d = img[r + (x + 1) * 4 + c] - img[r + x * 4 + c]; + s += d * d; + } + accx[x] += (s.sqrt()) as f64; + } + } + for x in 0..w - 1 { + e1x[x + 1] = accx[x] as f32 as f64; + } + for y in 0..h - 1 { + let r0 = y * w * 4; + let r1 = (y + 1) * w * 4; + let mut acc = 0f64; + for x in 0..w { + let mut s = 0f32; + for c in 0..4 { + let d = img[r1 + x * 4 + c] - img[r0 + x * 4 + c]; + s += d * d; + } + acc += (s.sqrt()) as f64; + } + e1y[y + 1] = acc as f32 as f64; + } + let mx = e1x[1..w].iter().cloned().fold(0.0, f64::max); + let my = e1y[1..h].iter().cloned().fold(0.0, f64::max); + let top = mx.max(my).max(1.0); + e1x[0] = top; + e1x[w] = top; + e1y[0] = top; + e1y[h] = top; + (e1x, e1y) +} + +/// Gradient/curvature magnitude maps: dqx (H, W-1), dqy (H-1, W) from the +/// quantized image; cox (H, W-2), coy (H-2, W) from the original. +pub struct GradMaps { + pub dqx: Vec, + pub dqy: Vec, + pub cox: Vec, + pub coy: Vec, + pub w: usize, + pub h: usize, +} + +fn grad_maps(orig: &[f32], quant: &[f32], w: usize, h: usize) -> GradMaps { + let mut dqx = vec![0f32; h * (w - 1)]; + let mut dqy = vec![0f32; (h - 1) * w]; + let mut cox = vec![0f32; h * (w - 2)]; + let mut coy = vec![0f32; (h - 2) * w]; + for y in 0..h { + let r = y * w * 4; + for x in 0..w - 1 { + let mut s = 0f32; + for c in 0..4 { + let d = quant[r + (x + 1) * 4 + c] - quant[r + x * 4 + c]; + s += d * d; + } + dqx[y * (w - 1) + x] = s.sqrt(); + } + for x in 0..w - 2 { + let mut s = 0f32; + for c in 0..4 { + let d = orig[r + (x + 2) * 4 + c] - 2.0 * orig[r + (x + 1) * 4 + c] + + orig[r + x * 4 + c]; + s += d * d; + } + cox[y * (w - 2) + x] = s.sqrt(); + } + } + for y in 0..h - 1 { + let r0 = y * w * 4; + let r1 = (y + 1) * w * 4; + for x in 0..w { + let mut s = 0f32; + for c in 0..4 { + let d = quant[r1 + x * 4 + c] - quant[r0 + x * 4 + c]; + s += d * d; + } + dqy[y * w + x] = s.sqrt(); + } + } + for y in 0..h - 2 { + let r0 = y * w * 4; + let r1 = (y + 1) * w * 4; + let r2 = (y + 2) * w * 4; + for x in 0..w { + let mut s = 0f32; + for c in 0..4 { + let d = orig[r2 + x * 4 + c] - 2.0 * orig[r1 + x * 4 + c] + orig[r0 + x * 4 + c]; + s += d * d; + } + coy[y * w + x] = s.sqrt(); + } + } + GradMaps { + dqx, + dqy, + cox, + coy, + w, + h, + } +} + +// ---------------------------------------------------------------- jpeg + +pub fn jpeg_lattice_strength(profile: &[f64]) -> f64 { + let norm = gaussian_filter1d(&normalise(profile), 0.6); + let n = norm.len() - 1; + let on: Vec = (8..n.saturating_sub(7)).step_by(8).collect(); + let off: Vec = (4..n.saturating_sub(3)).step_by(8).collect(); + if on.len() < 4 || off.len() < 4 { + return 0.0; + } + let interior = &norm[1..n]; + let bm = interior.iter().sum::() / interior.len() as f64; + let var = interior.iter().map(|&v| (v - bm) * (v - bm)).sum::() / interior.len() as f64; + let bs = var.sqrt() + 1e-9; + let z_on = (on.iter().map(|&i| norm[i]).sum::() / on.len() as f64 - bm) + / (bs / (on.len() as f64).sqrt()); + let z_off = (off.iter().map(|&i| norm[i]).sum::() / off.len() as f64 - bm) + / (bs / (off.len() as f64).sqrt()); + z_on - z_off.max(0.0) +} + +pub fn notch_jpeg(profile: &[f64]) -> Vec { + let n = profile.len() - 1; + let width = 1.0; + let mask: Vec = (0..=n) + .map(|p| { + if p == 0 || p == n { + return false; + } + let m = (p % 8).min(8 - (p % 8)) as f64; + m <= width + }) + .collect(); + if !mask.iter().any(|&m| m) || mask.iter().all(|&m| m) { + return profile.to_vec(); + } + let good_x: Vec = (0..=n).filter(|&i| !mask[i]).map(|i| i as f64).collect(); + let good_y: Vec = (0..=n).filter(|&i| !mask[i]).map(|i| profile[i]).collect(); + (0..=n) + .map(|i| { + if mask[i] { + interp(i as f64, &good_x, &good_y) + } else { + profile[i] + } + }) + .collect() +} + +// ---------------------------------------------------------------- channels + +/// Rayleigh phase-coherence score of profile peaks against a lattice +/// (channels._rayleigh_score on a pre-normalised profile). +pub fn rayleigh_score(profile_norm: &[f64], step: f64) -> f64 { + let n = profile_norm.len() - 1; + if step < 2.0 || step > n as f64 / 4.0 { + return 0.0; + } + let dist = ((step * 0.4) as i64).max(1) as f64; + let (pk, hts) = find_peaks(&profile_norm[1..n], 0.15, dist); + if pk.len() < 5 { + return 0.0; + } + let p: Vec = pk.iter().map(|&v| (v + 1) as f64).collect(); + let two_pi = 2.0 * std::f64::consts::PI; + let mut re = 0f64; + let mut im = 0f64; + let mut hsum = 0f64; + let mut h2sum = 0f64; + for (pi, &pos) in p.iter().enumerate() { + let ang = two_pi * pos / step; + re += hts[pi] * ang.cos(); + im += hts[pi] * ang.sin(); + hsum += hts[pi]; + h2sum += hts[pi] * hts[pi]; + } + let rmag = (re * re + im * im).sqrt(); + let big_r = rmag / hsum; + let n_eff = hsum * hsum / h2sum; + let phase = ((im.atan2(re)) / two_pi * step).rem_euclid(step); + let mut slots: Vec = Vec::new(); + for &pos in &p { + let slot = ((pos - phase) / step).round_ties_even() as i64; + if (pos - (phase + slot as f64 * step)).abs() < 0.35 * step { + slots.push(slot); + } + } + slots.sort_unstable(); + slots.dedup(); + let n_slots = std::cmp::max(1, (n as f64 / step) as i64 - 1); + let occupancy = (slots.len() as f64 / n_slots as f64).min(1.0); + (2.0 * n_eff).sqrt() * big_r * occupancy +} + +/// Tile peak lists for a gradient map (pre-oriented: peaks along x). +/// offset mirrors the +1/+... position shift of the diff maps. +pub struct Tile { + pub pos: Vec, + pub heights: Vec, + pub extent: usize, +} + +pub fn tile_peaks(dmap: &[f32], rows: usize, cols: usize, offset: usize) -> Vec { + let tw = (cols / 6).clamp(48, 192); + let th = (rows / 6).clamp(48, 192); + let mut tiles = Vec::new(); + let mut y0 = 0usize; + let ymax = std::cmp::max(rows as i64 - th as i64 / 2, 1) as usize; + while y0 < ymax { + let mut x0 = 0usize; + let xmax = std::cmp::max(cols as i64 - tw as i64 / 2, 1) as usize; + while x0 < xmax { + let y1 = (y0 + th).min(rows); + let x1 = (x0 + tw).min(cols); + if y1 > y0 && x1 > x0 { + let mut prof = vec![0f64; x1 - x0]; + for y in y0..y1 { + for x in x0..x1 { + prof[x - x0] += dmap[y * cols + x] as f64; + } + } + // f32-round the summed profile like numpy's f32 sum + for v in prof.iter_mut() { + *v = *v as f32 as f64; + } + let scale = percentile(&prof, 95.0); + if scale > 0.0 { + let norm: Vec = + prof.iter().map(|&v| (v / scale).clamp(0.0, 1.5)).collect(); + let (pk, hts) = find_peaks(&norm, 0.2, 2.0); + if pk.len() >= 4 { + tiles.push(Tile { + pos: pk.iter().map(|&p| (p + x0 + offset) as f64).collect(), + heights: hts, + extent: prof.len(), + }); + } + } + } + x0 += tw; + } + y0 += th; + } + if tiles.len() > 360 { + let idx: Vec = (0..360) + .map(|i| (i as f64 * (tiles.len() as f64 - 1.0) / 359.0) as usize) + .collect(); + tiles = idx + .into_iter() + .map(|i| Tile { + pos: tiles[i].pos.clone(), + heights: tiles[i].heights.clone(), + extent: tiles[i].extent, + }) + .collect(); + } + tiles +} + +/// Stouffer-combined per-tile Rayleigh coherence at `step`. +pub fn tiles_ray_z(tiles: &[Tile], step: f64) -> f64 { + if tiles.is_empty() || step < 2.0 { + return 0.0; + } + let two_pi = 2.0 * std::f64::consts::PI; + let mut zs: Vec = Vec::new(); + for t in tiles { + if step > t.extent as f64 / 3.0 || t.pos.len() < 4 { + continue; + } + let mut re = 0f64; + let mut im = 0f64; + let mut hsum = 0f64; + let mut h2sum = 0f64; + for (i, &pos) in t.pos.iter().enumerate() { + let ang = two_pi * pos / step; + re += t.heights[i] * ang.cos(); + im += t.heights[i] * ang.sin(); + hsum += t.heights[i]; + h2sum += t.heights[i] * t.heights[i]; + } + let big_r = (re * re + im * im).sqrt() / (hsum + 1e-9); + let n_eff = hsum * hsum / (h2sum + 1e-9); + let phase = (im.atan2(re) / two_pi * step).rem_euclid(step); + let mut slots: Vec = Vec::new(); + for &pos in &t.pos { + let slot = ((pos - phase) / step).round_ties_even() as i64; + if (pos - (phase + slot as f64 * step)).abs() < 0.35 * step { + slots.push(slot); + } + } + slots.sort_unstable(); + slots.dedup(); + let n_slots = std::cmp::max(1, (t.extent as f64 / step) as i64); + let occ = (slots.len() as f64 / n_slots as f64).min(1.0); + zs.push((2.0 * n_eff).sqrt() * big_r * occ - 1.0); + } + if zs.len() < 2 { + return 0.0; + } + zs.iter().sum::() / (zs.len() as f64).sqrt() +} + +// ---------------------------------------------------------------- spectrum + +pub struct Spectrum { + pub freqs: Vec, + pub power: Vec, + pub bg: Vec, +} + +/// Welch-averaged power spectrum of gradient scanline groups (row_group=4, +/// Hann window, hop win/2), pre-oriented map (rows x cols). +pub fn axis_spectrum( + dmap: &[f32], + rows: usize, + cols: usize, + planner: &mut FftPlanner, +) -> Spectrum { + let win = cols.min(1024); + if win < 32 { + return Spectrum { + freqs: vec![0.0], + power: vec![0.0], + bg: vec![0.0], + }; + } + // np.hanning(win): 0.5 - 0.5*cos(2*pi*i/(win-1)) + let window: Vec = (0..win) + .map(|i| 0.5 - 0.5 * (2.0 * std::f64::consts::PI * i as f64 / (win as f64 - 1.0)).cos()) + .collect(); + let hop = std::cmp::max(win / 2, 1); + let fft = planner.plan_fft_forward(win); + let nbins = win / 2 + 1; + let mut specs = vec![0f64; nbins]; + let mut count = 0usize; + let row_group = 4; + let mut buf = vec![Complex::new(0f64, 0f64); win]; + let mut y0 = 0usize; + while y0 + row_group <= rows { + // group profile: f32 sums per numpy + let mut prof = vec![0f64; cols]; + for y in y0..y0 + row_group { + for x in 0..cols { + prof[x] += dmap[y * cols + x] as f64; + } + } + for v in prof.iter_mut() { + *v = *v as f32 as f64; + } + let mean = prof.iter().sum::() / cols as f64; + for v in prof.iter_mut() { + *v -= mean; + } + let mut x0 = 0usize; + while x0 + win <= cols { + for i in 0..win { + buf[i] = Complex::new(prof[x0 + i] * window[i], 0.0); + } + fft.process(&mut buf); + for k in 0..nbins { + specs[k] += buf[k].norm_sqr(); + } + count += 1; + x0 += hop; + } + y0 += row_group; + } + if count == 0 { + return Spectrum { + freqs: vec![0.0], + power: vec![0.0], + bg: vec![0.0], + }; + } + let power: Vec = specs.iter().map(|&p| p / count as f64).collect(); + let freqs: Vec = (0..nbins).map(|k| k as f64 / win as f64).collect(); + // background: median filter, size k odd >= 5 + let mut ksz = std::cmp::max(5, power.len() / 24); + if ksz % 2 == 0 { + ksz += 1; + } + let bg = median_filter1d(&power, ksz); + Spectrum { freqs, power, bg } +} + +pub fn spectral_z(sp: &Spectrum, step: f64) -> f64 { + if step < 2.0 || sp.freqs.len() < 8 { + return 0.0; + } + let f = 1.0 / step; + if f <= sp.freqs[1] || f >= *sp.freqs.last().unwrap() { + return 0.0; + } + // np.searchsorted(freqs, f) side=left + let mut idx = sp.freqs.partition_point(|&v| v < f); + if idx >= sp.power.len() { + idx = sp.power.len() - 1; + } + let lo = std::cmp::max(1, idx as i64 - 1) as usize; + let hi = std::cmp::min(sp.power.len() - 1, idx + 1); + let p = sp.power[lo..=hi].iter().cloned().fold(f64::MIN, f64::max); + let b = sp.bg[idx] + 1e-12; + let ratio = (p / b).max(1e-6); + 6.0 * ratio.log10() +} + +// ---------------------------------------------------------------- evidence + +pub struct AxisEvidence { + pub e1: Vec, + pub tiles1: Vec, + pub tiles2: Vec, + pub spec1: Spectrum, + pub extent: usize, +} + +pub struct Evidence { + pub x: AxisEvidence, + pub y: AxisEvidence, + pub jpeg_z: f64, +} + +fn transpose_map(m: &[f32], rows: usize, cols: usize) -> Vec { + let mut out = vec![0f32; rows * cols]; + for y in 0..rows { + for x in 0..cols { + out[x * rows + y] = m[y * cols + x]; + } + } + out +} + +/// build_evidence(rgba, lean=True): median blur -> kmeans16 -> profiles, +/// jpeg notch, grad maps, tile peaks, E1 spectra. +pub fn build_evidence(rgba: &[u8], w: usize, h: usize) -> Evidence { + // cv2.medianBlur(rgba, 3) on all 4 channels + let base = crate::runlengths::prep_u8(rgba, w, h); + let quantized = kmeans_quantize_rgba(&base, w, h, 16); + + let img_o = flatten_channels(rgba, w, h); + let img_q = flatten_channels(&quantized, w, h); + + let (mut e1x, mut e1y) = e1_profiles(&img_q, w, h); + let jpeg_z = jpeg_lattice_strength(&e1x).max(jpeg_lattice_strength(&e1y)); + if jpeg_z > 5.0 { + e1x = notch_jpeg(&e1x); + e1y = notch_jpeg(&e1y); + } + + let gm = grad_maps(&img_o, &img_q, w, h); + drop(img_o); + drop(img_q); + + // x axis: dqx (h, w-1) offset 1; cox (h, w-2) offset 1 + // y axis: transpose dqy/coy so peaks run along the y extent + let dqy_t = transpose_map(&gm.dqy, h - 1, w); + let coy_t = transpose_map(&gm.coy, h - 2, w); + + let ((tiles1x, tiles2x, spec1x), (tiles1y, tiles2y, spec1y)) = rayon::join( + || { + let mut planner = FftPlanner::new(); + ( + tile_peaks(&gm.dqx, h, w - 1, 1), + tile_peaks(&gm.cox, h, w - 2, 1), + axis_spectrum(&gm.dqx, h, w - 1, &mut planner), + ) + }, + || { + let mut planner = FftPlanner::new(); + ( + tile_peaks(&dqy_t, w, h - 1, 1), + tile_peaks(&coy_t, w, h - 2, 1), + axis_spectrum(&dqy_t, w, h - 1, &mut planner), + ) + }, + ); + + Evidence { + x: AxisEvidence { + e1: e1x, + tiles1: tiles1x, + tiles2: tiles2x, + spec1: spec1x, + extent: w, + }, + y: AxisEvidence { + e1: e1y, + tiles1: tiles1y, + tiles2: tiles2y, + spec1: spec1y, + extent: h, + }, + jpeg_z, + } +} + +/// Fused curve over the ladder: equal-weight sum of per-scan max-normalised +/// {ray_e1, tile_e1, tile_e2, spec_e1} (fusion.py WEIGHTS). +pub fn fused_curve(ev_axis: &AxisEvidence, steps: &[f64]) -> Vec { + let e1_norm = normalise(&ev_axis.e1); + let cols: Vec> = vec![ + steps + .par_iter() + .map(|&s| rayleigh_score(&e1_norm, s)) + .collect(), + steps + .par_iter() + .map(|&s| tiles_ray_z(&ev_axis.tiles1, s)) + .collect(), + steps + .par_iter() + .map(|&s| tiles_ray_z(&ev_axis.tiles2, s)) + .collect(), + steps + .par_iter() + .map(|&s| spectral_z(&ev_axis.spec1, s)) + .collect(), + ]; + let mut fused = vec![0f64; steps.len()]; + for col in &cols { + let m = col.iter().cloned().map(|v| v.max(0.0)).fold(0.0, f64::max); + if m <= 1e-9 { + continue; + } + for i in 0..steps.len() { + fused[i] += col[i].max(0.0) / m; + } + } + fused +} diff --git a/backend/native/pixel_grid_detector/src/gray.rs b/backend/native/pixel_grid_detector/src/gray.rs new file mode 100644 index 00000000..8ea5ed3b --- /dev/null +++ b/backend/native/pixel_grid_detector/src/gray.rs @@ -0,0 +1,73 @@ +//! Feature maps: gray conversion, median quantization, derivative maps. +//! Mirrors detector/autocorr.py (to_gray, median_quant, d1_along, d2_along). + +/// Gray over white per alpha. rgba: interleaved u8, len = w*h*4. +pub fn to_gray(rgba: &[u8], w: usize, h: usize) -> Vec { + let mut g = vec![0f32; w * h]; + for i in 0..w * h { + let p = i * 4; + let a = rgba[p + 3] as f32 / 255.0; + let lum = 0.299 * rgba[p] as f32 + 0.587 * rgba[p + 1] as f32 + 0.114 * rgba[p + 2] as f32; + g[i] = lum * a + 127.5 * (1.0 - a); + } + g +} + +/// 3x3 median (border replicate) then quantize to steps of 12. +pub fn median_quant(g: &[f32], w: usize, h: usize) -> Vec { + // numpy astype(uint8): truncation toward zero (g is within [0, 255.001]) + let u8v: Vec = g.iter().map(|&v| v as u8).collect(); + let mut out = vec![0f32; w * h]; + let mut buf = [0u8; 9]; + for y in 0..h { + for x in 0..w { + let mut k = 0; + for dy in -1i64..=1 { + let yy = (y as i64 + dy).clamp(0, h as i64 - 1) as usize; + for dx in -1i64..=1 { + let xx = (x as i64 + dx).clamp(0, w as i64 - 1) as usize; + buf[k] = u8v[yy * w + xx]; + k += 1; + } + } + buf.sort_unstable(); + // np.round = half-to-even, not half-away-from-zero + out[y * w + x] = (buf[4] as f32 / 12.0).round_ties_even() * 12.0; + } + } + out +} + +/// |d1| along x, padded to width w (row-major lines x extent). +pub fn d1x(g: &[f32], w: usize, h: usize) -> Vec { + let mut out = vec![0f32; w * h]; + for y in 0..h { + let r = y * w; + for x in 0..w - 1 { + out[r + x] = (g[r + x + 1] - g[r + x]).abs(); + } + } + out +} + +/// |d2| along x, padded (1, 1). +pub fn d2x(g: &[f32], w: usize, h: usize) -> Vec { + let mut out = vec![0f32; w * h]; + for y in 0..h { + let r = y * w; + for x in 1..w - 1 { + out[r + x] = (g[r + x + 1] - 2.0 * g[r + x] + g[r + x - 1]).abs(); + } + } + out +} + +pub fn transpose(a: &[f32], w: usize, h: usize) -> Vec { + let mut out = vec![0f32; w * h]; + for y in 0..h { + for x in 0..w { + out[x * h + y] = a[y * w + x]; + } + } + out +} diff --git a/backend/native/pixel_grid_detector/src/kmeans.rs b/backend/native/pixel_grid_detector/src/kmeans.rs new file mode 100644 index 00000000..4f245ff2 --- /dev/null +++ b/backend/native/pixel_grid_detector/src/kmeans.rs @@ -0,0 +1,292 @@ +//! Deterministic k-means++ used by the fusion evidence (quantize.py) and +//! reconsearch (_quantize). cv2.kmeans depends on OpenCV's global RNG, so +//! bit-parity is impossible; this is a faithful-criteria replacement with +//! a fixed-seed RNG. Downstream scores are aggregates over many steps and +//! are robust to the small clustering differences. + +/// xorshift64* — deterministic, decent quality, no deps. +pub struct Rng(u64); + +impl Rng { + pub fn new(seed: u64) -> Rng { + Rng(seed.max(1)) + } + pub fn next_u64(&mut self) -> u64 { + let mut x = self.0; + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + self.0 = x; + x.wrapping_mul(0x2545F4914F6CDD1D) + } + pub fn next_f64(&mut self) -> f64 { + (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64 + } + pub fn below(&mut self, n: usize) -> usize { + (self.next_f64() * n as f64) as usize % n.max(1) + } +} + +/// Evenly-spaced deterministic sample of up to `max_n` row indices. +pub fn even_sample(n: usize, max_n: usize) -> Vec { + if n <= max_n { + (0..n).collect() + } else { + (0..max_n) + .map(|i| ((i as f64) * (n as f64 - 1.0) / (max_n as f64 - 1.0)) as usize) + .collect() + } +} + +fn dist2(a: &[f32; 3], b: &[f32; 3]) -> f64 { + let mut s = 0f64; + for c in 0..3 { + let d = (a[c] - b[c]) as f64; + s += d * d; + } + s +} + +/// k-means++ init + Lloyd iterations; returns (centers, inertia). +fn kmeans_once( + points: &[[f32; 3]], + k: usize, + max_iter: usize, + eps: f64, + rng: &mut Rng, +) -> (Vec<[f32; 3]>, f64) { + let n = points.len(); + let mut centers: Vec<[f32; 3]> = Vec::with_capacity(k); + centers.push(points[rng.below(n)]); + let mut d2: Vec = points.iter().map(|p| dist2(p, ¢ers[0])).collect(); + while centers.len() < k { + let total: f64 = d2.iter().sum(); + let mut pick = 0usize; + if total > 0.0 { + let target = rng.next_f64() * total; + let mut acc = 0f64; + for i in 0..n { + acc += d2[i]; + if acc >= target { + pick = i; + break; + } + } + } else { + pick = rng.below(n); + } + let c = points[pick]; + centers.push(c); + for i in 0..n { + let d = dist2(&points[i], &c); + if d < d2[i] { + d2[i] = d; + } + } + } + + let mut labels = vec![0u32; n]; + for _ in 0..max_iter { + // assign + for i in 0..n { + let mut best = 0usize; + let mut bd = f64::INFINITY; + for (ci, c) in centers.iter().enumerate() { + let d = dist2(&points[i], c); + if d < bd { + bd = d; + best = ci; + } + } + labels[i] = best as u32; + } + // update + let mut sums = vec![[0f64; 3]; k]; + let mut cnts = vec![0usize; k]; + for i in 0..n { + let l = labels[i] as usize; + cnts[l] += 1; + for c in 0..3 { + sums[l][c] += points[i][c] as f64; + } + } + let mut max_shift = 0f64; + for ci in 0..k { + if cnts[ci] == 0 { + // OpenCV-style: reseed empty cluster at the farthest point + let mut far = 0usize; + let mut fd = -1f64; + for i in 0..n { + let d = dist2(&points[i], ¢ers[labels[i] as usize]); + if d > fd { + fd = d; + far = i; + } + } + centers[ci] = points[far]; + max_shift = f64::INFINITY; + continue; + } + let mut nc = [0f32; 3]; + for c in 0..3 { + nc[c] = (sums[ci][c] / cnts[ci] as f64) as f32; + } + let shift = dist2(&nc, ¢ers[ci]); + if shift > max_shift { + max_shift = shift; + } + centers[ci] = nc; + } + if max_shift <= eps * eps { + break; + } + } + let mut inertia = 0f64; + for i in 0..n { + inertia += dist2(&points[i], ¢ers[labels[i] as usize]); + } + (centers, inertia) +} + +/// Multi-attempt k-means (best inertia wins), fixed seed. +pub fn kmeans( + points: &[[f32; 3]], + k: usize, + max_iter: usize, + eps: f64, + attempts: usize, + seed: u64, +) -> Vec<[f32; 3]> { + let mut rng = Rng::new(seed); + let mut best: Option<(Vec<[f32; 3]>, f64)> = None; + for _ in 0..attempts { + let (c, inertia) = kmeans_once(points, k, max_iter, eps, &mut rng); + if best.as_ref().map_or(true, |b| inertia < b.1) { + best = Some((c, inertia)); + } + } + best.unwrap().0 +} + +/// quantize.py kmeans_quantize: k=16 on RGB u8, returns quantized RGBA. +pub fn kmeans_quantize_rgba(rgba: &[u8], w: usize, h: usize, k: usize) -> Vec { + let n = w * h; + let sample_idx = even_sample(n, 60_000); + let sample: Vec<[f32; 3]> = sample_idx + .iter() + .map(|&i| { + let p = i * 4; + [rgba[p] as f32, rgba[p + 1] as f32, rgba[p + 2] as f32] + }) + .collect(); + // k_eff = min(k, unique sample colors) + let mut uniq: Vec<[u8; 3]> = sample_idx + .iter() + .map(|&i| { + let p = i * 4; + [rgba[p], rgba[p + 1], rgba[p + 2]] + }) + .collect(); + uniq.sort_unstable(); + uniq.dedup(); + let k_eff = k.min(uniq.len()); + if k_eff <= 1 { + return rgba.to_vec(); + } + let centers = kmeans(&sample, k_eff, 12, 0.5, 2, 42); + + let mut out = rgba.to_vec(); + for i in 0..n { + let p = i * 4; + let px = [rgba[p] as f32, rgba[p + 1] as f32, rgba[p + 2] as f32]; + let mut best = 0usize; + let mut bd = f64::INFINITY; + for (ci, c) in centers.iter().enumerate() { + let d = dist2(&px, c); + if d < bd { + bd = d; + best = ci; + } + } + for c in 0..3 { + // np.rint = banker's + out[p + c] = (centers[best][c] as f64) + .round_ties_even() + .clamp(0.0, 255.0) as u8; + } + } + out +} + +/// Adaptive structure-only K from coarse (4-bit) colour complexity of the +/// opaque pixels; mirrors detector.reconstruct.adaptive_k. +pub fn adaptive_k(rgba: &[u8], w: usize, h: usize, lo: usize, hi: usize, share: f64) -> usize { + let mut cnt = vec![0u32; 4096]; + let mut total = 0u64; + for i in 0..w * h { + if rgba[i * 4 + 3] > 0 { + let key = (((rgba[i * 4] >> 4) as usize) << 8) + | (((rgba[i * 4 + 1] >> 4) as usize) << 4) + | ((rgba[i * 4 + 2] >> 4) as usize); + cnt[key] += 1; + total += 1; + } + } + if total == 0 { + return lo; + } + let k = cnt + .iter() + .filter(|&&c| c as f64 / total as f64 >= share) + .count(); + k.clamp(lo, hi) +} + +/// k-means (sample for centroids, then assign every pixel) -> (labels, K). +/// Used by two-stage packing for the STRUCTURE quantisation. +pub fn kmeans_labels(rgba: &[u8], w: usize, h: usize, k: usize) -> (Vec, usize) { + use rayon::prelude::*; + let n = w * h; + let opaque: Vec = (0..n).filter(|&i| rgba[i * 4 + 3] > 0).collect(); + let src: Vec = if opaque.is_empty() { + (0..n).collect() + } else { + opaque + }; + let sample_idx = even_sample(src.len(), 60_000); + let sample: Vec<[f32; 3]> = sample_idx + .iter() + .map(|&si| { + let i = src[si]; + [ + rgba[i * 4] as f32, + rgba[i * 4 + 1] as f32, + rgba[i * 4 + 2] as f32, + ] + }) + .collect(); + let k_eff = k.min(sample.len()).max(1); + let centers = kmeans(&sample, k_eff, 15, 0.5, 1, 42); + let kc = centers.len(); + let labels: Vec = (0..n) + .into_par_iter() + .map(|i| { + let p = [ + rgba[i * 4] as f32, + rgba[i * 4 + 1] as f32, + rgba[i * 4 + 2] as f32, + ]; + let mut best = 0u32; + let mut bd = f32::INFINITY; + for (ci, c) in centers.iter().enumerate() { + let d = (p[0] - c[0]).powi(2) + (p[1] - c[1]).powi(2) + (p[2] - c[2]).powi(2); + if d < bd { + bd = d; + best = ci as u32; + } + } + best + }) + .collect(); + (labels, kc) +} diff --git a/backend/native/pixel_grid_detector/src/lib.rs b/backend/native/pixel_grid_detector/src/lib.rs new file mode 100644 index 00000000..4c55ea45 --- /dev/null +++ b/backend/native/pixel_grid_detector/src/lib.rs @@ -0,0 +1,141 @@ +//! 独立像素网格识别器:图片字节输入,网格元数据输出。 + +pub mod acf; +pub mod autocorr; +pub mod core; +pub mod fusionchan; +pub mod gray; +pub mod kmeans; +pub mod reconsearch; +pub mod runlengths; +pub mod selfsim; +pub mod sigproc; +pub mod varcontrast; + +use std::io::Cursor; + +use image::{ImageFormat, ImageReader, Limits}; +use serde::Serialize; + +pub const MAX_INPUT_PIXELS: usize = 4_000_000; +pub const MAX_INPUT_BYTES: usize = 32 * 1024 * 1024; +pub const MIN_INPUT_SIDE: usize = 16; +pub const MIN_RELIABLE_PIXEL_SIZE: f64 = 3.0; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DetectorMode { + Full, + Fast, +} + +#[derive(Debug, Serialize)] +pub struct GridDetection { + pub step_x: f64, + pub step_y: f64, + pub cols: i64, + pub rows: i64, + pub consensus: String, + pub confidence: &'static str, +} + +#[derive(Debug)] +pub struct DetectorError(String); + +impl std::fmt::Display for DetectorError { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str(&self.0) + } +} + +impl std::error::Error for DetectorError {} + +pub fn detect_bytes(source: &[u8], mode: DetectorMode) -> Result { + if source.len() > MAX_INPUT_BYTES { + return Err(DetectorError(format!( + "encoded input exceeds {MAX_INPUT_BYTES} bytes" + ))); + } + + let dimensions_reader = reader_for(source)?; + let (width, height) = dimensions_reader + .into_dimensions() + .map_err(|error| DetectorError(format!("cannot read image dimensions: {error}")))?; + let (width, height) = (width as usize, height as usize); + if width.min(height) < MIN_INPUT_SIDE { + return Err(DetectorError(format!( + "image is too small (minimum side is {MIN_INPUT_SIDE}px)" + ))); + } + let pixel_count = width + .checked_mul(height) + .ok_or_else(|| DetectorError("image dimensions overflow pixel count".into()))?; + if pixel_count > MAX_INPUT_PIXELS { + return Err(DetectorError(format!( + "image is too large (maximum is {MAX_INPUT_PIXELS} pixels)" + ))); + } + + let mut decode_reader = reader_for(source)?; + let mut limits = Limits::default(); + limits.max_image_width = Some(width as u32); + limits.max_image_height = Some(height as u32); + limits.max_alloc = Some(64 * 1024 * 1024); + decode_reader.limits(limits); + let image = decode_reader + .decode() + .map_err(|error| DetectorError(format!("cannot decode PNG/JPEG image: {error}")))? + .to_rgba8(); + + let detected = match mode { + DetectorMode::Full => core::detect_full(image.as_raw(), width, height), + DetectorMode::Fast => core::detect_fast(image.as_raw(), width, height), + }; + let confidence = confidence_for(&detected.consensus, detected.step_x, detected.step_y); + Ok(GridDetection { + step_x: detected.step_x, + step_y: detected.step_y, + cols: detected.cols, + rows: detected.rows, + consensus: detected.consensus, + confidence, + }) +} + +fn reader_for(source: &[u8]) -> Result>, DetectorError> { + let reader = ImageReader::new(Cursor::new(source)) + .with_guessed_format() + .map_err(|error| DetectorError(format!("cannot inspect image: {error}")))?; + match reader.format() { + Some(ImageFormat::Png | ImageFormat::Jpeg) => Ok(reader), + _ => Err(DetectorError("input must be PNG or JPEG".into())), + } +} + +fn confidence_for(consensus: &str, step_x: f64, step_y: f64) -> &'static str { + if step_x < MIN_RELIABLE_PIXEL_SIZE || step_y < MIN_RELIABLE_PIXEL_SIZE { + return "low"; + } + if consensus.starts_with("fast:") { + "high" + } else if consensus == "arbitrated" + || consensus.starts_with("fastmode:") && consensus.contains('+') + { + "medium" + } else { + "low" + } +} + +#[cfg(test)] +mod tests { + use super::confidence_for; + + #[test] + fn confidence_reflects_the_detector_decision_path() { + assert_eq!(confidence_for("fast:ac+rl(S)", 4.0, 4.0), "high"); + assert_eq!(confidence_for("arbitrated", 4.0, 4.0), "medium"); + assert_eq!(confidence_for("fastmode:ac+ss", 4.0, 4.0), "medium"); + assert_eq!(confidence_for("fastmode:lowconf", 4.0, 4.0), "low"); + assert_eq!(confidence_for("fast:ac+rl(S)", 2.9, 4.0), "low"); + } +} diff --git a/backend/native/pixel_grid_detector/src/main.rs b/backend/native/pixel_grid_detector/src/main.rs new file mode 100644 index 00000000..c1ec2931 --- /dev/null +++ b/backend/native/pixel_grid_detector/src/main.rs @@ -0,0 +1,34 @@ +use std::io::{Read, Write}; + +use windup_pixel_grid_detector::{detect_bytes, DetectorMode, MAX_INPUT_BYTES}; + +fn fail(message: impl std::fmt::Display) -> ! { + eprintln!("{message}"); + std::process::exit(1); +} + +fn main() { + let mode = match std::env::args().nth(1).as_deref() { + None | Some("--full") => DetectorMode::Full, + Some("--fast") => DetectorMode::Fast, + Some(option) => fail(format!("unknown option: {option}")), + }; + if std::env::args().nth(2).is_some() { + fail("too many arguments"); + } + + let mut source = Vec::with_capacity(MAX_INPUT_BYTES.min(1024 * 1024)); + std::io::stdin() + .take((MAX_INPUT_BYTES + 1) as u64) + .read_to_end(&mut source) + .unwrap_or_else(|error| fail(format!("cannot read image: {error}"))); + if source.len() > MAX_INPUT_BYTES { + fail(format!("encoded input exceeds {MAX_INPUT_BYTES} bytes")); + } + let result = detect_bytes(&source, mode).unwrap_or_else(|error| fail(error)); + serde_json::to_writer(std::io::stdout(), &result) + .unwrap_or_else(|error| fail(format!("cannot encode detection: {error}"))); + std::io::stdout() + .write_all(b"\n") + .unwrap_or_else(|error| fail(format!("cannot write detection: {error}"))); +} diff --git a/backend/native/pixel_grid_detector/src/reconsearch.rs b/backend/native/pixel_grid_detector/src/reconsearch.rs new file mode 100644 index 00000000..a636545a --- /dev/null +++ b/backend/native/pixel_grid_detector/src/reconsearch.rs @@ -0,0 +1,427 @@ +//! Reconstruction-search ("distillability") channel, trimmed to what +//! core.py's arbitration uses: _prep, AxisData with nbc=1 energies, +//! _s_grid[::3] coarse curves, _trend_fn, eval_s and _score. +//! Mirrors detector/reconsearch.py. + +use crate::kmeans::{even_sample, kmeans}; +use crate::sigproc::{interp, median}; +use rayon::prelude::*; + +pub const S_MIN: f64 = 1.6; +pub const S_MAX: f64 = 24.0; +pub const S_RATIO: f64 = 1.025; +pub const MAX_ROWS: usize = 360; +pub const N_ROWBLOCKS: usize = 5; + +/// k-means quantize premultiplied RGB (f32 planes), centroid-color image. +fn quantize(rgb: &[f32], n: usize) -> Vec { + let sample_idx = even_sample(n, 48_000); + let sample: Vec<[f32; 3]> = sample_idx + .iter() + .map(|&i| [rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]]) + .collect(); + // k limited by unique(round(sample[::7]/8)) colors + let mut coarse: Vec<[i32; 3]> = sample + .iter() + .step_by(7) + .map(|p| { + [ + (p[0] as f64 / 8.0).round_ties_even() as i32, + (p[1] as f64 / 8.0).round_ties_even() as i32, + (p[2] as f64 / 8.0).round_ties_even() as i32, + ] + }) + .collect(); + coarse.sort_unstable(); + coarse.dedup(); + let k = 14usize.min(coarse.len().max(2)); + let centers = kmeans(&sample, k, 25, 0.25, 3, 12345); + + let mut out = vec![0f32; n * 3]; + for i in 0..n { + let px = [rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]]; + let mut best = 0usize; + let mut bd = f64::INFINITY; + for (ci, c) in centers.iter().enumerate() { + let mut d = 0f64; + for ch in 0..3 { + let dd = (px[ch] - c[ch]) as f64; + d += dd * dd; + } + if d < bd { + bd = d; + best = ci; + } + } + for ch in 0..3 { + out[i * 3 + ch] = centers[best][ch]; + } + } + out +} + +/// 3x3 symmetric eigen-decomposition (Jacobi), eigenvalues descending. +fn eigh3_desc(cov: [[f64; 3]; 3]) -> ([f64; 3], [[f64; 3]; 3]) { + let mut a = cov; + let mut v = [[0f64; 3]; 3]; + for i in 0..3 { + v[i][i] = 1.0; + } + for _ in 0..50 { + // largest off-diagonal + let mut p = 0usize; + let mut q = 1usize; + let mut mx = 0f64; + for i in 0..3 { + for j in i + 1..3 { + if a[i][j].abs() > mx { + mx = a[i][j].abs(); + p = i; + q = j; + } + } + } + if mx < 1e-14 { + break; + } + let theta = 0.5 * (a[q][q] - a[p][p]) / a[p][q]; + let t = theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt()); + let c = 1.0 / (t * t + 1.0).sqrt(); + let s = t * c; + let (app, aqq, apq) = (a[p][p], a[q][q], a[p][q]); + a[p][p] = app - t * apq; + a[q][q] = aqq + t * apq; + a[p][q] = 0.0; + a[q][p] = 0.0; + for i in 0..3 { + if i != p && i != q { + let (aip, aiq) = (a[i][p], a[i][q]); + a[i][p] = c * aip - s * aiq; + a[p][i] = a[i][p]; + a[i][q] = s * aip + c * aiq; + a[q][i] = a[i][q]; + } + } + for i in 0..3 { + let (vip, viq) = (v[i][p], v[i][q]); + v[i][p] = c * vip - s * viq; + v[i][q] = s * vip + c * viq; + } + } + let mut order = [0usize, 1, 2]; + order.sort_by(|&i, &j| a[j][j].partial_cmp(&a[i][i]).unwrap()); + let evals = [ + a[order[0]][order[0]], + a[order[1]][order[1]], + a[order[2]][order[2]], + ]; + let mut evecs = [[0f64; 3]; 3]; // evecs[comp][channel] + for (oi, &o) in order.iter().enumerate() { + for ch in 0..3 { + evecs[oi][ch] = v[ch][o]; + } + } + (evals, evecs) +} + +/// _prep: premultiply, quantize, PCA top-2 (+alpha channel if it varies). +/// Returns (channels interleaved f32, n_channels). +pub fn prep(rgba: &[u8], w: usize, h: usize) -> (Vec, usize) { + let n = w * h; + let mut rgb = vec![0f32; n * 3]; + let mut alpha = vec![0f32; n]; + for i in 0..n { + let p = i * 4; + let a = rgba[p + 3] as f32; + alpha[i] = a; + for c in 0..3 { + rgb[i * 3 + c] = rgba[p + c] as f32 * (a / 255.0); + } + } + let q = quantize(&rgb, n); + drop(rgb); + + // PCA over the quantized colors + let mut mu = [0f64; 3]; + for i in 0..n { + for c in 0..3 { + mu[c] += q[i * 3 + c] as f64; + } + } + for c in mu.iter_mut() { + *c /= n as f64; + } + let mut cov = [[0f64; 3]; 3]; + for i in 0..n { + let x = [ + q[i * 3] as f64 - mu[0], + q[i * 3 + 1] as f64 - mu[1], + q[i * 3 + 2] as f64 - mu[2], + ]; + for a in 0..3 { + for b in 0..3 { + cov[a][b] += x[a] * x[b]; + } + } + } + for a in 0..3 { + for b in 0..3 { + cov[a][b] /= n.max(1) as f64; + } + } + let (_evals, evecs) = eigh3_desc(cov); + + let amean = alpha.iter().map(|&v| v as f64).sum::() / n as f64; + let avar = alpha + .iter() + .map(|&v| (v as f64 - amean).powi(2)) + .sum::() + / n as f64; + let use_alpha = avar.sqrt() > 2.0; + let nc = if use_alpha { 3 } else { 2 }; + + let mut ch = vec![0f32; n * nc]; + for i in 0..n { + let x = [ + q[i * 3] as f64 - mu[0], + q[i * 3 + 1] as f64 - mu[1], + q[i * 3 + 2] as f64 - mu[2], + ]; + for comp in 0..2 { + let mut acc = 0f64; + for c in 0..3 { + acc += x[c] * evecs[comp][c]; + } + ch[i * nc + comp] = acc as f32; + } + if use_alpha { + ch[i * nc + 2] = (alpha[i] as f64 - amean) as f32; + } + } + (ch, nc) +} + +/// Cumsum tables for one axis (rows subsampled), nbc=1 machinery only. +pub struct AxisData { + _h: usize, + w: usize, + c: usize, + s: Vec, // (H, W+1, C) cumsum + q_t: Vec, // per-rowblock total sq sums (nbr) + r_edges: Vec, + nbr: usize, + t_sum: f64, +} + +impl AxisData { + /// ch: interleaved (h, w, nc); y_axis transposes first. + pub fn new(ch: &[f32], w: usize, h: usize, nc: usize, y_axis: bool) -> AxisData { + let (rows0, cols) = if y_axis { (w, h) } else { (h, w) }; + let get = |r: usize, col: usize, c: usize| -> f32 { + if y_axis { + ch[(col * w + r) * nc + c] + } else { + ch[(r * w + col) * nc + c] + } + }; + // row subsample: linspace(0, rows0-1, 360).astype(int) + let rows_idx: Vec = if rows0 > MAX_ROWS { + (0..MAX_ROWS) + .map(|i| (i as f64 * (rows0 as f64 - 1.0) / (MAX_ROWS as f64 - 1.0)) as usize) + .collect() + } else { + (0..rows0).collect() + }; + let hh = rows_idx.len(); + + let mut s = vec![0f32; hh * (cols + 1) * nc]; + let mut row_sq = vec![0f64; hh]; // total sq sum per row (f64 of f32 cumdiff) + for (ri, &r0) in rows_idx.iter().enumerate() { + let base = ri * (cols + 1) * nc; + let mut acc = vec![0f32; nc]; + let mut accq = vec![0f32; nc]; + for col in 0..cols { + for c in 0..nc { + let v = get(r0, col, c); + acc[c] += v; + accq[c] += v * v; + s[base + (col + 1) * nc + c] = acc[c]; + } + } + // qcol for seg[1]: diff of Q at edges [0, W] = total sq sum + row_sq[ri] = accq.iter().map(|&v| v as f64).sum(); + } + + let nbr = std::cmp::max(1, std::cmp::min(N_ROWBLOCKS, hh / 48)); + let r_edges: Vec = (0..=nbr) + .map(|i| (hh as f64 * i as f64 / nbr as f64) as usize) + .collect(); + let mut q_t = vec![0f64; nbr]; + for b in 0..nbr { + for r in r_edges[b]..r_edges[b + 1] { + q_t[b] += row_sq[r]; + } + } + // normalizer: total variance about per-rowblock means + let mut t_sum = 0f64; + for b in 0..nbr { + let mut en1 = 0f64; + for r in r_edges[b]..r_edges[b + 1] { + let base = r * (cols + 1) * nc; + let mut sq = 0f64; + for c in 0..nc { + let tot = s[base + cols * nc + c] as f64; + sq += tot * tot; + } + en1 += sq / cols as f64; + } + t_sum += (q_t[b] - en1).max(1e-9); + } + AxisData { + _h: hh, + w: cols, + c: nc, + s, + q_t, + r_edges, + nbr, + t_sum, + } + } + + fn n_phase(&self, s: f64, dense: bool) -> usize { + let base = if dense { 4.0 * s } else { 2.0 * s }; + let n = base.clamp(8.0, 40.0) as usize; + n & !1 + } + + /// Per-rowblock cell energies for all phases at step s (nbc=1). + /// Returns (P, nbr) energies. + fn energy_tiles(&self, s: f64, phases: &[f64]) -> Vec> { + let w = self.w; + let nc = self.c; + let j_cells = (w as f64 / s).ceil() as i64 + 2; + let np_ = phases.len(); + // per (phase, rowblock) energies; parallel over phases + let per_phase: Vec> = phases + .par_iter() + .map(|&phase| { + // boundaries B[j] = clip(phase + s*(j-1), 0, W), j=0..j_cells + let nb = (j_cells + 1) as usize; + let mut bpos = vec![0f64; nb]; + let mut i0 = vec![0usize; nb]; + let mut fr = vec![0f32; nb]; + let mut wds = vec![0f32; nb - 1]; + for j in 0..nb { + let b = (phase + s * (j as f64 - 1.0)).clamp(0.0, w as f64); + bpos[j] = b; + let ii = (b.floor()).clamp(0.0, w as f64 - 1.0) as usize; + i0[j] = ii; + fr[j] = (b - ii as f64) as f32; + } + for j in 0..nb - 1 { + wds[j] = (bpos[j + 1] - bpos[j]) as f32; + } + let mut out = vec![0f64; self.nbr]; + let mut g = vec![0f32; nb * nc]; + for b in 0..self.nbr { + let mut block_acc = 0f64; + for r in self.r_edges[b]..self.r_edges[b + 1] { + let base = r * (w + 1) * nc; + for j in 0..nb { + for c in 0..nc { + let g0 = self.s[base + i0[j] * nc + c]; + let g1 = self.s[base + (i0[j] + 1) * nc + c]; + g[j * nc + c] = g0 + (g1 - g0) * fr[j]; + } + } + let mut row_en = 0f64; + for j in 0..nb - 1 { + let wd = wds[j].max(1e-3); + let mut sq = 0f32; + for c in 0..nc { + let bs = g[(j + 1) * nc + c] - g[j * nc + c]; + sq += bs * bs; + } + row_en += (sq / wd) as f64; + } + // numpy sums the (H,P,J) f32 energies over J in f32 + block_acc += row_en as f32 as f64; + } + out[b] = block_acc; + } + out + }) + .collect(); + let _ = np_; + per_phase + } + + /// (e_best, e_anti) at step s, per-rowblock best phase (nbc=1). + pub fn eval_s(&self, s: f64, dense: bool) -> (f64, f64) { + let n = self.n_phase(s, dense); + let phases: Vec = (0..n).map(|i| i as f64 * (s / n as f64)).collect(); + let ens = self.energy_tiles(s, &phases); // (P, nbr) + let mut err_b = 0f64; + let mut err_r = 0f64; + for b in 0..self.nbr { + let mut ib = 0usize; + for p in 0..n { + if ens[p][b] > ens[ib][b] { + ib = p; + } + } + let best = ens[ib][b]; + let anti = ens[(ib + n / 2) % n][b]; + err_b += (self.q_t[b] - best).max(0.0); + err_r += (self.q_t[b] - anti).max(0.0); + } + (err_b / self.t_sum, err_r / self.t_sum) + } +} + +/// _s_grid(size): geometric 1.6 .. min(24, size/8), ratio 1.025. +pub fn s_grid(size: usize) -> Vec { + let smax = S_MAX.min(size as f64 / 8.0); + let n = ((smax / S_MIN).ln() / S_RATIO.ln()).ceil() as usize; + (0..=n).map(|i| S_MIN * S_RATIO.powi(i as i32)).collect() +} + +/// _trend_fn data: running median of coarse error vs log-step. +pub struct Trend { + ls: Vec, + trend: Vec, +} + +impl Trend { + pub fn new(s_list: &[f64], eb: &[f64]) -> Trend { + let ls: Vec = s_list.iter().map(|&v| v.ln()).collect(); + let mut trend = vec![0f64; eb.len()]; + for i in 0..eb.len() { + let sel: Vec = (0..eb.len()) + .filter(|&j| (ls[j] - ls[i]).abs() <= 0.14) + .map(|j| eb[j]) + .collect(); + trend[i] = median(&sel).max(1e-6); + } + Trend { ls, trend } + } + + pub fn at(&self, s: f64) -> f64 { + interp(s.ln(), &self.ls, &self.trend) + } +} + +/// Distillability score. +pub fn score(eb: f64, er: f64, trend_e: f64) -> f64 { + (er - eb).max(0.0) * (1.0 - eb / trend_e).max(0.0) +} + +/// Coarse curves over a step list (parallel). +pub fn coarse_curves(ad: &AxisData, s_list: &[f64]) -> (Vec, Vec) { + let pairs: Vec<(f64, f64)> = s_list.par_iter().map(|&s| ad.eval_s(s, false)).collect(); + ( + pairs.iter().map(|p| p.0).collect(), + pairs.iter().map(|p| p.1).collect(), + ) +} diff --git a/backend/native/pixel_grid_detector/src/runlengths.rs b/backend/native/pixel_grid_detector/src/runlengths.rs new file mode 100644 index 00000000..8ebbee66 --- /dev/null +++ b/backend/native/pixel_grid_detector/src/runlengths.rs @@ -0,0 +1,577 @@ +//! Grid detection from boundary-run statistics + robust soft-GCD lattice fit. +//! Mirrors detector/runlengths.py exactly (see that file for the theory). +//! +//! Parallelism note: rayon is used only as ordered map -> sequential reduce, +//! so every float accumulation happens in the reference order and results +//! stay bit-identical to the single-threaded path. + +use rayon::prelude::*; + +pub const S_MIN: f64 = 2.05; +pub const S_MAX: f64 = 26.0; +pub const RUN_MIN: f64 = 2.0; +pub const RUN_MAX: f64 = 64.0; +pub const BIN: f64 = 0.25; +pub const COHERENCE: usize = 7; +pub const THR_FRAC: f32 = 0.10; +pub const MAX_LAG: usize = 4; +pub const TILINGS: [(usize, usize); 3] = [(3, 3), (5, 5), (1, 8)]; + +/// 3x3 median blur of one u8 channel, border replicate (cv2.medianBlur). +fn median3_channel(src: &[u8], w: usize, h: usize) -> Vec { + let mut out = vec![0u8; w * h]; + out.par_chunks_mut(w).enumerate().for_each(|(y, row)| { + let mut buf = [0u8; 9]; + for x in 0..w { + let mut k = 0; + for dy in -1i64..=1 { + let yy = (y as i64 + dy).clamp(0, h as i64 - 1) as usize; + for dx in -1i64..=1 { + let xx = (x as i64 + dx).clamp(0, w as i64 - 1) as usize; + buf[k] = src[yy * w + xx]; + k += 1; + } + } + buf.sort_unstable(); + row[x] = buf[4]; + } + }); + out +} + +/// 3x3 median blur of the 4-channel image (cv2.medianBlur semantics), +/// interleaved u8 out. +pub fn prep_u8(rgba: &[u8], w: usize, h: usize) -> Vec { + let chans: Vec> = (0..4usize) + .into_par_iter() + .map(|c| { + let plane: Vec = (0..w * h).map(|i| rgba[i * 4 + c]).collect(); + median3_channel(&plane, w, h) + }) + .collect(); + let mut out = vec![0u8; w * h * 4]; + for i in 0..w * h { + for c in 0..4 { + out[i * 4 + c] = chans[c][i]; + } + } + out +} + +/// Median-filtered float image, alpha folded in as a 4th channel. +/// Returns (H, W, 4) interleaved f32. +pub fn prep(rgba: &[u8], w: usize, h: usize) -> Vec { + prep_u8(rgba, w, h).iter().map(|&v| v as f32).collect() +} + +fn transpose4(img: &[f32], w: usize, h: usize) -> Vec { + let mut out = vec![0f32; w * h * 4]; + for y in 0..h { + for x in 0..w { + let s = (y * w + x) * 4; + let d = (x * h + y) * 4; + out[d..d + 4].copy_from_slice(&img[s..s + 4]); + } + } + out +} + +/// numpy-style linear percentile of the positive entries of `d`. +/// Order statistics via O(n) selection (same values a full sort would give). +fn percentile95_positive(d: &[f32]) -> f32 { + let mut v: Vec = d.iter().cloned().filter(|&x| x > 0.0).collect(); + if v.is_empty() { + return 0.0; + } + let n = v.len(); + let pos = 0.95 * (n as f64 - 1.0); + let lo = pos.floor() as usize; + let hi = pos.ceil() as usize; + let f = (pos - lo as f64) as f32; + let (_, &mut vlo, rest) = v.select_nth_unstable_by(lo, |a, b| a.total_cmp(b)); + let vhi = if hi == lo { + vlo + } else { + // hi == lo + 1: the minimum of the right partition + rest.iter().cloned().fold(f32::INFINITY, f32::min) + }; + vlo + (vhi - vlo) * f +} + +/// Sub-pixel boundary positions along the scan axis of a pre-oriented +/// (rows x cols x 4) image. Returns (scanline_idx, position), row-major. +pub fn boundaries(img4: &[f32], rows: usize, cols: usize) -> (Vec, Vec) { + if cols < 2 { + return (Vec::new(), Vec::new()); + } + let dw = cols - 1; + // L1 color+alpha difference between x-neighbors + let mut d = vec![0f32; rows * dw]; + d.par_chunks_mut(dw).enumerate().for_each(|(y, row)| { + for x in 0..dw { + let a = (y * cols + x) * 4; + let b = a + 4; + let mut s = 0f32; + for c in 0..4 { + s += (img4[b + c] - img4[a + c]).abs(); + } + row[x] = s; + } + }); + // coherence: vertical box mean over COHERENCE rows, border replicate + let mut sm = vec![0f32; rows * dw]; + let half = (COHERENCE / 2) as i64; + { + let d_ref = &d; + sm.par_chunks_mut(dw).enumerate().for_each(|(y, row)| { + for x in 0..dw { + let mut acc = 0f64; + for k in -half..=half { + let yy = (y as i64 + k).clamp(0, rows as i64 - 1) as usize; + acc += d_ref[yy * dw + x] as f64; + } + row[x] = (acc / COHERENCE as f64) as f32; + } + }); + } + let d = sm; + let p95 = percentile95_positive(&d); + let thr = (20.0f32).max(THR_FRAC * p95); + + let mut ys: Vec = Vec::new(); + let mut pos: Vec = Vec::new(); + for y in 0..rows { + let r = y * dw; + for x in 0..dw { + let dc = d[r + x]; + let left = if x == 0 { 0.0 } else { d[r + x - 1] }; + let right = if x == dw - 1 { 0.0 } else { d[r + x + 1] }; + if dc > thr && dc > left && dc >= right { + let dl = d[r + x.saturating_sub(1)]; + let dr = d[r + (x + 1).min(dw - 1)]; + let denom = dl - 2.0 * dc + dr; + let off = if denom.abs() > 1e-6 { + (0.5 * (dl - dr) / denom).clamp(-0.5, 0.5) + } else { + 0.0 + }; + ys.push(y as i64); + pos.push(x as f64 + off as f64); + } + } + } + if pos.len() < 4 { + return (Vec::new(), Vec::new()); + } + (ys, pos) +} + +/// Pooled pos[i+lag]-pos[i] (lag 1..MAX_LAG) within each scanline, +/// filtered to [RUN_MIN, RUN_MAX], stored as f32 like the reference. +pub fn lag_diffs(ys: &[i64], pos: &[f64], max_lag: usize) -> Vec { + let mut out: Vec = Vec::new(); + let n = pos.len(); + for lag in 1..=max_lag { + if n <= lag { + break; + } + for i in 0..n - lag { + if ys[i + lag] == ys[i] { + let d = pos[i + lag] - pos[i]; + if d >= RUN_MIN && d <= RUN_MAX { + out.push(d as f32); + } + } + } + } + out +} + +/// numpy-equivalent uniform histogram over (0, RUN_MAX); returns only the +/// non-empty bins as (count, center) pairs. +fn hist(runs: &[f32], bin_w: f64) -> (Vec, Vec) { + let nb = (RUN_MAX / bin_w) as usize + 1; + let mut counts = vec![0u64; nb]; + for &r in runs { + let v = r as f64; + if !(0.0..=RUN_MAX).contains(&v) { + continue; + } + let mut i = (v / RUN_MAX * nb as f64) as usize; + if i >= nb { + i = nb - 1; + } + // numpy's float-error correction against the exact edges + let edge = |j: usize| RUN_MAX * j as f64 / nb as f64; + if v < edge(i) && i > 0 { + i -= 1; + } else if i + 1 < nb && v >= edge(i + 1) { + i += 1; + } + counts[i] += 1; + } + let mut hv: Vec = Vec::new(); + let mut cv: Vec = Vec::new(); + for i in 0..nb { + if counts[i] > 0 { + hv.push(counts[i] as f64); + let e0 = RUN_MAX * i as f64 / nb as f64; + let e1 = RUN_MAX * (i + 1) as f64 / nb as f64; + cv.push(0.5 * (e0 + e1)); + } + } + (hv, cv) +} + +/// S(s) = weighted mean over distances of cos(2*pi*r/s). Returns (S, total). +fn comb_score_grid(runs: &[f32], s_grid: &[f64], bin_w: f64) -> (Vec, f64) { + if runs.is_empty() { + return (vec![0.0; s_grid.len()], 0.0); + } + let (hv, cv) = hist(runs, bin_w); + let total: f64 = hv.iter().sum(); + let wsum = total; + let s_out: Vec = s_grid + .par_iter() + .map(|&s| { + let mut acc = 0f64; + for (j, &c) in cv.iter().enumerate() { + acc += hv[j] * (2.0 * std::f64::consts::PI * c / s).cos(); + } + acc / wsum + }) + .collect(); + (s_out, total) +} + +pub struct PickResult { + pub s: Option, + pub v: f64, + pub cands: Vec<(f64, f64)>, +} + +/// Best step from the comb score, with largest-near-tie divisor logic. +pub fn pick_step(runs: &[f32], s_grid: &[f64]) -> PickResult { + let none = PickResult { + s: None, + v: 0.0, + cands: Vec::new(), + }; + let (s_score, total) = comb_score_grid(runs, s_grid, BIN); + if total < 50.0 { + return none; + } + let ns = s_grid.len(); + let mut idx: Vec = Vec::new(); + for i in 1..ns.saturating_sub(1) { + if s_score[i] > s_score[i - 1] && s_score[i] >= s_score[i + 1] { + idx.push(i); + } + } + if idx.is_empty() { + return none; + } + let mut order = idx.clone(); + order.sort_by(|&a, &b| s_score[b].partial_cmp(&s_score[a]).unwrap()); + let smax = s_score[order[0]]; + if smax <= 0.0 { + return none; + } + let cands: Vec<(f64, f64)> = order + .iter() + .take(12) + .map(|&i| (s_grid[i], s_score[i])) + .collect(); + + let fund = |s: f64| -> f64 { + let tol = (0.6f64).max(0.18 * s); + let cnt = runs + .iter() + .filter(|&&r| ((r as f64) - s).abs() < tol) + .count(); + cnt as f64 / total + }; + + let mut tied: Vec<(f64, f64)> = order + .iter() + .filter(|&&i| s_score[i] >= 0.70 * smax) + .map(|&i| (s_grid[i], s_score[i])) + .collect(); + tied.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); + let mut best_s = s_grid[order[0]]; + let mut best_v = smax; + for &(s, v) in &tied { + if fund(s) >= 0.04 { + best_s = s; + best_v = v; + break; + } + } + PickResult { + s: Some(best_s), + v: best_v, + cands, + } +} + +fn arange(start: f64, stop: f64, step: f64) -> Vec { + let n = ((stop - start) / step).ceil().max(0.0) as usize; + (0..n).map(|i| start + i as f64 * step).collect() +} + +/// Sub-pixel refinement: fine k-weighted comb + k-weighted LS polish. +pub fn refine(runs: &[f32], s0: f64) -> f64 { + if runs.is_empty() { + return s0; + } + let (hv, cv) = hist(runs, 0.05); + let fine = arange(0.94 * s0, 1.06 * s0, 0.002); + let sf: Vec = fine + .par_iter() + .map(|&f| { + let mut acc = 0f64; + for j in 0..cv.len() { + let wk = cv[j] / s0; + acc += hv[j] * wk * (2.0 * std::f64::consts::PI * cv[j] / f).cos(); + } + acc + }) + .collect(); + let mut best_i = 0; + for fi in 0..sf.len() { + if sf[fi] > sf[best_i] { + best_i = fi; + } + } + let mut s = fine[best_i]; + for _ in 0..2 { + let mut num = 0f64; + let mut den = 0f64; + for &r in runs { + let rf = r as f64; + let k = (rf / s).round_ties_even(); + if k < 1.0 { + continue; + } + let res = (rf - k * s).abs(); + let w = (1.0 - res / (0.30 * s)).clamp(0.0, 1.0) * k; + num += w * k * rf; + den += w * k * k; + } + if den <= 0.0 { + break; + } + s = num / den; + } + s +} + +/// Fine comb peak near s0 for one tile; None if unreliable. +fn tile_peak(diffs: &[f32], s0: f64) -> Option { + if diffs.len() < 350 { + return None; + } + let fine = arange(0.87 * s0, 1.13 * s0, 0.005); + let (hv, cv) = hist(diffs, 0.05); + let total: f64 = hv.iter().sum(); + let sf: Vec = fine + .iter() + .map(|&f| { + let mut acc = 0f64; + for j in 0..cv.len() { + acc += hv[j] * (2.0 * std::f64::consts::PI * cv[j] / f).cos(); + } + acc / total + }) + .collect(); + let mut best_i = 0; + for fi in 0..sf.len() { + if sf[fi] > sf[best_i] { + best_i = fi; + } + } + if best_i == 0 || best_i == fine.len() - 1 || sf[best_i] < 0.12 { + return None; + } + Some(fine[best_i]) +} + +/// cols = W * mean(1/s_local), pooled over several tile grids. +pub fn integrate_step(ys: &[i64], pos: &[f64], n_perp: usize, n_scan: usize, s0: f64) -> f64 { + // enumerate tiles in reference order, evaluate in parallel, reduce in order + let mut tiles: Vec<(f64, f64, f64, f64)> = Vec::new(); + for &(tp, tsc) in TILINGS.iter() { + // np.linspace: value = i * (extent / n) + let dy = n_perp as f64 / tp as f64; + let dx = n_scan as f64 / tsc as f64; + for i in 0..tp { + let y0 = i as f64 * dy; + let y1 = if i + 1 == tp { + n_perp as f64 + } else { + (i + 1) as f64 * dy + }; + for j in 0..tsc { + let x0 = j as f64 * dx; + let x1 = if j + 1 == tsc { + n_scan as f64 + } else { + (j + 1) as f64 * dx + }; + tiles.push((y0, y1, x0, x1)); + } + } + } + let peaks: Vec> = tiles + .par_iter() + .map(|&(y0, y1, x0, x1)| { + let mut tys: Vec = Vec::new(); + let mut tpos: Vec = Vec::new(); + for k in 0..pos.len() { + let yf = ys[k] as f64; + if yf >= y0 && yf < y1 && pos[k] >= x0 && pos[k] < x1 { + tys.push(ys[k]); + tpos.push(pos[k]); + } + } + let diffs = lag_diffs(&tys, &tpos, MAX_LAG); + tile_peak(&diffs, s0) + }) + .collect(); + let inv: Vec = peaks.iter().flatten().map(|&s| 1.0 / s).collect(); + if inv.is_empty() { + return s0; + } + let mean_inv = inv.iter().sum::() / inv.len() as f64; + 1.0 / mean_inv +} + +pub struct RlDetection { + pub step_x: f64, + pub step_y: f64, + pub cols: i64, + pub rows: i64, + pub score_x: f64, + pub score_y: f64, + pub nruns_x: usize, + pub nruns_y: usize, + pub candidates: Vec<(f64, f64)>, +} + +struct AxisData { + s: Option, + v: f64, + cands: Vec<(f64, f64)>, + runs: Vec, + ys: Vec, + pos: Vec, +} + +fn axis_pass(img4: &[f32], rows: usize, cols: usize, s_grid: &[f64]) -> AxisData { + let (ys, pos) = boundaries(img4, rows, cols); + let runs = lag_diffs(&ys, &pos, MAX_LAG); + let picked = pick_step(&runs, s_grid); + let s = picked.s.map(|s| refine(&runs, s)); + AxisData { + s, + v: picked.v, + cands: picked.cands, + runs, + ys, + pos, + } +} + +pub fn detect(rgba: &[u8], w: usize, h: usize) -> RlDetection { + let img4 = prep(rgba, w, h); + let s_grid = arange(S_MIN, S_MAX, 0.01); + + let img4t = transpose4(&img4, w, h); + let (ax, ay) = rayon::join( + || axis_pass(&img4, h, w, &s_grid), + || axis_pass(&img4t, w, h, &s_grid), + ); + drop(img4t); + + let (mut sx, mut sy) = (ax.s, ay.s); + let (vx, vy) = (ax.v, ay.v); + if sx.is_none() && sy.is_none() { + return RlDetection { + step_x: 8.0, + step_y: 8.0, + cols: (w as f64 / 8.0).round_ties_even() as i64, + rows: (h as f64 / 8.0).round_ties_even() as i64, + score_x: vx, + score_y: vy, + nruns_x: ax.runs.len(), + nruns_y: ay.runs.len(), + candidates: Vec::new(), + }; + } + // cross-axis reconciliation: a weak axis borrows the strong axis's step + let borrow_x = match (sx, sy) { + (None, Some(_)) => true, + (Some(x), Some(y)) => vx < 0.5 * vy && (x - y).abs() > 0.15 * y, + _ => false, + }; + if borrow_x { + let syv = sy.unwrap(); + let sx2 = if !ax.runs.is_empty() { + refine(&ax.runs, syv) + } else { + syv + }; + if (sx2 - syv).abs() < 0.15 * syv { + sx = Some(sx2); + } + } + let borrow_y = match (sy, sx) { + (None, Some(_)) => true, + (Some(y), Some(x)) => vy < 0.5 * vx && (y - x).abs() > 0.15 * x, + _ => false, + }; + if borrow_y { + let sxv = sx.unwrap(); + let sy2 = if !ay.runs.is_empty() { + refine(&ay.runs, sxv) + } else { + sxv + }; + if (sy2 - sxv).abs() < 0.15 * sxv { + sy = Some(sy2); + } + } + let mut sx = sx.unwrap_or_else(|| sy.unwrap()); + let mut sy = sy.unwrap_or(sx); + + // local-step integration (drift-aware effective step) + let (sx2, sy2) = rayon::join( + || integrate_step(&ax.ys, &ax.pos, h, w, sx), + || integrate_step(&ay.ys, &ay.pos, w, h, sy), + ); + sx = sx2; + sy = sy2; + + // square-cell reconciliation: pool near-agreeing axes (harmonic mean) + let rel = (sx - sy).abs() / (0.5 * (sx + sy)); + if rel < 0.085 || (rel < 0.15 && vx.max(vy) < 0.15) { + let hm = 2.0 / (1.0 / sx + 1.0 / sy); + sx = hm; + sy = hm; + } + + let mut cands: Vec<(f64, f64)> = ax.cands.iter().chain(ay.cands.iter()).cloned().collect(); + cands.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + RlDetection { + step_x: sx, + step_y: sy, + cols: (w as f64 / sx).round_ties_even() as i64, + rows: (h as f64 / sy).round_ties_even() as i64, + score_x: vx, + score_y: vy, + nruns_x: ax.runs.len(), + nruns_y: ay.runs.len(), + candidates: cands, + } +} diff --git a/backend/native/pixel_grid_detector/src/selfsim.rs b/backend/native/pixel_grid_detector/src/selfsim.rs new file mode 100644 index 00000000..b2d70102 --- /dev/null +++ b/backend/native/pixel_grid_detector/src/selfsim.rs @@ -0,0 +1,735 @@ +//! Shift self-similarity grid detection. Mirrors detector/selfsim.py. +//! +//! The "quant" feature is weight-0 in the reference (ablated) and is not +//! ported. `_phase` is skipped too: no caller consumes selfsim's phase. + +use rayon::prelude::*; + +pub const TMAX: usize = 72; +pub const PMIN: f64 = 1.7; +pub const PMAX: f64 = 24.0; +pub const PSTEP: f64 = 0.02; +pub const KCAP: usize = 24; +pub const PIX_BUDGET: usize = 1_200_000; +pub const TILE_TARGET: f64 = 144.0; +pub const QUALIFY_FRAC: f64 = 0.40; +pub const QUALIFY_ABS: f64 = 4.0; +pub const VOTE_WIN: f64 = 0.15; +pub const VOTE_MIN: f64 = 1.5; +pub const DISP_UNIFORM: f64 = 0.015; + +pub struct Plane { + pub data: Vec, + pub w: usize, + pub h: usize, +} + +impl Plane { + fn at(&self, y: usize, x: usize) -> f32 { + self.data[y * self.w + x] + } +} + +fn luma(rgba: &[u8], w: usize, h: usize) -> Plane { + let mut data = vec![0f32; w * h]; + for i in 0..w * h { + let p = i * 4; + data[i] = rgba[p] as f32 * 0.299 + rgba[p + 1] as f32 * 0.587 + rgba[p + 2] as f32 * 0.114; + } + Plane { data, w, h } +} + +/// reflect101 index (OpenCV BORDER_DEFAULT) +fn refl(i: i64, n: i64) -> usize { + let mut i = i; + loop { + if i < 0 { + i = -i; + } else if i >= n { + i = 2 * n - 2 - i; + } else { + return i as usize; + } + } +} + +/// cv2.GaussianBlur(y, (0,0), 1.0): 9-tap separable Gaussian, reflect101. +fn gaussian_blur_1(src: &Plane) -> Plane { + let (w, h) = (src.w, src.h); + // OpenCV kernel: exp(-x^2/(2 sigma^2)) at x = i - 4, normalized (f64), + // then applied as f32 coefficients + let mut k64 = [0f64; 9]; + let mut sum = 0f64; + for i in 0..9 { + let x = i as f64 - 4.0; + k64[i] = (-0.5 * x * x).exp(); + sum += k64[i]; + } + let k: Vec = k64.iter().map(|&v| (v / sum) as f32).collect(); + + let mut tmp = vec![0f32; w * h]; + tmp.par_chunks_mut(w).enumerate().for_each(|(y, row)| { + for x in 0..w { + let mut acc = 0f32; + for t in 0..9 { + let xx = refl(x as i64 + t as i64 - 4, w as i64); + acc += k[t] * src.at(y, xx); + } + row[x] = acc; + } + }); + let mut out = vec![0f32; w * h]; + out.par_chunks_mut(w).enumerate().for_each(|(y, row)| { + for x in 0..w { + let mut acc = 0f32; + for t in 0..9 { + let yy = refl(y as i64 + t as i64 - 4, h as i64); + acc += k[t] * tmp[yy * w + x]; + } + row[x] = acc; + } + }); + Plane { data: out, w, h } +} + +/// |cv2.Laplacian(x, CV_32F)| (3x3 aperture, reflect101). +fn abs_laplacian(src: &Plane) -> Plane { + let (w, h) = (src.w, src.h); + let mut out = vec![0f32; w * h]; + out.par_chunks_mut(w).enumerate().for_each(|(y, row)| { + for x in 0..w { + let up = src.at(refl(y as i64 - 1, h as i64), x); + let dn = src.at(refl(y as i64 + 1, h as i64), x); + let lf = src.at(y, refl(x as i64 - 1, w as i64)); + let rt = src.at(y, refl(x as i64 + 1, w as i64)); + let c = src.at(y, x); + row[x] = (up + dn + lf + rt - 4.0 * c).abs(); + } + }); + Plane { data: out, w, h } +} + +/// |d/dx| + |d/dy| with prepend-first semantics (first row/col diff = 0). +fn gradmag(y: &Plane) -> Plane { + let (w, h) = (y.w, y.h); + let mut out = vec![0f32; w * h]; + for r in 0..h { + for c in 0..w { + let gx = if c == 0 { + 0.0 + } else { + (y.at(r, c) - y.at(r, c - 1)).abs() + }; + let gy = if r == 0 { + 0.0 + } else { + (y.at(r, c) - y.at(r - 1, c)).abs() + }; + out[r * w + c] = gx + gy; + } + } + Plane { data: out, w, h } +} + +pub struct Features { + pub grad0: Plane, + pub gradb: Plane, + pub lapb: Plane, +} + +pub fn build_features(rgba: &[u8], w: usize, h: usize) -> (Features, Vec) { + let y = luma(rgba, w, h); + let yb = gaussian_blur_1(&y); + let grad0 = gradmag(&y); + let gradb = gradmag(&yb); + let lapb = abs_laplacian(&yb); + let mut wt = vec![0f32; w * h]; + for i in 0..w * h { + wt[i] = (rgba[i * 4 + 3] as f32 / 255.0).max(0.05); + } + (Features { grad0, gradb, lapb }, wt) +} + +fn transpose_plane(p: &Plane) -> Plane { + let mut data = vec![0f32; p.w * p.h]; + for y in 0..p.h { + for x in 0..p.w { + data[x * p.h + y] = p.data[y * p.w + x]; + } + } + Plane { + data, + w: p.h, + h: p.w, + } +} + +/// np.unique(np.linspace(0, n, parts+1)[:-1].astype(int)) +fn tile_starts(n: usize, parts: usize) -> Vec { + let step = n as f64 / parts as f64; + let mut v: Vec = (0..parts).map(|i| (i as f64 * step) as usize).collect(); + v.dedup(); + v +} + +/// Per-tile numerator/denominator d(t) curves for one (pre-oriented) +/// single-channel feature. Returns (num, den) shaped [n_tiles_y][n_tiles_x][T]. +#[allow(clippy::type_complexity)] +fn dcurves_tiled( + arr0: &Plane, + wt0: &Plane, + tmax: usize, + ny: usize, + nx: usize, +) -> (Vec>>, Vec>>, usize) { + let (full_h, w) = (arr0.h, arr0.w); + let stride = std::cmp::max(1, (full_h * w + PIX_BUDGET - 1) / PIX_BUDGET); + let rows_idx: Vec = (0..full_h).step_by(stride).collect(); + let h = rows_idx.len(); + let t_cap = std::cmp::max(std::cmp::min(tmax, w / 3), 1); + + let rs = tile_starts(h, ny); + let cs0 = tile_starts(w, nx); + let (ny, nx) = (rs.len(), cs0.len()); + + let mut num = vec![vec![vec![0f64; t_cap]; nx]; ny]; + let mut den = vec![vec![vec![0f64; t_cap]; nx]; ny]; + + // per-t work is independent; parallelize over t and merge + let results: Vec<(usize, Vec>, Vec>, bool)> = (1..=t_cap) + .into_par_iter() + .map(|t| { + // column boundaries clamped like the reference + let clamp_at = if w >= t + 1 { w - t - 1 } else { 0 }; + let cs: Vec = cs0.iter().map(|&c| c.min(clamp_at)).collect(); + let mut cs_u = cs.clone(); + cs_u.dedup(); + let degenerate = cs_u.len() != nx; + let use_cs: Vec = if degenerate { vec![0] } else { cs }; + let ncols = use_cs.len(); + let ext = w - t; + + // two-stage sums like the reference reduceat chain: rows first + // (f32-rounded per column), then column segments (f32-rounded) + let mut nu = vec![vec![0f64; ncols]; ny]; + let mut de = vec![vec![0f64; ncols]; ny]; + let mut coln = vec![0f64; ext]; + let mut cold = vec![0f64; ext]; + for (ri, &r0) in rs.iter().enumerate() { + let r1 = if ri + 1 < ny { rs[ri + 1] } else { h }; + coln.iter_mut().for_each(|v| *v = 0.0); + cold.iter_mut().for_each(|v| *v = 0.0); + for rr in r0..r1 { + let y = rows_idx[rr]; + let arow = &arr0.data[y * w..y * w + w]; + let wrow = &wt0.data[y * w..y * w + w]; + for x in 0..ext { + let diff = (arow[x + t] - arow[x]).abs(); + let wp = wrow[x + t] * wrow[x]; + coln[x] += (diff * wp) as f64; + cold[x] += wp as f64; + } + } + for (ci, &c0) in use_cs.iter().enumerate() { + let c1 = if ci + 1 < ncols { use_cs[ci + 1] } else { ext }; + let mut anum = 0f64; + let mut aden = 0f64; + for x in c0..c1.max(c0) { + anum += coln[x] as f32 as f64; + aden += cold[x] as f32 as f64; + } + nu[ri][ci] = anum as f32 as f64; + de[ri][ci] = aden as f32 as f64; + } + } + (t, nu, de, degenerate) + }) + .collect(); + + for (t, nu, de, degenerate) in results { + if degenerate && nx > 1 { + let nsum: f64 = nu.iter().flatten().sum(); + let dsum: f64 = de.iter().flatten().sum(); + for a in 0..ny { + for b in 0..nx { + num[a][b][t - 1] += nsum / (ny * nx) as f64; + den[a][b][t - 1] += dsum / (ny * nx) as f64; + } + } + } else { + for a in 0..ny { + for b in 0..nx.min(nu[a].len()) { + num[a][b][t - 1] = nu[a][b]; + den[a][b][t - 1] = de[a][b]; + } + } + } + } + (num, den, t_cap) +} + +/// Regroup tile num/den grids into gy x gx summed d(t) curves. +fn group(num: &[Vec>], den: &[Vec>], gy: usize, gx: usize) -> Vec> { + let ny = num.len(); + let nx = num[0].len(); + let t_len = num[0][0].len(); + let ys: Vec = (0..=gy) + .map(|i| (ny as f64 * i as f64 / gy as f64) as usize) + .collect(); + let xs: Vec = (0..=gx) + .map(|i| (nx as f64 * i as f64 / gx as f64) as usize) + .collect(); + let mut out: Vec> = Vec::with_capacity(gy * gx); + for a in 0..gy { + for b in 0..gx { + let mut n = vec![0f64; t_len]; + let mut d = vec![0f64; t_len]; + for yy in ys[a]..ys[a + 1] { + for xx in xs[b]..xs[b + 1] { + for t in 0..t_len { + n[t] += num[yy][xx][t]; + d[t] += den[yy][xx][t]; + } + } + } + for t in 0..t_len { + n[t] /= d[t].max(1e-9); + } + out.push(n); + } + } + out +} + +fn interp1(xs_start: f64, dm: &[f64], q: f64) -> f64 { + // np.interp over ts = 1..T with clamping + let t_len = dm.len(); + let pos = q - xs_start; + if pos <= 0.0 { + return dm[0]; + } + if pos >= (t_len - 1) as f64 { + return dm[t_len - 1]; + } + let i = pos.floor() as usize; + let f = pos - i as f64; + dm[i] * (1.0 - f) + dm[i + 1] * f +} + +/// t-statistic comb score for one normalized curve dm(t), t = 1..T. +fn comb_tstat(dm: &[f64], p_grid: &[f64], kcap: usize) -> Vec { + let t_len = dm.len(); + let mut s_out = vec![-1e9; p_grid.len()]; + if t_len < 8 { + return s_out; + } + // trapezoid cumulative integral F over ts = 1..T + let mut cum = vec![0f64; t_len]; + for i in 1..t_len { + cum[i] = cum[i - 1] + 0.5 * (dm[i] + dm[i - 1]); + } + let mut d2: Vec = (2..t_len) + .map(|i| (dm[i] - 2.0 * dm[i - 1] + dm[i - 2]).abs()) + .collect(); + let noise = if d2.is_empty() { + 1e-9 + } else { + d2.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let n = d2.len(); + (if n % 2 == 1 { + d2[n / 2] + } else { + 0.5 * (d2[n / 2 - 1] + d2[n / 2]) + }) + 1e-9 + }; + let boxm = |q: f64, p: f64| -> f64 { + let lo = (q - p / 2.0).max(1.0); + let hi = (q + p / 2.0).min(t_len as f64); + (interp1(1.0, &cum, hi) - interp1(1.0, &cum, lo)) / (hi - lo).max(1e-9) + }; + for (pi, &p) in p_grid.iter().enumerate() { + let kf = (t_len as f64 / p - 0.5).floor() as i64; + let k = std::cmp::min(kf.max(0) as usize, kcap); + if k < 2 { + continue; + } + let mut c = vec![0f64; k]; + for kk in 1..=k { + let q = p * kk as f64; + let rm = interp1(1.0, dm, q) - boxm(q, p); + let rh1 = interp1(1.0, dm, q - p / 2.0) - boxm(q - p / 2.0, p); + let rh2 = interp1(1.0, dm, q + p / 2.0) - boxm(q + p / 2.0, p); + c[kk - 1] = 0.5 * (rh1 + rh2) - rm; + } + let mean_c = c.iter().sum::() / k as f64; + let var = c.iter().map(|&v| (v - mean_c) * (v - mean_c)).sum::() / k as f64; + let std_c = var.sqrt(); + let kpen = (1.0f64).min((k as f64 - 1.0) / 3.0); + s_out[pi] = kpen * mean_c * (k as f64).sqrt() / (std_c + 0.5 * noise + 1e-9); + } + s_out +} + +/// local maxima of z (>= left, > right), sorted by z descending +fn local_maxima(z: &[f64]) -> Vec { + let mut idx: Vec = Vec::new(); + for i in 1..z.len().saturating_sub(1) { + if z[i] >= z[i - 1] && z[i] > z[i + 1] { + idx.push(i); + } + } + idx.sort_by(|&a, &b| z[b].partial_cmp(&z[a]).unwrap()); + idx +} + +fn parabolic(x0: f64, dx: f64, y: &[f64], i: usize) -> f64 { + if i > 0 && i < y.len() - 1 { + let denom = y[i - 1] - 2.0 * y[i] + y[i + 1]; + if denom.abs() > 1e-12 { + let off = (0.5 * (y[i - 1] - y[i + 1]) / denom).clamp(-1.0, 1.0); + return x0 + i as f64 * dx + off * dx; + } + } + x0 + i as f64 * dx +} + +/// Sub-pixel step from minima of R(t) near k*s0, weighted LS fit. +fn refine_step(r: &[f64], s0: f64) -> f64 { + let t_len = r.len(); + let kmax = std::cmp::min(KCAP, ((t_len as f64 - 1.0) / s0) as usize); + let mut pairs: Vec<(f64, f64, f64)> = Vec::new(); + for k in 1..=kmax { + let t0 = k as f64 * s0; + let half = (1.5f64).max(0.3 * s0); + let lo = std::cmp::max((t0 - half).floor() as i64, 1) as usize; + let hi = std::cmp::min((t0 + half).ceil() as usize, t_len); + if hi < lo + 2 { + continue; + } + let seg = &r[lo - 1..hi]; + let mut j = 0; + for jj in 0..seg.len() { + if seg[jj] < seg[j] { + j = jj; + } + } + let tk = parabolic(lo as f64, 1.0, seg, j); + pairs.push((k as f64, tk, 0.85f64.powi(k as i32 - 1))); + } + if pairs.is_empty() { + return s0; + } + let mut s = s0; + for _ in 0..2 { + let num: f64 = pairs.iter().map(|&(k, t, w)| w * k * t).sum(); + let den: f64 = pairs.iter().map(|&(k, _, w)| w * k * k).sum(); + s = num / den; + let tol = (0.2 * s0).max(1.0); + let keep: Vec<(f64, f64, f64)> = pairs + .iter() + .cloned() + .filter(|&(k, t, _)| (t - k * s).abs() <= tol) + .collect(); + if keep.len() == pairs.len() || keep.is_empty() { + break; + } + pairs = keep; + } + if 0.6 * s0 < s && s < 1.4 * s0 { + s + } else { + s0 + } +} + +/// Mean-normalized, box(9)-detrended residual (edge-padded convolution). +fn residual_curve(d: &[f64]) -> Vec { + let n = d.len(); + let mean = d.iter().sum::() / n.max(1) as f64; + if mean < 1e-9 { + return vec![0.0; n]; + } + let dm: Vec = d.iter().map(|&v| v / mean).collect(); + let mut out = vec![0f64; n]; + for i in 0..n { + let mut acc = 0f64; + for k in -4i64..=4 { + let j = (i as i64 + k).clamp(0, n as i64 - 1) as usize; + acc += dm[j]; + } + out[i] = dm[i] - acc / 9.0; + } + out +} + +fn score_curves(curves: &[Vec], p_grid: &[f64], fw: f64, kcap: usize) -> Vec> { + curves + .par_iter() + .map(|d| { + let m = d.iter().sum::() / d.len().max(1) as f64; + if m > 1e-9 { + let dn: Vec = d.iter().map(|&v| v / m).collect(); + comb_tstat(&dn, p_grid, kcap) + .iter() + .map(|&v| fw * v.clamp(-10.0, 30.0)) + .collect() + } else { + vec![0.0; p_grid.len()] + } + }) + .collect() +} + +fn arange(start: f64, stop: f64, step: f64) -> Vec { + let n = ((stop - start) / step).ceil().max(0.0) as usize; + (0..n).map(|i| start + i as f64 * step).collect() +} + +/// python round() = banker's rounding +fn pyround(v: f64) -> f64 { + v.round_ties_even() +} + +pub struct SsAxis { + pub s: f64, + pub conf: f64, +} + +fn detect_axis(feats: [(&Plane, f64); 3], wt: &Plane, size: usize) -> SsAxis { + let p_grid = arange(PMIN, PMAX.min(size as f64 / 4.0), PSTEP); + let np_ = p_grid.len(); + // wt here is pre-oriented: shift axis horizontal + let (h, w) = (wt.h, wt.w); + let (other, shift) = (h, w); + let ny = (pyround(other as f64 / 128.0)).clamp(1.0, 8.0) as usize; + let nx = (pyround(shift as f64 / TILE_TARGET)).clamp(1.0, 6.0) as usize; + let gy = std::cmp::min(ny, 4); + + let mut s_fine: Vec> = Vec::new(); + let mut s_coarse: Vec> = Vec::new(); + let mut s_glob = vec![0f64; np_]; + let mut agg_res: Vec = Vec::new(); + let mut w_fine: Vec = Vec::new(); + let mut w_coarse: Vec = Vec::new(); + let mut first = true; + + for (fi, &(plane, fw)) in feats.iter().enumerate() { + let (num, den, _t) = dcurves_tiled(plane, wt, TMAX, ny, nx); + let (tny, tnx) = (num.len(), num[0].len()); + let d_fine = group(&num, &den, tny, tnx); + let d_coarse = group(&num, &den, gy, 1); + let d_glob = group(&num, &den, 1, 1); + if first { + s_fine = vec![vec![0f64; np_]; d_fine.len()]; + s_coarse = vec![vec![0f64; np_]; d_coarse.len()]; + agg_res = vec![0f64; d_glob[0].len()]; + first = false; + } + let sf = score_curves(&d_fine, &p_grid, fw, 6); + for i in 0..d_fine.len() { + for j in 0..np_ { + s_fine[i][j] += sf[i][j]; + } + } + let sc = score_curves(&d_coarse, &p_grid, fw, KCAP); + for i in 0..d_coarse.len() { + for j in 0..np_ { + s_coarse[i][j] += sc[i][j]; + } + } + let sg = score_curves(&d_glob, &p_grid, fw, KCAP); + for j in 0..np_ { + s_glob[j] += sg[0][j]; + } + let res = residual_curve(&d_glob[0]); + for j in 0..agg_res.len() { + agg_res[j] += fw * res[j]; + } + if fi == 0 { + // grad0 tile weights + w_fine = d_fine + .iter() + .map(|c| c.iter().sum::() / c.len().max(1) as f64) + .collect(); + w_coarse = d_coarse + .iter() + .map(|c| c.iter().sum::() / c.len().max(1) as f64) + .collect(); + } + } + let wc_sum: f64 = w_coarse.iter().sum(); + if s_fine.is_empty() || wc_sum <= 1e-9 { + return SsAxis { s: 8.0, conf: 0.0 }; + } + let mut stot = vec![0f64; np_]; + for j in 0..np_ { + let mut acc = 0f64; + for i in 0..s_coarse.len() { + acc += (w_coarse[i] / wc_sum) * s_coarse[i][j]; + } + stot[j] = (acc + 0.5 * s_glob[j]) / 1.5; + } + let peaks = local_maxima(&stot); + if peaks.is_empty() || stot[peaks[0]] <= 0.0 { + return SsAxis { s: 8.0, conf: 0.0 }; + } + let smax = stot[peaks[0]]; + let floor = (QUALIFY_FRAC * smax).max(QUALIFY_ABS); + let qual: Vec = peaks + .iter() + .cloned() + .filter(|&i| stot[i] >= floor) + .collect(); + let mut best_i = if qual.is_empty() { + peaks[0] + } else { + *qual + .iter() + .min_by(|&&a, &&b| p_grid[a].partial_cmp(&p_grid[b]).unwrap()) + .unwrap() + }; + // explicit divisor walk + let mut changed = true; + while changed { + changed = false; + for m in [6f64, 5.0, 4.0, 3.0, 2.0] { + let pf = p_grid[best_i] / m; + if pf < p_grid[0] { + continue; + } + let j0 = pyround((pf - p_grid[0]) / PSTEP) as i64; + let rad = std::cmp::max(pyround(0.05 * pf / PSTEP) as i64, 4); + let lo = std::cmp::max(j0 - rad, 0) as usize; + let hi = std::cmp::min((j0 + rad + 1) as usize, np_); + if hi > lo { + let mut j = lo; + for jj in lo..hi { + if stot[jj] > stot[j] { + j = jj; + } + } + if stot[j] >= (0.45 * stot[best_i]).max(0.8 * QUALIFY_ABS) { + best_i = j; + changed = true; + break; + } + } + } + } + let p_star = p_grid[best_i]; + + // per-fine-tile votes around a center + let tile_votes = |center: f64| -> (Vec, Vec) { + let win = (VOTE_WIN * center).max(6.0 * PSTEP); + let lo = std::cmp::max(pyround((center - win - p_grid[0]) / PSTEP) as i64, 0) as usize; + let hi = std::cmp::min( + pyround((center + win - p_grid[0]) / PSTEP) as i64 + 1, + np_ as i64, + ) as usize; + let mut vs = Vec::new(); + let mut ws = Vec::new(); + for i in 0..s_fine.len() { + if w_fine[i] <= 1e-9 || hi <= lo || hi - lo < 3 { + continue; + } + let seg = &s_fine[i][lo..hi]; + let mut j = 0; + for jj in 0..seg.len() { + if seg[jj] > seg[j] { + j = jj; + } + } + if seg[j] < VOTE_MIN { + continue; + } + vs.push(parabolic(p_grid[lo], PSTEP, seg, j)); + ws.push(seg[j].min(8.0)); + } + (vs, ws) + }; + let wmedian = |v: &[f64], w: &[f64]| -> f64 { + let mut order: Vec = (0..v.len()).collect(); + order.sort_by(|&a, &b| v[a].partial_cmp(&v[b]).unwrap()); + let total: f64 = w.iter().sum(); + let mut cw = 0f64; + for &o in &order { + cw += w[o]; + if cw >= 0.5 * total { + return v[o]; + } + } + v[order[order.len() - 1]] + }; + + let (mut votes, mut vw) = tile_votes(p_star); + if !votes.is_empty() { + let med = wmedian(&votes, &vw); + let re = tile_votes(med); + votes = re.0; + vw = re.1; + } + let s0 = if !votes.is_empty() { + let med = wmedian(&votes, &vw); + let devs: Vec = votes.iter().map(|&v| (v - med).abs()).collect(); + let disp = wmedian(&devs, &vw) / med.max(1e-9); + if disp < DISP_UNIFORM { + let mut s0 = med; + let s_ls = refine_step(&agg_res, s0); + if (s_ls - s0).abs() <= (0.03 * s0).max(0.04) { + s0 = s_ls; + } + s0 + } else { + let wsum: f64 = vw.iter().sum(); + let hsum: f64 = vw.iter().zip(votes.iter()).map(|(&w, &v)| w / v).sum(); + wsum / hsum + } + } else { + let mut s0 = parabolic(p_grid[0], PSTEP, &stot, best_i); + let s_ls = refine_step(&agg_res, s0); + if (s_ls - s0).abs() <= (0.03 * s0).max(0.04) { + s0 = s_ls; + } + s0 + }; + SsAxis { + s: s0, + conf: stot[best_i], + } +} + +pub struct SsDetection { + pub step_x: f64, + pub step_y: f64, + pub cols: i64, + pub rows: i64, + pub conf: f64, +} + +pub fn detect(rgba: &[u8], w: usize, h: usize) -> SsDetection { + let (feats, wt) = build_features(rgba, w, h); + let wt_plane = Plane { data: wt, w, h }; + // pre-orient the y-axis pass (shift axis horizontal) + let g0t = transpose_plane(&feats.grad0); + let gbt = transpose_plane(&feats.gradb); + let lbt = transpose_plane(&feats.lapb); + let wtt = transpose_plane(&wt_plane); + + let (ax, ay) = rayon::join( + || { + detect_axis( + [(&feats.grad0, 1.0), (&feats.gradb, 1.0), (&feats.lapb, 1.0)], + &wt_plane, + w, + ) + }, + || detect_axis([(&g0t, 1.0), (&gbt, 1.0), (&lbt, 1.0)], &wtt, h), + ); + SsDetection { + step_x: ax.s, + step_y: ay.s, + cols: std::cmp::max(1, pyround(w as f64 / ax.s) as i64), + rows: std::cmp::max(1, pyround(h as f64 / ay.s) as i64), + conf: ax.conf.min(ay.conf), + } +} diff --git a/backend/native/pixel_grid_detector/src/sigproc.rs b/backend/native/pixel_grid_detector/src/sigproc.rs new file mode 100644 index 00000000..87d5f2ef --- /dev/null +++ b/backend/native/pixel_grid_detector/src/sigproc.rs @@ -0,0 +1,186 @@ +//! Signal-processing helpers mirroring the scipy pieces the detector uses: +//! percentile, profile normalisation, gaussian_filter1d, find_peaks +//! (height + distance), 1-D median filter (mode="nearest"). + +/// numpy linear-interpolation percentile (q in [0,100]). +pub fn percentile(values: &[f64], q: f64) -> f64 { + if values.is_empty() { + return 0.0; + } + let mut v = values.to_vec(); + v.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let pos = q / 100.0 * (v.len() as f64 - 1.0); + let lo = pos.floor() as usize; + let hi = pos.ceil() as usize; + let f = pos - lo as f64; + v[lo] + (v[hi] - v[lo]) * f +} + +/// channels.py _normalise: clip(profile / (p95_interior + 1e-9), 0, 1.5) +pub fn normalise(profile: &[f64]) -> Vec { + let n = profile.len(); + if n <= 2 { + return profile.to_vec(); + } + let interior = &profile[1..n - 1]; + let mut scale = percentile(interior, 95.0); + if scale <= 0.0 { + let m = interior.iter().cloned().fold(f64::MIN, f64::max); + scale = if m > 0.0 { m } else { 1.0 }; + } + profile + .iter() + .map(|&v| (v / (scale + 1e-9)).clamp(0.0, 1.5)) + .collect() +} + +/// scipy gaussian_filter1d(sigma, truncate=4.0, mode="reflect"). +pub fn gaussian_filter1d(x: &[f64], sigma: f64) -> Vec { + let radius = (4.0 * sigma + 0.5) as i64; + let mut kern: Vec = (-radius..=radius) + .map(|i| (-0.5 * (i as f64 / sigma).powi(2)).exp()) + .collect(); + let s: f64 = kern.iter().sum(); + for k in kern.iter_mut() { + *k /= s; + } + let n = x.len() as i64; + let refl = |mut i: i64| -> usize { + // scipy "reflect": (d c b a | a b c d | d c b a) + loop { + if i < 0 { + i = -i - 1; + } else if i >= n { + i = 2 * n - 1 - i; + } else { + return i as usize; + } + } + }; + (0..n) + .map(|i| { + let mut acc = 0f64; + for (j, &kv) in kern.iter().enumerate() { + // scipy correlate1d applies weights reversed relative to + // convolution; the gaussian kernel is symmetric so it + // doesn't matter + acc += kv * x[refl(i + j as i64 - radius)]; + } + acc + }) + .collect() +} + +/// scipy find_peaks with `height` and `distance`. Returns (positions, +/// heights). Plateau peaks resolve to the plateau midpoint like scipy. +pub fn find_peaks(x: &[f64], height: f64, distance: f64) -> (Vec, Vec) { + let n = x.len(); + let mut peaks: Vec = Vec::new(); + let mut i = 1usize; + while n >= 3 && i < n - 1 { + if x[i - 1] < x[i] { + let mut ahead = i + 1; + while ahead < n - 1 && x[ahead] == x[i] { + ahead += 1; + } + if x[ahead] < x[i] { + let left = i; + let right = ahead - 1; + peaks.push((left + right) / 2); + i = ahead; + continue; + } + } + i += 1; + } + // height filter first (scipy order) + let mut kept: Vec = peaks.into_iter().filter(|&p| x[p] >= height).collect(); + // distance filter: highest priority first, remove neighbours closer + // than ceil(distance) + if distance > 1.0 && kept.len() > 1 { + let dmin = distance.ceil(); + let m = kept.len(); + let mut keep = vec![true; m]; + let mut priority: Vec = (0..m).collect(); + priority.sort_by(|&a, &b| x[kept[a]].partial_cmp(&x[kept[b]]).unwrap()); + for pi in (0..m).rev() { + let j = priority[pi]; + if !keep[j] { + continue; + } + let mut k = j as i64 - 1; + while k >= 0 && (kept[j] - kept[k as usize]) < dmin as usize { + keep[k as usize] = false; + k -= 1; + } + let mut k = j + 1; + while k < m && (kept[k] - kept[j]) < dmin as usize { + keep[k] = false; + k += 1; + } + } + kept = kept + .into_iter() + .zip(keep) + .filter(|&(_, k)| k) + .map(|(p, _)| p) + .collect(); + } + let heights: Vec = kept.iter().map(|&p| x[p]).collect(); + (kept, heights) +} + +/// scipy.ndimage.median_filter 1-D, mode="nearest", odd size. +pub fn median_filter1d(x: &[f64], size: usize) -> Vec { + let n = x.len(); + let half = size as i64 / 2; + let mut buf = vec![0f64; size]; + (0..n as i64) + .map(|i| { + for (bi, k) in (-half..=half).enumerate() { + let j = (i + k).clamp(0, n as i64 - 1) as usize; + buf[bi] = x[j]; + } + buf.sort_by(|a, b| a.partial_cmp(b).unwrap()); + buf[size / 2] + }) + .collect() +} + +/// np.median +pub fn median(values: &[f64]) -> f64 { + if values.is_empty() { + return 0.0; + } + let mut v = values.to_vec(); + v.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let n = v.len(); + if n % 2 == 1 { + v[n / 2] + } else { + 0.5 * (v[n / 2 - 1] + v[n / 2]) + } +} + +/// np.interp with clamped ends (xp ascending). +pub fn interp(x: f64, xp: &[f64], fp: &[f64]) -> f64 { + let n = xp.len(); + if x <= xp[0] { + return fp[0]; + } + if x >= xp[n - 1] { + return fp[n - 1]; + } + let mut lo = 0usize; + let mut hi = n - 1; + while hi - lo > 1 { + let mid = (lo + hi) / 2; + if xp[mid] <= x { + lo = mid; + } else { + hi = mid; + } + } + let f = (x - xp[lo]) / (xp[hi] - xp[lo]); + fp[lo] * (1.0 - f) + fp[hi] * f +} diff --git a/backend/native/pixel_grid_detector/src/varcontrast.rs b/backend/native/pixel_grid_detector/src/varcontrast.rs new file mode 100644 index 00000000..fa82a136 --- /dev/null +++ b/backend/native/pixel_grid_detector/src/varcontrast.rs @@ -0,0 +1,231 @@ +//! CellVarContrast "square packer" channel (detector/varcontrast.py), +//! trimmed to what core.py's arbitration reads: the detrended z curve +//! (scored_curve) exposed via z_of(step). + +use crate::sigproc::{median, median_filter1d}; + +fn pyround(v: f64) -> f64 { + v.round_ties_even() +} + +pub struct CellVarContrast { + w: usize, + h: usize, + c: usize, + scflat: Vec, // (h+1)*(w+1) x (C+1) fused SAT: channels + sqsum + total_var: f64, + active_var: f64, + px: Vec, + py: Vec, +} + +impl CellVarContrast { + pub fn new(rgba: &[u8], w: usize, h: usize) -> CellVarContrast { + let c = 4usize; + // premultiplied f64 image with alpha channel + let stride = w + 1; + let mut scflat = vec![0f64; (h + 1) * stride * (c + 1)]; + // build cumulative sums row by row + { + let idx = |y: usize, x: usize, ch: usize| (y * stride + x) * (c + 1) + ch; + for y in 0..h { + let mut row = vec![0f64; c + 1]; + for x in 0..w { + let p = (y * w + x) * 4; + let a = rgba[p + 3] as f64 / 255.0; + let vals = [ + rgba[p] as f64 * a, + rgba[p + 1] as f64 * a, + rgba[p + 2] as f64 * a, + rgba[p + 3] as f64, + ]; + let sq: f64 = vals.iter().map(|&v| v * v).sum(); + for ch in 0..c { + row[ch] += vals[ch]; + } + row[c] += sq; + for ch in 0..=c { + scflat[idx(y + 1, x + 1, ch)] = scflat[idx(y, x + 1, ch)] + row[ch]; + } + } + } + } + let n = (h * w) as f64; + let idx = |y: usize, x: usize, ch: usize| (y * stride + x) * (c + 1) + ch; + let mut mean_sq = 0f64; + for ch in 0..c { + let m = scflat[idx(h, w, ch)] / n; + mean_sq += m * m; + } + let total_var = scflat[idx(h, w, c)] / n - mean_sq; + + // activity map: variance of 8x8 blocks + let mut bs = 8usize; + let mut by: Vec = (0..h.saturating_sub(bs - 1)).step_by(bs).collect(); + let mut bx: Vec = (0..w.saturating_sub(bs - 1)).step_by(bs).collect(); + if by.is_empty() || bx.is_empty() { + by = vec![0]; + bx = vec![0]; + bs = h.min(w); + } + let area = (bs * bs) as f64; + let thresh = (0.02 * total_var).max(1e-6); + let mut act_px: Vec = Vec::new(); + let mut act_py: Vec = Vec::new(); + let mut all_px: Vec = Vec::new(); + let mut all_py: Vec = Vec::new(); + let mut active_sum = 0f64; + let mut active_cnt = 0usize; + for &y0 in &by { + for &x0 in &bx { + let rect = |ch: usize| { + scflat[idx(y0 + bs, x0 + bs, ch)] + - scflat[idx(y0, x0 + bs, ch)] + - scflat[idx(y0 + bs, x0, ch)] + + scflat[idx(y0, x0, ch)] + }; + let mut msq = 0f64; + for ch in 0..c { + let m = rect(ch) / area; + msq += m * m; + } + let bvar = rect(c) / area - msq; + let cx = x0 as f64 + bs as f64 / 2.0; + let cy = y0 as f64 + bs as f64 / 2.0; + all_px.push(cx); + all_py.push(cy); + if bvar > thresh { + act_px.push(cx); + act_py.push(cy); + active_sum += bvar; + active_cnt += 1; + } + } + } + let (mut px, mut py) = if act_px.len() < 8 { + (all_px, all_py) + } else { + (act_px, act_py) + }; + let max_points = 2600usize; + if px.len() > max_points { + let m = px.len(); + let sel: Vec = (0..max_points) + .map(|i| (i as f64 * (m as f64 - 1.0) / (max_points as f64 - 1.0)) as usize) + .collect(); + let mut pairs: Vec<(f64, f64)> = sel.iter().map(|&i| (px[i], py[i])).collect(); + pairs.sort_by(|a, b| { + (a.1 * w as f64 + a.0) + .partial_cmp(&(b.1 * w as f64 + b.0)) + .unwrap() + }); + px = pairs.iter().map(|p| p.0).collect(); + py = pairs.iter().map(|p| p.1).collect(); + } + let active_var = if active_cnt > 0 { + active_sum / active_cnt as f64 + } else { + total_var + }; + CellVarContrast { + w, + h, + c, + scflat, + total_var, + active_var, + px, + py, + } + } + + /// Mean within-cell variance over active points (integer-snapped + /// corners, fused-SAT gathers). Mirrors _cells_variance + .mean(). + fn cells_variance_mean(&self, step: f64, phase_x: f64, phase_y: f64) -> f64 { + let stride = self.w + 1; + let cw = self.c + 1; + let mut acc = 0f64; + for i in 0..self.px.len() { + let x0 = phase_x + ((self.px[i] - phase_x) / step).floor() * step; + let y0 = phase_y + ((self.py[i] - phase_y) / step).floor() * step; + let ix0 = pyround(x0).clamp(0.0, self.w as f64 - 1.0) as usize; + let iy0 = pyround(y0).clamp(0.0, self.h as f64 - 1.0) as usize; + let ix1 = pyround(x0 + step).clamp(ix0 as f64 + 1.0, self.w as f64) as usize; + let iy1 = pyround(y0 + step).clamp(iy0 as f64 + 1.0, self.h as f64) as usize; + let g = |y: usize, x: usize, ch: usize| self.scflat[(y * stride + x) * cw + ch]; + let area = ((ix1 - ix0) * (iy1 - iy0)) as f64; + let mut msq = 0f64; + for ch in 0..self.c { + let s1 = g(iy1, ix1, ch) - g(iy1, ix0, ch) - g(iy0, ix1, ch) + g(iy0, ix0, ch); + let m = s1 / area; + msq += m * m; + } + let s2 = g(iy1, ix1, self.c) - g(iy1, ix0, self.c) - g(iy0, ix1, self.c) + + g(iy0, ix0, self.c); + acc += (s2 / area - msq).max(0.0); + } + acc / self.px.len().max(1) as f64 + } + + /// Global-phase contrast (varcontrast.contrast, n_phases=3). + fn contrast(&self, step: f64, n_phases: usize) -> f64 { + if step < 1.5 + || step > self.w as f64 / 3.0 + || step > self.h as f64 / 3.0 + || self.total_var <= 1e-9 + || self.px.is_empty() + { + return 0.0; + } + let mut best = f64::INFINITY; + let mut worst = f64::MIN; + for iy in 0..n_phases { + let py = iy as f64 * (step / n_phases as f64); + for ix in 0..n_phases { + let px = ix as f64 * (step / n_phases as f64); + let v = self.cells_variance_mean(step, px, py); + if v < best { + best = v; + } + if v > worst { + worst = v; + } + } + } + (worst - best) / (best + 0.05 * self.active_var) + } + + /// (log-steps, z) detrended prominence curve (scored_curve). + pub fn z_curve(&self) -> (Vec, Vec) { + use rayon::prelude::*; + let l = self.w.min(self.h) as f64; + let max_step = (l / 8.0).max(4.0).min(64.0); + let mut steps = Vec::new(); + let mut s = 2.0f64; + while s <= max_step { + steps.push(s); + s *= 1.04; + } + let cs: Vec = steps.par_iter().map(|&s| self.contrast(s, 3)).collect(); + let base = median_filter1d(&cs, 15); + let resid: Vec = cs.iter().zip(base.iter()).map(|(&c, &b)| c - b).collect(); + let absr: Vec = resid.iter().map(|&r| r.abs()).collect(); + let sigma = median(&absr) * 1.4826 + 1e-9; + let z: Vec = resid.iter().map(|&r| r / sigma).collect(); + let logs: Vec = steps.iter().map(|&v| v.ln()).collect(); + // store step range endpoints inside logs for the z_of gate + (logs, z) + } +} + +/// z_of(step) closure data: (log_steps, z) from z_curve; gate outside range. +pub fn vc_z_of(logs: &[f64], z: &[f64], step: f64) -> f64 { + if logs.is_empty() { + return 0.0; + } + let ls = step.ln(); + if ls < logs[0] || ls > logs[logs.len() - 1] { + return 0.0; + } + crate::sigproc::interp(ls, logs, z) +} From 4fcd162c5debd24d718b20da18c9b8559d50b25a Mon Sep 17 00:00:00 2001 From: huyan Date: Thu, 20 Aug 2026 19:51:45 +0800 Subject: [PATCH 2/9] build(pixel-grid): define detector crate The native detector needs a reproducible package boundary and dependency graph. Add the Rust manifest, locked dependency set, and local target exclusion. The module can now be built in isolation with deterministic dependency resolution. --- backend/native/pixel_grid_detector/.gitignore | 1 + backend/native/pixel_grid_detector/Cargo.lock | 366 ++++++++++++++++++ backend/native/pixel_grid_detector/Cargo.toml | 21 + 3 files changed, 388 insertions(+) create mode 100644 backend/native/pixel_grid_detector/.gitignore create mode 100644 backend/native/pixel_grid_detector/Cargo.lock create mode 100644 backend/native/pixel_grid_detector/Cargo.toml diff --git a/backend/native/pixel_grid_detector/.gitignore b/backend/native/pixel_grid_detector/.gitignore new file mode 100644 index 00000000..b83d2226 --- /dev/null +++ b/backend/native/pixel_grid_detector/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/backend/native/pixel_grid_detector/Cargo.lock b/backend/native/pixel_grid_detector/Cargo.lock new file mode 100644 index 00000000..538559cc --- /dev/null +++ b/backend/native/pixel_grid_detector/Cargo.lock @@ -0,0 +1,366 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rustfft" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "windup-pixel-grid-detector" +version = "0.1.0" +dependencies = [ + "image", + "rayon", + "rustfft", + "serde", + "serde_json", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/backend/native/pixel_grid_detector/Cargo.toml b/backend/native/pixel_grid_detector/Cargo.toml new file mode 100644 index 00000000..a7c5fe32 --- /dev/null +++ b/backend/native/pixel_grid_detector/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "windup-pixel-grid-detector" +version = "0.1.0" +edition = "2021" +license = "MIT" +publish = false + +[dependencies] +image = { version = "0.25", default-features = false, features = ["png", "jpeg"] } +rayon = "1.12.0" +rustfft = "6" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[[bin]] +name = "windup-pixel-grid-detector" +path = "src/main.rs" + +[profile.release] +lto = true +codegen-units = 1 From 533e0d477674dcfa18b62b4061ec1de5b247fec6 Mon Sep 17 00:00:00 2001 From: huyan Date: Thu, 20 Aug 2026 19:51:56 +0800 Subject: [PATCH 3/9] test(pixel-grid): cover detector boundaries The detector contract needs evidence for both simple grids and disputed inputs. Cover arbitration, sub-three-pixel confidence, JSON fields, and bounded encoded input. Regressions in consensus execution or resource handling now fail at the module boundary. --- .../pixel_grid_detector/tests/detector.rs | 136 ++++++++++++++++++ .../pixel_grid_detector/tests/frog-500.png | Bin 0 -> 299734 bytes 2 files changed, 136 insertions(+) create mode 100644 backend/native/pixel_grid_detector/tests/detector.rs create mode 100644 backend/native/pixel_grid_detector/tests/frog-500.png diff --git a/backend/native/pixel_grid_detector/tests/detector.rs b/backend/native/pixel_grid_detector/tests/detector.rs new file mode 100644 index 00000000..fc82cda9 --- /dev/null +++ b/backend/native/pixel_grid_detector/tests/detector.rs @@ -0,0 +1,136 @@ +use std::collections::BTreeSet; +use std::io::{Cursor, Write}; +use std::process::{Command, Stdio}; + +use image::{DynamicImage, ImageFormat, Rgba, RgbaImage}; +use windup_pixel_grid_detector::{detect_bytes, DetectorMode}; + +fn upscaled_pixel_art() -> Vec { + let mut logical = RgbaImage::new(16, 16); + let palette = [ + Rgba([35, 28, 24, 255]), + Rgba([201, 135, 77, 255]), + Rgba([237, 224, 197, 255]), + Rgba([75, 119, 132, 255]), + ]; + for y in 0..16 { + for x in 0..16 { + let index = (x * 7 + y * 11) as usize % palette.len(); + logical.put_pixel(x, y, palette[index]); + } + } + let enlarged = image::imageops::resize(&logical, 64, 64, image::imageops::Nearest); + let mut encoded = Cursor::new(Vec::new()); + DynamicImage::ImageRgba8(enlarged) + .write_to(&mut encoded, ImageFormat::Png) + .expect("encode fixture"); + encoded.into_inner() +} + +#[test] +fn full_detector_recovers_a_regular_pixel_grid() { + let result = detect_bytes(&upscaled_pixel_art(), DetectorMode::Full).expect("detect grid"); + + assert!((15..=17).contains(&result.cols)); + assert!((15..=17).contains(&result.rows)); + assert!((3.7..=4.3).contains(&result.step_x)); + assert!((3.7..=4.3).contains(&result.step_y)); +} + +#[test] +fn full_detector_exercises_the_arbitrated_consensus_path() { + let source = include_bytes!("frog-500.png"); + + let result = detect_bytes(source, DetectorMode::Full).expect("detect distorted grid"); + + assert_eq!((result.cols, result.rows), (121, 114)); + assert_eq!(result.consensus, "arbitrated"); + assert_eq!(result.confidence, "medium"); +} + +#[test] +fn sub_three_pixel_results_are_reported_as_low_confidence() { + let source = encode(RgbaImage::new(64, 64)); + + let result = detect_bytes(&source, DetectorMode::Full).expect("detect ambiguous grid"); + + assert!(result.step_x < 3.0 || result.step_y < 3.0); + assert_eq!(result.confidence, "low"); +} + +#[test] +fn cli_emits_only_the_six_field_grid_contract() { + let mut child = Command::new(env!("CARGO_BIN_EXE_windup-pixel-grid-detector")) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("spawn detector CLI"); + child + .stdin + .take() + .expect("open stdin") + .write_all(&upscaled_pixel_art()) + .expect("write fixture"); + + let output = child.wait_with_output().expect("wait for detector CLI"); + assert!(output.status.success()); + let json: serde_json::Value = serde_json::from_slice(&output.stdout).expect("parse JSON"); + let keys: BTreeSet<&str> = json + .as_object() + .expect("JSON object") + .keys() + .map(String::as_str) + .collect(); + + assert_eq!( + keys, + BTreeSet::from([ + "cols", + "confidence", + "consensus", + "rows", + "step_x", + "step_y" + ]) + ); +} + +#[test] +fn cli_rejects_an_oversized_encoded_input_before_decoding() { + let mut child = Command::new(env!("CARGO_BIN_EXE_windup-pixel-grid-detector")) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("spawn detector CLI"); + child + .stdin + .take() + .expect("open stdin") + .write_all(&vec![0u8; 32 * 1024 * 1024 + 1]) + .expect("write oversized input"); + + let output = child.wait_with_output().expect("wait for detector CLI"); + assert!(!output.status.success()); + assert!(String::from_utf8_lossy(&output.stderr).contains("encoded input exceeds")); +} + +#[test] +fn detector_rejects_images_outside_its_resource_boundary() { + let image = RgbaImage::new(15, 32); + let mut encoded = Cursor::new(Vec::new()); + DynamicImage::ImageRgba8(image) + .write_to(&mut encoded, ImageFormat::Png) + .expect("encode fixture"); + + let error = detect_bytes(&encoded.into_inner(), DetectorMode::Full).unwrap_err(); + + assert!(error.to_string().contains("minimum side is 16px")); +} + +fn encode(image: RgbaImage) -> Vec { + let mut encoded = Cursor::new(Vec::new()); + DynamicImage::ImageRgba8(image) + .write_to(&mut encoded, ImageFormat::Png) + .expect("encode fixture"); + encoded.into_inner() +} diff --git a/backend/native/pixel_grid_detector/tests/frog-500.png b/backend/native/pixel_grid_detector/tests/frog-500.png new file mode 100644 index 0000000000000000000000000000000000000000..65c50aad5c3b2dcb83d7889bd5544195611289d0 GIT binary patch literal 299734 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Π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

e+ZAldw4kjm8XdH@%c>NK&8?e~|zH5CBO;K~zH$ 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 Date: Thu, 20 Aug 2026 19:52:11 +0800 Subject: [PATCH 4/9] docs(pixel-grid): record detector provenance The reused detector must remain reviewable and license compliant. Document the standalone CLI, capability boundary, fixed upstream revision, and MIT terms. Maintainers can trace both the algorithm and its arbitration fixture to their source. --- backend/native/pixel_grid_detector/LICENSE | 21 +++++++++++++++++++ backend/native/pixel_grid_detector/README.md | 16 ++++++++++++++ .../native/pixel_grid_detector/UPSTREAM.md | 7 +++++++ 3 files changed, 44 insertions(+) create mode 100644 backend/native/pixel_grid_detector/LICENSE create mode 100644 backend/native/pixel_grid_detector/README.md create mode 100644 backend/native/pixel_grid_detector/UPSTREAM.md diff --git a/backend/native/pixel_grid_detector/LICENSE b/backend/native/pixel_grid_detector/LICENSE new file mode 100644 index 00000000..9610c123 --- /dev/null +++ b/backend/native/pixel_grid_detector/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Astropulse, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/backend/native/pixel_grid_detector/README.md b/backend/native/pixel_grid_detector/README.md new file mode 100644 index 00000000..6c7a1197 --- /dev/null +++ b/backend/native/pixel_grid_detector/README.md @@ -0,0 +1,16 @@ +# Pixel grid detector + +Windup 的独立本地像素网格识别器。它只读取 PNG/JPEG 字节并输出网格元数据,不负责重建图片,也不引用任何生成管线或应用服务。 + +默认执行 Pixel Art Fixer 的完整多检测器共识流程:autocorrelation、run-length comb、shift self-similarity 与证据仲裁。`--fast` 仅用于显式选择低延迟模式。 + +```bash +cargo run --release -- < input.png +cargo run --release -- --fast < input.png +``` + +输出字段包括 `cols`、`rows`、`step_x`、`step_y`、`consensus` 和 `confidence`。 + +## Capability boundary + +传统网格检测默认面向隐含像素单元不小于约 3px 的输入。小于 3px 的高密度伪像素可能缺少足够的周期证据,本模块暂不保证其自动识别结果;调用方应允许用户提供显式像素尺寸。这里不添加针对单张图片的启发式特例。 diff --git a/backend/native/pixel_grid_detector/UPSTREAM.md b/backend/native/pixel_grid_detector/UPSTREAM.md new file mode 100644 index 00000000..a7035647 --- /dev/null +++ b/backend/native/pixel_grid_detector/UPSTREAM.md @@ -0,0 +1,7 @@ +# Upstream + +检测算法源自 [Retro-Diffusion/pixel-art-fixer](https://github.com/Retro-Diffusion/pixel-art-fixer),固定于提交 `ef376e57e1c272633ca2dbf5f29ec3fcf6596465`,使用 MIT License。 + +Windup 仅增加独立的 stdin/JSON 边界、输入资源限制、项目内测试与统一格式化;检测算法逻辑保持上游实现。 + +`tests/frog-500.png` 由同一固定提交的 MIT 示例 `examples/frog.png` 以 Lanczos 缩放至 500×500,用于覆盖完整检测器的仲裁路径。 From baa1eb679134341f3a5d8397d24f99e8aec76f21 Mon Sep 17 00:00:00 2001 From: huyan Date: Thu, 20 Aug 2026 19:52:22 +0800 Subject: [PATCH 5/9] ci(pixel-grid): verify detector module The standalone native module needs a gate that does not widen backend CI. Run format and locked release tests only when detector paths or its workflow change. Detector changes now receive isolated validation without touching generation jobs. --- .github/workflows/pixel-grid-detector.yml | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/pixel-grid-detector.yml diff --git a/.github/workflows/pixel-grid-detector.yml b/.github/workflows/pixel-grid-detector.yml new file mode 100644 index 00000000..c00d0c8d --- /dev/null +++ b/.github/workflows/pixel-grid-detector.yml @@ -0,0 +1,29 @@ +name: Pixel grid detector CI + +on: + push: + paths: + - "backend/native/pixel_grid_detector/**" + - ".github/workflows/pixel-grid-detector.yml" + pull_request: + paths: + - "backend/native/pixel_grid_detector/**" + - ".github/workflows/pixel-grid-detector.yml" + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend/native/pixel_grid_detector + steps: + - uses: actions/checkout@v7 + + - name: Rust format + run: cargo fmt --check + + - name: Rust tests + run: cargo test --release --locked From f1ef06ce0336f845dafa8bdff22b0ed9c6f59de3 Mon Sep 17 00:00:00 2001 From: huyan Date: Fri, 21 Aug 2026 11:44:41 +0800 Subject: [PATCH 6/9] refactor(pixel-grid): move detector outside Python backend The detector is a native algorithm library rather than Python backend code. Move the crate into the root native workspace and remove the retired CLI boundary. Keep the detector independently buildable for the later PyO3 binding. --- backend/native/pixel_grid_detector/.gitignore | 1 - .../native/pixel_grid_detector/src/main.rs | 34 ------ native/pixel-perfect/.gitignore | 1 + .../pixel-perfect}/Cargo.lock | 98 ------------------ native/pixel-perfect/Cargo.toml | 7 ++ .../pixel-perfect/crates/detector}/Cargo.toml | 10 -- .../pixel-perfect/crates/detector}/LICENSE | 0 .../pixel-perfect/crates/detector}/README.md | 9 +- .../crates/detector}/UPSTREAM.md | 2 +- .../pixel-perfect/crates/detector}/src/acf.rs | 0 .../crates/detector}/src/autocorr.rs | 0 .../crates/detector}/src/core.rs | 0 .../crates/detector}/src/fusionchan.rs | 0 .../crates/detector}/src/gray.rs | 0 .../crates/detector}/src/kmeans.rs | 0 .../pixel-perfect/crates/detector}/src/lib.rs | 3 +- .../crates/detector}/src/reconsearch.rs | 0 .../crates/detector}/src/runlengths.rs | 0 .../crates/detector}/src/selfsim.rs | 0 .../crates/detector}/src/sigproc.rs | 0 .../crates/detector}/src/varcontrast.rs | 0 .../crates/detector}/tests/detector.rs | 60 +---------- .../crates/detector}/tests/frog-500.png | Bin 23 files changed, 15 insertions(+), 210 deletions(-) delete mode 100644 backend/native/pixel_grid_detector/.gitignore delete mode 100644 backend/native/pixel_grid_detector/src/main.rs create mode 100644 native/pixel-perfect/.gitignore rename {backend/native/pixel_grid_detector => native/pixel-perfect}/Cargo.lock (73%) create mode 100644 native/pixel-perfect/Cargo.toml rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/Cargo.toml (57%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/LICENSE (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/README.md (52%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/UPSTREAM.md (71%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/acf.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/autocorr.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/core.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/fusionchan.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/gray.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/kmeans.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/lib.rs (98%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/reconsearch.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/runlengths.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/selfsim.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/sigproc.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/src/varcontrast.rs (100%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/tests/detector.rs (59%) rename {backend/native/pixel_grid_detector => native/pixel-perfect/crates/detector}/tests/frog-500.png (100%) diff --git a/backend/native/pixel_grid_detector/.gitignore b/backend/native/pixel_grid_detector/.gitignore deleted file mode 100644 index b83d2226..00000000 --- a/backend/native/pixel_grid_detector/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/backend/native/pixel_grid_detector/src/main.rs b/backend/native/pixel_grid_detector/src/main.rs deleted file mode 100644 index c1ec2931..00000000 --- a/backend/native/pixel_grid_detector/src/main.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::io::{Read, Write}; - -use windup_pixel_grid_detector::{detect_bytes, DetectorMode, MAX_INPUT_BYTES}; - -fn fail(message: impl std::fmt::Display) -> ! { - eprintln!("{message}"); - std::process::exit(1); -} - -fn main() { - let mode = match std::env::args().nth(1).as_deref() { - None | Some("--full") => DetectorMode::Full, - Some("--fast") => DetectorMode::Fast, - Some(option) => fail(format!("unknown option: {option}")), - }; - if std::env::args().nth(2).is_some() { - fail("too many arguments"); - } - - let mut source = Vec::with_capacity(MAX_INPUT_BYTES.min(1024 * 1024)); - std::io::stdin() - .take((MAX_INPUT_BYTES + 1) as u64) - .read_to_end(&mut source) - .unwrap_or_else(|error| fail(format!("cannot read image: {error}"))); - if source.len() > MAX_INPUT_BYTES { - fail(format!("encoded input exceeds {MAX_INPUT_BYTES} bytes")); - } - let result = detect_bytes(&source, mode).unwrap_or_else(|error| fail(error)); - serde_json::to_writer(std::io::stdout(), &result) - .unwrap_or_else(|error| fail(format!("cannot encode detection: {error}"))); - std::io::stdout() - .write_all(b"\n") - .unwrap_or_else(|error| fail(format!("cannot write detection: {error}"))); -} diff --git a/native/pixel-perfect/.gitignore b/native/pixel-perfect/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/native/pixel-perfect/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/backend/native/pixel_grid_detector/Cargo.lock b/native/pixel-perfect/Cargo.lock similarity index 73% rename from backend/native/pixel_grid_detector/Cargo.lock rename to native/pixel-perfect/Cargo.lock index 538559cc..68550986 100644 --- a/backend/native/pixel_grid_detector/Cargo.lock +++ b/native/pixel-perfect/Cargo.lock @@ -112,18 +112,6 @@ dependencies = [ "zune-jpeg", ] -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -193,30 +181,12 @@ dependencies = [ "num-integer", ] -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - [[package]] name = "pxfm" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - [[package]] name = "rayon" version = "1.12.0" @@ -251,49 +221,6 @@ dependencies = [ "transpose", ] -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - [[package]] name = "simd-adler32" version = "0.3.10" @@ -306,17 +233,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" -[[package]] -name = "syn" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "transpose" version = "0.2.3" @@ -327,12 +243,6 @@ dependencies = [ "strength_reduce", ] -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - [[package]] name = "windup-pixel-grid-detector" version = "0.1.0" @@ -340,16 +250,8 @@ dependencies = [ "image", "rayon", "rustfft", - "serde", - "serde_json", ] -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - [[package]] name = "zune-core" version = "0.5.3" diff --git a/native/pixel-perfect/Cargo.toml b/native/pixel-perfect/Cargo.toml new file mode 100644 index 00000000..2a538946 --- /dev/null +++ b/native/pixel-perfect/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +members = ["crates/detector"] +resolver = "2" + +[profile.release] +lto = true +codegen-units = 1 diff --git a/backend/native/pixel_grid_detector/Cargo.toml b/native/pixel-perfect/crates/detector/Cargo.toml similarity index 57% rename from backend/native/pixel_grid_detector/Cargo.toml rename to native/pixel-perfect/crates/detector/Cargo.toml index a7c5fe32..6c56cf40 100644 --- a/backend/native/pixel_grid_detector/Cargo.toml +++ b/native/pixel-perfect/crates/detector/Cargo.toml @@ -9,13 +9,3 @@ publish = false image = { version = "0.25", default-features = false, features = ["png", "jpeg"] } rayon = "1.12.0" rustfft = "6" -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -[[bin]] -name = "windup-pixel-grid-detector" -path = "src/main.rs" - -[profile.release] -lto = true -codegen-units = 1 diff --git a/backend/native/pixel_grid_detector/LICENSE b/native/pixel-perfect/crates/detector/LICENSE similarity index 100% rename from backend/native/pixel_grid_detector/LICENSE rename to native/pixel-perfect/crates/detector/LICENSE diff --git a/backend/native/pixel_grid_detector/README.md b/native/pixel-perfect/crates/detector/README.md similarity index 52% rename from backend/native/pixel_grid_detector/README.md rename to native/pixel-perfect/crates/detector/README.md index 6c7a1197..03108558 100644 --- a/backend/native/pixel_grid_detector/README.md +++ b/native/pixel-perfect/crates/detector/README.md @@ -1,15 +1,14 @@ # Pixel grid detector -Windup 的独立本地像素网格识别器。它只读取 PNG/JPEG 字节并输出网格元数据,不负责重建图片,也不引用任何生成管线或应用服务。 +Windup 的独立本地像素网格识别库。它只读取 PNG/JPEG 字节并返回网格元数据,不负责重建图片,也不引用任何生成管线或应用服务。 -默认执行 Pixel Art Fixer 的完整多检测器共识流程:autocorrelation、run-length comb、shift self-similarity 与证据仲裁。`--fast` 仅用于显式选择低延迟模式。 +默认执行 Pixel Art Fixer 的完整多检测器共识流程:autocorrelation、run-length comb、shift self-similarity 与证据仲裁。调用方可以显式选择 `DetectorMode::Fast` 作为低延迟模式。 ```bash -cargo run --release -- < input.png -cargo run --release -- --fast < input.png +cargo test --release --locked -p windup-pixel-grid-detector ``` -输出字段包括 `cols`、`rows`、`step_x`、`step_y`、`consensus` 和 `confidence`。 +公共入口为 `detect_bytes`,返回字段包括 `cols`、`rows`、`step_x`、`step_y`、`consensus` 和 `confidence`。Python 绑定由独立集成模块提供,本 crate 不依赖 Python 或 Windup 后端。 ## Capability boundary diff --git a/backend/native/pixel_grid_detector/UPSTREAM.md b/native/pixel-perfect/crates/detector/UPSTREAM.md similarity index 71% rename from backend/native/pixel_grid_detector/UPSTREAM.md rename to native/pixel-perfect/crates/detector/UPSTREAM.md index a7035647..1b4ca069 100644 --- a/backend/native/pixel_grid_detector/UPSTREAM.md +++ b/native/pixel-perfect/crates/detector/UPSTREAM.md @@ -2,6 +2,6 @@ 检测算法源自 [Retro-Diffusion/pixel-art-fixer](https://github.com/Retro-Diffusion/pixel-art-fixer),固定于提交 `ef376e57e1c272633ca2dbf5f29ec3fcf6596465`,使用 MIT License。 -Windup 仅增加独立的 stdin/JSON 边界、输入资源限制、项目内测试与统一格式化;检测算法逻辑保持上游实现。 +Windup 仅增加字节输入的 library 边界、输入资源限制、项目内测试与统一格式化;检测算法逻辑保持上游实现。 `tests/frog-500.png` 由同一固定提交的 MIT 示例 `examples/frog.png` 以 Lanczos 缩放至 500×500,用于覆盖完整检测器的仲裁路径。 diff --git a/backend/native/pixel_grid_detector/src/acf.rs b/native/pixel-perfect/crates/detector/src/acf.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/acf.rs rename to native/pixel-perfect/crates/detector/src/acf.rs diff --git a/backend/native/pixel_grid_detector/src/autocorr.rs b/native/pixel-perfect/crates/detector/src/autocorr.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/autocorr.rs rename to native/pixel-perfect/crates/detector/src/autocorr.rs diff --git a/backend/native/pixel_grid_detector/src/core.rs b/native/pixel-perfect/crates/detector/src/core.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/core.rs rename to native/pixel-perfect/crates/detector/src/core.rs diff --git a/backend/native/pixel_grid_detector/src/fusionchan.rs b/native/pixel-perfect/crates/detector/src/fusionchan.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/fusionchan.rs rename to native/pixel-perfect/crates/detector/src/fusionchan.rs diff --git a/backend/native/pixel_grid_detector/src/gray.rs b/native/pixel-perfect/crates/detector/src/gray.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/gray.rs rename to native/pixel-perfect/crates/detector/src/gray.rs diff --git a/backend/native/pixel_grid_detector/src/kmeans.rs b/native/pixel-perfect/crates/detector/src/kmeans.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/kmeans.rs rename to native/pixel-perfect/crates/detector/src/kmeans.rs diff --git a/backend/native/pixel_grid_detector/src/lib.rs b/native/pixel-perfect/crates/detector/src/lib.rs similarity index 98% rename from backend/native/pixel_grid_detector/src/lib.rs rename to native/pixel-perfect/crates/detector/src/lib.rs index 4c55ea45..450dec24 100644 --- a/backend/native/pixel_grid_detector/src/lib.rs +++ b/native/pixel-perfect/crates/detector/src/lib.rs @@ -15,7 +15,6 @@ pub mod varcontrast; use std::io::Cursor; use image::{ImageFormat, ImageReader, Limits}; -use serde::Serialize; pub const MAX_INPUT_PIXELS: usize = 4_000_000; pub const MAX_INPUT_BYTES: usize = 32 * 1024 * 1024; @@ -28,7 +27,7 @@ pub enum DetectorMode { Fast, } -#[derive(Debug, Serialize)] +#[derive(Debug)] pub struct GridDetection { pub step_x: f64, pub step_y: f64, diff --git a/backend/native/pixel_grid_detector/src/reconsearch.rs b/native/pixel-perfect/crates/detector/src/reconsearch.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/reconsearch.rs rename to native/pixel-perfect/crates/detector/src/reconsearch.rs diff --git a/backend/native/pixel_grid_detector/src/runlengths.rs b/native/pixel-perfect/crates/detector/src/runlengths.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/runlengths.rs rename to native/pixel-perfect/crates/detector/src/runlengths.rs diff --git a/backend/native/pixel_grid_detector/src/selfsim.rs b/native/pixel-perfect/crates/detector/src/selfsim.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/selfsim.rs rename to native/pixel-perfect/crates/detector/src/selfsim.rs diff --git a/backend/native/pixel_grid_detector/src/sigproc.rs b/native/pixel-perfect/crates/detector/src/sigproc.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/sigproc.rs rename to native/pixel-perfect/crates/detector/src/sigproc.rs diff --git a/backend/native/pixel_grid_detector/src/varcontrast.rs b/native/pixel-perfect/crates/detector/src/varcontrast.rs similarity index 100% rename from backend/native/pixel_grid_detector/src/varcontrast.rs rename to native/pixel-perfect/crates/detector/src/varcontrast.rs diff --git a/backend/native/pixel_grid_detector/tests/detector.rs b/native/pixel-perfect/crates/detector/tests/detector.rs similarity index 59% rename from backend/native/pixel_grid_detector/tests/detector.rs rename to native/pixel-perfect/crates/detector/tests/detector.rs index fc82cda9..8b33f99f 100644 --- a/backend/native/pixel_grid_detector/tests/detector.rs +++ b/native/pixel-perfect/crates/detector/tests/detector.rs @@ -1,6 +1,4 @@ -use std::collections::BTreeSet; -use std::io::{Cursor, Write}; -use std::process::{Command, Stdio}; +use std::io::Cursor; use image::{DynamicImage, ImageFormat, Rgba, RgbaImage}; use windup_pixel_grid_detector::{detect_bytes, DetectorMode}; @@ -58,62 +56,6 @@ fn sub_three_pixel_results_are_reported_as_low_confidence() { assert_eq!(result.confidence, "low"); } -#[test] -fn cli_emits_only_the_six_field_grid_contract() { - let mut child = Command::new(env!("CARGO_BIN_EXE_windup-pixel-grid-detector")) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("spawn detector CLI"); - child - .stdin - .take() - .expect("open stdin") - .write_all(&upscaled_pixel_art()) - .expect("write fixture"); - - let output = child.wait_with_output().expect("wait for detector CLI"); - assert!(output.status.success()); - let json: serde_json::Value = serde_json::from_slice(&output.stdout).expect("parse JSON"); - let keys: BTreeSet<&str> = json - .as_object() - .expect("JSON object") - .keys() - .map(String::as_str) - .collect(); - - assert_eq!( - keys, - BTreeSet::from([ - "cols", - "confidence", - "consensus", - "rows", - "step_x", - "step_y" - ]) - ); -} - -#[test] -fn cli_rejects_an_oversized_encoded_input_before_decoding() { - let mut child = Command::new(env!("CARGO_BIN_EXE_windup-pixel-grid-detector")) - .stdin(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("spawn detector CLI"); - child - .stdin - .take() - .expect("open stdin") - .write_all(&vec![0u8; 32 * 1024 * 1024 + 1]) - .expect("write oversized input"); - - let output = child.wait_with_output().expect("wait for detector CLI"); - assert!(!output.status.success()); - assert!(String::from_utf8_lossy(&output.stderr).contains("encoded input exceeds")); -} - #[test] fn detector_rejects_images_outside_its_resource_boundary() { let image = RgbaImage::new(15, 32); diff --git a/backend/native/pixel_grid_detector/tests/frog-500.png b/native/pixel-perfect/crates/detector/tests/frog-500.png similarity index 100% rename from backend/native/pixel_grid_detector/tests/frog-500.png rename to native/pixel-perfect/crates/detector/tests/frog-500.png From 3be6ccc1952f9411548d1edefc3f7a369b8611f8 Mon Sep 17 00:00:00 2001 From: huyan Date: Fri, 21 Aug 2026 11:44:53 +0800 Subject: [PATCH 7/9] ci(pixel-grid): follow detector workspace move The detector CI still watched the retired backend-native path. Point path filters and the working directory at the root native workspace. Detector checks now run for the relocated crate. --- .github/workflows/pixel-grid-detector.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pixel-grid-detector.yml b/.github/workflows/pixel-grid-detector.yml index c00d0c8d..0e781d8b 100644 --- a/.github/workflows/pixel-grid-detector.yml +++ b/.github/workflows/pixel-grid-detector.yml @@ -3,11 +3,11 @@ name: Pixel grid detector CI on: push: paths: - - "backend/native/pixel_grid_detector/**" + - "native/pixel-perfect/**" - ".github/workflows/pixel-grid-detector.yml" pull_request: paths: - - "backend/native/pixel_grid_detector/**" + - "native/pixel-perfect/**" - ".github/workflows/pixel-grid-detector.yml" permissions: @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: backend/native/pixel_grid_detector + working-directory: native/pixel-perfect steps: - uses: actions/checkout@v7 From cc72014694201b0c4bb7fe48ab3e491724734163 Mon Sep 17 00:00:00 2001 From: huyan Date: Fri, 21 Aug 2026 11:56:29 +0800 Subject: [PATCH 8/9] refactor(pixel-grid): keep detector crate independent A shared workspace would couple later pull requests before their merge order is established. Keep the detector lockfile with its crate and remove the premature workspace manifest. Each native module can now build and land independently. --- native/pixel-perfect/Cargo.toml | 7 ------- native/pixel-perfect/{ => crates/detector}/Cargo.lock | 0 native/pixel-perfect/crates/detector/README.md | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 native/pixel-perfect/Cargo.toml rename native/pixel-perfect/{ => crates/detector}/Cargo.lock (100%) diff --git a/native/pixel-perfect/Cargo.toml b/native/pixel-perfect/Cargo.toml deleted file mode 100644 index 2a538946..00000000 --- a/native/pixel-perfect/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[workspace] -members = ["crates/detector"] -resolver = "2" - -[profile.release] -lto = true -codegen-units = 1 diff --git a/native/pixel-perfect/Cargo.lock b/native/pixel-perfect/crates/detector/Cargo.lock similarity index 100% rename from native/pixel-perfect/Cargo.lock rename to native/pixel-perfect/crates/detector/Cargo.lock diff --git a/native/pixel-perfect/crates/detector/README.md b/native/pixel-perfect/crates/detector/README.md index 03108558..d30311d2 100644 --- a/native/pixel-perfect/crates/detector/README.md +++ b/native/pixel-perfect/crates/detector/README.md @@ -5,7 +5,7 @@ Windup 的独立本地像素网格识别库。它只读取 PNG/JPEG 字节并返 默认执行 Pixel Art Fixer 的完整多检测器共识流程:autocorrelation、run-length comb、shift self-similarity 与证据仲裁。调用方可以显式选择 `DetectorMode::Fast` 作为低延迟模式。 ```bash -cargo test --release --locked -p windup-pixel-grid-detector +cargo test --release --locked ``` 公共入口为 `detect_bytes`,返回字段包括 `cols`、`rows`、`step_x`、`step_y`、`consensus` 和 `confidence`。Python 绑定由独立集成模块提供,本 crate 不依赖 Python 或 Windup 后端。 From 0e07222d7eb432d5de92f18dd961383f26bf2574 Mon Sep 17 00:00:00 2001 From: huyan Date: Fri, 21 Aug 2026 11:56:29 +0800 Subject: [PATCH 9/9] ci(pixel-grid): test the independent detector crate The detector no longer builds through a shared native workspace. Run its workflow from the crate directory with the crate-local lockfile. CI now matches the independently reviewable module boundary. --- .github/workflows/pixel-grid-detector.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pixel-grid-detector.yml b/.github/workflows/pixel-grid-detector.yml index 0e781d8b..9c2602c5 100644 --- a/.github/workflows/pixel-grid-detector.yml +++ b/.github/workflows/pixel-grid-detector.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: native/pixel-perfect + working-directory: native/pixel-perfect/crates/detector steps: - uses: actions/checkout@v7