use pagesift_infer::{Linear, Tensor, Weights, ops};
use super::query::Bbox;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Level {
pub h: usize,
pub w: usize,
pub start: usize,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn sample_add(
value: &[f32],
stride: usize,
off: usize,
lv: &Level,
lx: f32,
ly: f32,
weight: f32,
out: &mut [f32],
) {
let pixel = |loc: f32, n: usize| ((2.0 * loc - 1.0 + 1.0) * n as f32 - 1.0) / 2.0;
let (ix, iy) = (pixel(lx, lv.w), pixel(ly, lv.h));
let (fx, fy) = (ix.floor(), iy.floor());
let (tx, ty) = (ix - fx, iy - fy);
let (x0, y0) = (fx as i64, fy as i64);
let n = out.len();
let corners = [
(x0, y0, (1.0 - tx) * (1.0 - ty)),
(x0 + 1, y0, tx * (1.0 - ty)),
(x0, y0 + 1, (1.0 - tx) * ty),
(x0 + 1, y0 + 1, tx * ty),
];
for (x, y, c) in corners {
if x < 0 || y < 0 || x >= lv.w as i64 || y >= lv.h as i64 {
continue;
}
let at = (lv.start + y as usize * lv.w + x as usize) * stride + off;
let k = weight * c;
for (o, v) in out.iter_mut().zip(&value[at..at + n]) {
*o += k * v;
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct DeformAttn {
offsets: Linear,
weights: Linear,
value: Linear,
output: Linear,
heads: usize,
points: usize,
}
impl DeformAttn {
pub(crate) fn load(
w: &Weights,
p: &str,
heads: usize,
points: usize,
) -> Result<DeformAttn, String> {
Ok(DeformAttn {
offsets: Linear::load(w, &format!("{p}.sampling_offsets"))?,
weights: Linear::load(w, &format!("{p}.attention_weights"))?,
value: Linear::load(w, &format!("{p}.value_proj"))?,
output: Linear::load(w, &format!("{p}.output_proj"))?,
heads,
points,
})
}
pub(crate) fn forward(
&self,
query: &Tensor,
src: &Tensor,
levels: &[Level],
refs: &[Bbox],
) -> Tensor {
let (q, c) = query.tc();
let (heads, points, nl) = (self.heads, self.points, levels.len());
let d = c / heads;
let lp = nl * points;
let value = self.value.forward(src);
let off = self.offsets.forward(query);
let mut aw = self.weights.forward(query).reshape(vec![q * heads, lp]);
ops::softmax_rows(&mut aw);
let mut out = vec![0f32; q * c];
let half = 0.5f32;
for (qi, row) in out.chunks_exact_mut(c).enumerate() {
let [cx, cy, bw, bh] = refs[qi];
for (h, acc) in row.chunks_exact_mut(d).enumerate() {
let at = qi * heads + h;
for (l, lv) in levels.iter().enumerate() {
for p in 0..points {
let i = (at * nl + l) * points + p;
let lx = cx + off.data[i * 2] / points as f32 * bw * half;
let ly = cy + off.data[i * 2 + 1] / points as f32 * bh * half;
let wt = aw.data[at * lp + l * points + p];
sample_add(&value.data, c, h * d, lv, lx, ly, wt, acc);
}
}
}
}
self.output.forward(&Tensor::new(vec![q, c], out))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn grid_sample(x: &Tensor, grid: &[(f32, f32)]) -> Vec<Vec<f32>> {
let (c, h, w) = x.chw();
let seq = ops::chw_to_seq(x);
let lv = Level { h, w, start: 0 };
grid.iter()
.map(|&(gx, gy)| {
let mut o = vec![0f32; c];
sample_add(
&seq.data,
c,
0,
&lv,
(gx + 1.0) / 2.0,
(gy + 1.0) / 2.0,
1.0,
&mut o,
);
o
})
.collect()
}
fn close(a: &[f32], b: &[f32]) {
for (p, q) in a.iter().zip(b) {
assert!((p - q).abs() < 1e-5, "{a:?} {b:?}");
}
}
#[test]
fn grid_sample_语义() {
let x = Tensor::new(vec![1, 2, 2], vec![1.0, 2.0, 3.0, 4.0]);
let r = grid_sample(
&x,
&[
(-0.5, -0.5), (0.5, 0.5), (0.0, 0.0), (0.0, -0.5), ],
);
close(&r[0], &[1.0]);
close(&r[1], &[4.0]);
close(&r[2], &[2.5]);
close(&r[3], &[1.5]);
}
#[test]
fn grid_sample_越界补零() {
let x = Tensor::new(vec![1, 2, 2], vec![1.0, 2.0, 3.0, 4.0]);
let r = grid_sample(&x, &[(-1.0, -0.5), (-1.0, -1.0), (-3.0, 0.0), (1.0, 0.5)]);
close(&r[0], &[0.5]);
close(&r[1], &[0.25]);
close(&r[2], &[0.0]);
close(&r[3], &[2.0]);
}
#[test]
fn 多通道与层起点() {
let value = [9., 9., 9., 9., 1., 2., 3., 4., 5., 6., 7., 8.];
let lv = Level {
h: 1,
w: 2,
start: 1,
};
let mut o = [0f32; 2];
sample_add(&value, 4, 2, &lv, 0.5, 0.5, 2.0, &mut o);
close(&o, &[10.0, 12.0]);
}
}