use ndarray::Array2;
pub use crate::upstream::scipy_ndimage::rotate;
use crate::upstream::scipy_ndimage::{sample_bilinear, sample_nearest, sincosdg};
pub fn rotate_fixed_plane(input: &Array2<f64>, angle_deg: f64, order: u32) -> Array2<f64> {
assert!(order <= 1, "only spline orders 0 and 1 are supported");
let (nrows, ncols) = (input.nrows(), input.ncols());
let (c, s) = sincosdg(angle_deg);
let cr = (nrows as f64 - 1.0) / 2.0;
let cc = (ncols as f64 - 1.0) / 2.0;
let mut out = Array2::<f64>::from_elem((nrows, ncols), f64::NAN);
for or in 0..nrows {
for oc in 0..ncols {
let pr = c * (or as f64 - cr) + s * (oc as f64 - cc) + cr;
let pc = -s * (or as f64 - cr) + c * (oc as f64 - cc) + cc;
out[(or, oc)] = match order {
0 => sample_nearest(input, pr, pc, f64::NAN),
_ => sample_bilinear(input, pr, pc, f64::NAN),
};
}
}
out
}
#[cfg(test)]
mod fixed_plane_tests {
use super::*;
use ndarray::array;
#[test]
fn same_dims_any_angle() {
let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
for angle in [0.0, 17.0, 90.0, 331.0] {
let out = rotate_fixed_plane(&a, angle, 0);
assert_eq!(out.dim(), a.dim());
}
}
#[test]
fn identity_and_quarter_turn() {
let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
assert_eq!(rotate_fixed_plane(&a, 0.0, 0), a);
let out = rotate_fixed_plane(&a, 90.0, 0);
assert_eq!(
out,
array![[3.0, 6.0, 9.0], [2.0, 5.0, 8.0], [1.0, 4.0, 7.0]]
);
assert!(out.iter().all(|v| v.is_finite()));
}
#[test]
fn off_grid_angles_leave_corner_gaps() {
let a = Array2::from_shape_fn((8, 8), |(r, c)| (r * 8 + c) as f64);
let out = rotate_fixed_plane(&a, 45.0, 0);
assert_eq!(out.dim(), (8, 8));
assert!(out[[0, 0]].is_nan(), "corner becomes a gap, not zero");
assert!(out[[7, 7]].is_nan());
assert!(out[[3, 4]].is_finite(), "interior survives");
let center = out[[3, 3]];
assert!(
[a[[3, 3]], a[[3, 4]], a[[4, 3]], a[[4, 4]]].contains(¢er),
"center sampled {center}"
);
}
}