use super::cv::{Interp, connected_components, resize};
use image::GrayImage;
const OUT_STRIDE: usize = 2;
const INPUT_MULTIPLE: usize = 32;
const CORE_X_SHRINK: f64 = 0.15;
pub(super) fn page_to_input(gray: &GrayImage, long_side: usize) -> (Vec<f32>, usize, usize, f64) {
let (w, h) = (gray.width() as usize, gray.height() as usize);
let scale = long_side as f64 / w.max(h) as f64;
let nh = ((h as f64 * scale).round_ties_even() as usize).max(1);
let nw = ((w as f64 * scale).round_ties_even() as usize).max(1);
let interp = if scale < 1.0 {
Interp::Area
} else {
Interp::Linear
};
let scaled = resize(gray, nw as u32, nh as u32, interp);
let ph = nh.div_ceil(INPUT_MULTIPLE) * INPUT_MULTIPLE;
let pw = nw.div_ceil(INPUT_MULTIPLE) * INPUT_MULTIPLE;
let mut out = vec![0f32; ph * pw];
for (y, row) in scaled.as_raw().chunks_exact(nw).enumerate() {
for (o, &v) in out[y * pw..y * pw + nw].iter_mut().zip(row) {
*o = 1.0 - v as f32 / 255.0;
}
}
(out, ph, pw, scale)
}
pub(super) fn decode(out: &[f32], h: usize, w: usize) -> Vec<[f32; 5]> {
let plane = h * w;
let prob: Vec<f32> = out[..plane]
.iter()
.map(|&v| 1.0 / (1.0 + (-v).exp()))
.collect();
let mask: Vec<bool> = prob.iter().map(|&p| p > 0.5).collect();
let (labels, stats) = connected_components(&mask, w, h);
let n = stats.len();
let (mut top, mut bot, mut score) = (vec![0f64; n], vec![0f64; n], vec![0f64; n]);
for (i, &l) in labels.iter().enumerate() {
if l == 0 {
continue;
}
let ys = ((i / w) as f32 + 0.5) * OUT_STRIDE as f32;
let c = l as usize - 1;
top[c] += (ys - out[plane + i]) as f64;
bot[c] += (ys + out[2 * plane + i]) as f64;
score[c] += prob[i] as f64;
}
let s = OUT_STRIDE as f64;
stats
.iter()
.enumerate()
.filter(|(_, st)| st.area >= 2)
.map(|(c, st)| {
let cnt = st.area as f64;
let (y0, y1) = (top[c] / cnt, bot[c] / cnt);
let lh = (y1 - y0).max(1.0);
let ex = (CORE_X_SHRINK * lh).min(0.5 * lh);
[
(st.left as f64 * s - ex) as f32,
y0 as f32,
(st.right as f64 * s + ex) as f32,
y1 as f32,
(score[c] / cnt) as f32,
]
})
.collect()
}