use rten_tensor::prelude::*;
use rten_tensor::{NdTensor, NdTensorView};
pub fn pad_hw_end(
img: NdTensorView<f32, 4>,
pad_h: usize,
pad_w: usize,
value: f32,
) -> NdTensor<f32, 4> {
let [n, c, h, w] = img.shape();
let (oh, ow) = (h + pad_h, w + pad_w);
let mut out = vec![value; n * c * oh * ow];
for ni in 0..n {
for ci in 0..c {
for y in 0..h {
let row = ((ni * c + ci) * oh + y) * ow;
for x in 0..w {
out[row + x] = img[[ni, ci, y, x]];
}
}
}
}
NdTensor::from_data([n, c, oh, ow], out)
}
pub fn resize_bilinear(img: NdTensorView<f32, 4>, out_h: usize, out_w: usize) -> NdTensor<f32, 4> {
let [n, c, h, w] = img.shape();
if out_h == h && out_w == w {
return img.to_tensor();
}
let mut out = vec![0f32; n * c * out_h * out_w];
let scale_y = h as f32 / out_h as f32;
let scale_x = w as f32 / out_w as f32;
let max_y = h.saturating_sub(1);
let max_x = w.saturating_sub(1);
for ni in 0..n {
for ci in 0..c {
for oy in 0..out_h {
let fy = ((oy as f32 + 0.5) * scale_y - 0.5).clamp(0.0, max_y as f32);
let y0 = fy.floor() as usize;
let y1 = (y0 + 1).min(max_y);
let wy = fy - y0 as f32;
let dst_row = ((ni * c + ci) * out_h + oy) * out_w;
for ox in 0..out_w {
let fx = ((ox as f32 + 0.5) * scale_x - 0.5).clamp(0.0, max_x as f32);
let x0 = fx.floor() as usize;
let x1 = (x0 + 1).min(max_x);
let wx = fx - x0 as f32;
let v00 = img[[ni, ci, y0, x0]];
let v01 = img[[ni, ci, y0, x1]];
let v10 = img[[ni, ci, y1, x0]];
let v11 = img[[ni, ci, y1, x1]];
let top = v00 + (v01 - v00) * wx;
let bot = v10 + (v11 - v10) * wx;
out[dst_row + ox] = top + (bot - top) * wy;
}
}
}
}
NdTensor::from_data([n, c, out_h, out_w], out)
}
#[cfg(test)]
mod timing {
use super::*;
#[test]
#[ignore]
fn time_host_resize() {
let (h, w) = (2048usize, 2048usize);
let img = NdTensor::from_data([1, 1, h, w], vec![0.5f32; h * w]);
let t = std::time::Instant::now();
let _ = resize_bilinear(img.view(), 1024, 1024);
eprintln!("contiguous 2048x2048 -> 1024x1024: {:?}", t.elapsed());
let sliced = img.slice((.., .., ..1500, ..1500));
let t2 = std::time::Instant::now();
let _ = resize_bilinear(sliced, 1654, 2339);
eprintln!("strided 1500x1500 -> 1654x2339: {:?}", t2.elapsed());
let t3 = std::time::Instant::now();
let _ = pad_hw_end(img.view(), 200, 200, -0.5);
eprintln!("pad 2048x2048 +200,200: {:?}", t3.elapsed());
}
}
#[cfg(all(test, feature = "tensor-ops"))]
mod parity_tests {
use super::*;
use rten::{FloatOperators, Operators};
fn ramp(h: usize, w: usize) -> NdTensor<f32, 4> {
let data: Vec<f32> = (0..h * w)
.map(|i| ((i * 31 % 251) as f32) / 251.0)
.collect();
NdTensor::from_data([1, 1, h, w], data)
}
fn max_abs_diff(a: &[f32], b: &[f32]) -> f32 {
assert_eq!(
a.len(),
b.len(),
"shape mismatch: {} vs {}",
a.len(),
b.len()
);
a.iter()
.zip(b)
.map(|(x, y)| (x - y).abs())
.fold(0.0f32, f32::max)
}
#[test]
fn resize_matches_rten() {
let img = ramp(17, 23);
for (oh, ow) in [(32, 40), (8, 11), (17, 23), (40, 7)] {
let mine: Vec<f32> = resize_bilinear(img.view(), oh, ow)
.iter()
.copied()
.collect();
let theirs: Vec<f32> = img
.view()
.resize_image([oh, ow])
.unwrap()
.iter()
.copied()
.collect();
let d = max_abs_diff(&mine, &theirs);
assert!(d < 1e-4, "resize {oh}x{ow}: max abs diff {d}");
}
}
#[test]
fn pad_matches_rten() {
let img = ramp(13, 19);
let (pb, pr) = (6, 4);
let val = super::super::preprocess::BLACK_VALUE;
let mine: Vec<f32> = pad_hw_end(img.view(), pb, pr, val)
.iter()
.copied()
.collect();
let pads = &[0, 0, 0, 0, 0, 0, pb as i32, pr as i32];
let theirs: Vec<f32> = img
.view()
.pad(pads.into(), val)
.unwrap()
.iter()
.copied()
.collect();
let d = max_abs_diff(&mine, &theirs);
assert!(d < 1e-6, "pad: max abs diff {d}");
}
}