use ndarray::ArrayView2;
#[inline]
pub(super) fn catmull_rom_weights(frac: f64) -> [f64; 4] {
let t = frac;
[
((-0.5 * t + 1.0) * t - 0.5) * t,
((1.5 * t - 2.5) * t) * t + 1.0,
((-1.5 * t + 2.0) * t + 0.5) * t,
((0.5 * t - 0.5) * t) * t,
]
}
#[inline]
pub(super) fn catmull_rom_weight_derivatives(frac: f64) -> [f64; 4] {
let t = frac;
[
(-1.5 * t + 2.0) * t - 0.5,
(4.5 * t - 5.0) * t,
(-4.5 * t + 4.0) * t + 0.5,
(1.5 * t - 1.0) * t,
]
}
pub(super) fn catmull_rom_sample(epsf: &ArrayView2<f64>, k_u: f64, k_v: f64) -> f64 {
let side = epsf.shape()[0] as i64;
let u_floor = k_u.floor();
let v_floor = k_v.floor();
let weights_u = catmull_rom_weights(k_u - u_floor);
let weights_v = catmull_rom_weights(k_v - v_floor);
let base_u = u_floor as i64 - 1;
let base_v = v_floor as i64 - 1;
let mut value = 0.0_f64;
for (tap_u, &weight_u) in weights_u.iter().enumerate() {
let row = base_u + tap_u as i64;
if row < 0 || row >= side {
continue; }
for (tap_v, &weight_v) in weights_v.iter().enumerate() {
let column = base_v + tap_v as i64;
if column < 0 || column >= side {
continue; }
value += epsf[(row as usize, column as usize)] * weight_u * weight_v;
}
}
value
}
#[cfg(test)]
mod tests {
use super::{catmull_rom_weight_derivatives, catmull_rom_weights};
#[test]
fn weight_derivatives_match_central_difference() {
let step = 1e-6;
for &frac in &[0.05, 0.2, 0.37, 0.5, 0.63, 0.8, 0.95] {
let analytic = catmull_rom_weight_derivatives(frac);
let forward = catmull_rom_weights(frac + step);
let backward = catmull_rom_weights(frac - step);
for tap in 0..4 {
let finite_difference = (forward[tap] - backward[tap]) / (2.0 * step);
assert!(
(analytic[tap] - finite_difference).abs() < 1e-6,
"tap {tap} at frac {frac}: analytic {} vs finite difference {}",
analytic[tap],
finite_difference
);
}
}
}
#[test]
fn weight_derivatives_sum_to_zero() {
for &frac in &[0.0, 0.1, 0.25, 0.5, 0.75, 0.999] {
let derivatives = catmull_rom_weight_derivatives(frac);
let sum: f64 = derivatives.iter().sum();
assert!(sum.abs() < 1e-12, "sum {sum} at frac {frac}");
}
}
}