#[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) {
let u = x_d / self.scale;
let v = y_d / self.scale;
let dx = eval_poly(&self.ap_coeffs, self.order, u, v);
let dy = eval_poly(&self.bp_coeffs, self.order, u, v);
(x_d + dx * self.scale, y_d + dy * self.scale)
}
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
}
pub fn num_coeffs_range(min_order: u32, max_order: u32) -> usize {
assert!(min_order <= max_order);
let total = num_coeffs(max_order);
if min_order == 0 {
total
} else {
total - num_coeffs(min_order - 1)
}
}
pub fn term_pairs_range(min_order: u32, max_order: u32) -> Vec<(u32, u32)> {
assert!(min_order <= max_order);
let mut pairs = Vec::with_capacity(num_coeffs_range(min_order, max_order));
for s in min_order..=max_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
}
#[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_num_coeffs_range() {
assert_eq!(num_coeffs_range(2, 4), 12);
assert_eq!(num_coeffs_range(0, 4), num_coeffs(4));
assert_eq!(num_coeffs_range(2, 2), 3); assert_eq!(num_coeffs_range(3, 3), 4); assert_eq!(num_coeffs_range(1, 4), num_coeffs(4) - 1); }
#[test]
fn test_term_pairs_range() {
let pairs = term_pairs_range(2, 4);
assert_eq!(pairs.len(), 12);
for &(p, q) in &pairs {
assert!(p + q >= 2, "({}, {}) has sum < 2", p, q);
assert!(p + q <= 4, "({}, {}) has sum > 4", p, q);
}
assert_eq!(pairs[0], (2, 0));
assert!(!pairs.contains(&(0, 0)));
assert!(!pairs.contains(&(1, 0)));
assert!(!pairs.contains(&(0, 1)));
}
#[test]
fn test_term_pairs_range_matches_full() {
let full = term_pairs(4);
let range = term_pairs_range(0, 4);
assert_eq!(full, range);
}
#[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_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);
}
}