#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct PolynomialDistortion {
pub order: u32,
pub scale: f64,
pub a_coeffs: Vec<f64>,
pub b_coeffs: Vec<f64>,
pub ap_coeffs: Vec<f64>,
pub bp_coeffs: Vec<f64>,
}
impl PolynomialDistortion {
pub fn new(
order: u32,
scale: f64,
a_coeffs: Vec<f64>,
b_coeffs: Vec<f64>,
ap_coeffs: Vec<f64>,
bp_coeffs: Vec<f64>,
) -> Self {
let n = num_coeffs(order);
assert_eq!(a_coeffs.len(), n, "a_coeffs length mismatch");
assert_eq!(b_coeffs.len(), n, "b_coeffs length mismatch");
assert_eq!(ap_coeffs.len(), n, "ap_coeffs length mismatch");
assert_eq!(bp_coeffs.len(), n, "bp_coeffs length mismatch");
Self {
order,
scale,
a_coeffs,
b_coeffs,
ap_coeffs,
bp_coeffs,
}
}
pub fn zero(order: u32, scale: f64) -> Self {
let n = num_coeffs(order);
Self {
order,
scale,
a_coeffs: vec![0.0; n],
b_coeffs: vec![0.0; n],
ap_coeffs: vec![0.0; n],
bp_coeffs: vec![0.0; n],
}
}
pub fn distort(&self, x: f64, y: f64) -> (f64, f64) {
let u = x / self.scale;
let v = y / self.scale;
let dx = eval_poly(&self.a_coeffs, self.order, u, v);
let dy = eval_poly(&self.b_coeffs, self.order, u, v);
(x + dx * self.scale, y + dy * self.scale)
}
pub fn undistort(&self, x_d: f64, y_d: f64) -> (f64, f64) {
const MAX_ITER: usize = 8;
const TOL_PX: f64 = 1e-9;
let mut x = x_d;
let mut y = y_d;
for _ in 0..MAX_ITER {
let u = x / self.scale;
let v = y / self.scale;
let (a_val, da_du, da_dv) = eval_poly_with_grad(&self.a_coeffs, self.order, u, v);
let (b_val, db_du, db_dv) = eval_poly_with_grad(&self.b_coeffs, self.order, u, v);
let fx = x + a_val * self.scale;
let fy = y + b_val * self.scale;
let rx = fx - x_d;
let ry = fy - y_d;
if rx * rx + ry * ry < TOL_PX * TOL_PX {
break;
}
let j11 = 1.0 + da_du;
let j12 = da_dv;
let j21 = db_du;
let j22 = 1.0 + db_dv;
let det = j11 * j22 - j12 * j21;
debug_assert!(det.abs() > 1e-15, "singular Jacobian in undistort Newton step");
if det.abs() < 1e-15 {
break;
}
let inv_det = 1.0 / det;
x -= inv_det * (j22 * rx - j12 * ry);
y -= inv_det * (-j21 * rx + j11 * ry);
}
(x, y)
}
pub fn is_zero(&self) -> bool {
self.a_coeffs.iter().all(|&c| c == 0.0)
&& self.b_coeffs.iter().all(|&c| c == 0.0)
&& self.ap_coeffs.iter().all(|&c| c == 0.0)
&& self.bp_coeffs.iter().all(|&c| c == 0.0)
}
}
pub fn num_coeffs(order: u32) -> usize {
((order + 1) * (order + 2) / 2) as usize
}
pub fn coeff_index(p: u32, q: u32) -> usize {
let s = p + q;
let base = (s * (s + 1) / 2) as usize;
base + (s - p) as usize
}
pub fn term_pairs(order: u32) -> Vec<(u32, u32)> {
let mut pairs = Vec::with_capacity(num_coeffs(order));
for s in 0..=order {
for p in (0..=s).rev() {
let q = s - p;
pairs.push((p, q));
}
}
pairs
}
fn eval_poly(coeffs: &[f64], order: u32, x: f64, y: f64) -> f64 {
let mut result = 0.0;
let mut idx = 0;
for s in 0..=order {
for p in (0..=s).rev() {
let q = s - p;
result += coeffs[idx] * x.powi(p as i32) * y.powi(q as i32);
idx += 1;
}
}
result
}
fn eval_poly_with_grad(coeffs: &[f64], order: u32, x: f64, y: f64) -> (f64, f64, f64) {
let mut value = 0.0;
let mut df_dx = 0.0;
let mut df_dy = 0.0;
let mut idx = 0;
for s in 0..=order {
for p in (0..=s).rev() {
let q = s - p;
let c = coeffs[idx];
let xp = x.powi(p as i32);
let yq = y.powi(q as i32);
value += c * xp * yq;
if p > 0 {
df_dx += c * (p as f64) * x.powi(p as i32 - 1) * yq;
}
if q > 0 {
df_dy += c * (q as f64) * xp * y.powi(q as i32 - 1);
}
idx += 1;
}
}
(value, df_dx, df_dy)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_num_coeffs() {
assert_eq!(num_coeffs(0), 1); assert_eq!(num_coeffs(1), 3); assert_eq!(num_coeffs(2), 6); assert_eq!(num_coeffs(3), 10); assert_eq!(num_coeffs(4), 15);
assert_eq!(num_coeffs(5), 21);
}
#[test]
fn test_coeff_index() {
assert_eq!(coeff_index(0, 0), 0);
assert_eq!(coeff_index(1, 0), 1);
assert_eq!(coeff_index(0, 1), 2);
assert_eq!(coeff_index(2, 0), 3);
assert_eq!(coeff_index(1, 1), 4);
assert_eq!(coeff_index(0, 2), 5);
assert_eq!(coeff_index(3, 0), 6);
assert_eq!(coeff_index(2, 1), 7);
assert_eq!(coeff_index(1, 2), 8);
assert_eq!(coeff_index(0, 3), 9);
assert_eq!(coeff_index(4, 0), 10);
assert_eq!(coeff_index(3, 1), 11);
assert_eq!(coeff_index(2, 2), 12);
assert_eq!(coeff_index(1, 3), 13);
assert_eq!(coeff_index(0, 4), 14);
}
#[test]
fn test_term_pairs() {
let pairs = term_pairs(3);
assert_eq!(
pairs,
vec![
(0, 0),
(1, 0),
(0, 1),
(2, 0),
(1, 1),
(0, 2),
(3, 0),
(2, 1),
(1, 2),
(0, 3)
]
);
}
#[test]
fn test_zero_distortion_roundtrip() {
let d = PolynomialDistortion::zero(4, 1024.0);
let (xu, yu) = d.undistort(100.0, -200.0);
assert!((xu - 100.0).abs() < 1e-12);
assert!((yu + 200.0).abs() < 1e-12);
let (xd, yd) = d.distort(100.0, -200.0);
assert!((xd - 100.0).abs() < 1e-12);
assert!((yd + 200.0).abs() < 1e-12);
}
#[test]
fn test_newton_undistort_exact_inverse() {
let n = num_coeffs(4);
let mut a = vec![0.0; n];
let mut b = vec![0.0; n];
a[coeff_index(2, 0)] = 0.01;
a[coeff_index(0, 2)] = 0.005;
a[coeff_index(1, 1)] = -0.003;
b[coeff_index(2, 0)] = -0.004;
b[coeff_index(0, 2)] = 0.012;
b[coeff_index(3, 0)] = 0.001;
let d = PolynomialDistortion::new(4, 1024.0, a, b, vec![0.0; n], vec![0.0; n]);
for &(x, y) in &[(0.0, 0.0), (100.0, -200.0), (500.0, 400.0), (-800.0, 100.0)] {
let (xd, yd) = d.distort(x, y);
let (xu, yu) = d.undistort(xd, yd);
assert!(
(xu - x).abs() < 1e-9,
"x roundtrip {} -> {} -> {} (err {:.2e})",
x,
xd,
xu,
xu - x
);
assert!(
(yu - y).abs() < 1e-9,
"y roundtrip {} -> {} -> {} (err {:.2e})",
y,
yd,
yu,
yu - y
);
}
}
#[test]
fn test_distort_undistort_basic() {
let n = num_coeffs(4);
let mut a = vec![0.0; n];
let mut b = vec![0.0; n];
a[coeff_index(2, 0)] = 0.01; b[coeff_index(0, 2)] = -0.005;
let d = PolynomialDistortion::new(
4,
1024.0,
a,
b,
vec![0.0; n], vec![0.0; n], );
let (xd, yd) = d.distort(0.0, 0.0);
assert!(xd.abs() < 1e-12);
assert!(yd.abs() < 1e-12);
let (xd, _yd) = d.distort(512.0, 512.0);
assert!((xd - 512.0 - 2.56).abs() < 1e-10);
}
}