ndarray_vision/transform/
affine.rs

1use super::Transform;
2use ndarray::{array, prelude::*};
3use ndarray_linalg::Inverse;
4
5/// converts a matrix into an equivalent `AffineTransform`
6pub fn transform_from_2dmatrix(in_array: Array2<f64>) -> AffineTransform {
7    match in_array.inv() {
8        Ok(inv) => AffineTransform {
9            matrix2d_transform: in_array,
10            matrix2d_transform_inverse: inv,
11            inverse_exists: true,
12        },
13        Err(_e) => AffineTransform {
14            matrix2d_transform: in_array,
15            matrix2d_transform_inverse: Array2::zeros((2, 2)),
16            inverse_exists: false,
17        },
18    }
19}
20
21/// a linear transform of an image represented by either size 2x2
22/// or 3x3 ( right column is a translation and projection ) matrix applied to the image index
23/// coordinates
24pub struct AffineTransform {
25    matrix2d_transform: Array2<f64>,
26    matrix2d_transform_inverse: Array2<f64>,
27    inverse_exists: bool,
28}
29
30fn source_coordinate(p: (f64, f64), trans: ArrayView2<f64>) -> (f64, f64) {
31    let p = match trans.shape()[0] {
32        2 => array![[p.0], [p.1]],
33        3 => array![[p.0], [p.1], [1.0]],
34        _ => unreachable!(),
35    };
36
37    let result = trans.dot(&p);
38    let x = result[[0, 0]];
39    let y = result[[1, 0]];
40    let w = match trans.shape()[0] {
41        2 => 1.0,
42        3 => result[[2, 0]],
43        _ => unreachable!(),
44    };
45    if (w - 1.0).abs() > std::f64::EPSILON {
46        (x / w, y / w)
47    } else {
48        (x, y)
49    }
50}
51
52impl Transform for AffineTransform {
53    fn apply(&self, p: (f64, f64)) -> (f64, f64) {
54        return source_coordinate(p, self.matrix2d_transform.view());
55    }
56
57    fn apply_inverse(&self, p: (f64, f64)) -> (f64, f64) {
58        return source_coordinate(p, self.matrix2d_transform_inverse.view());
59    }
60
61    fn inverse_exists(&self) -> bool {
62        self.inverse_exists
63    }
64}
65
66/// describes the Axes to use in rotation_3d
67/// X and Y correspond to the image index coordinates and
68/// Z is perpendicular out of the image plane
69pub enum Axes {
70    X,
71    Y,
72    Z,
73}
74
75/// generates a 2d matrix describing a rotation around a 2d coordinate
76pub fn rotate_around_centre(radians: f64, centre: (f64, f64)) -> Array2<f64> {
77    translation(centre.0, centre.1)
78        .dot(&rotation_3d(radians, Axes::Z))
79        .dot(&translation(-centre.0, -centre.1))
80}
81
82/// generates a matrix describing 2d rotation around origin
83pub fn rotation_2d(radians: f64) -> Array2<f64> {
84    let s = radians.sin();
85    let c = radians.cos();
86    array![[c, -s], [s, c]]
87}
88
89/// generates a 3x3 matrix describing a rotation around either the index coordinate axes
90/// (X,Y) or in the perpendicular axes to the image (Z)
91pub fn rotation_3d(radians: f64, ax: Axes) -> Array2<f64> {
92    let s = radians.sin();
93    let c = radians.cos();
94
95    match ax {
96        Axes::X => array![[1.0, 0.0, 0.0], [0.0, c, -s], [0.0, s, c]],
97        Axes::Y => array![[c, 0.0, s], [0.0, 1.0, 0.0], [-s, 0.0, c]],
98        Axes::Z => array![[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]],
99    }
100}
101
102/// generates a matrix describing translation in the image index space
103pub fn translation(x: f64, y: f64) -> Array2<f64> {
104    array![[1.0, 0.0, x], [0.0, 1.0, y], [0.0, 0.0, 1.0]]
105}
106
107/// generates a matrix describing scaling in image index space
108pub fn scale(x: f64, y: f64) -> Array2<f64> {
109    array![[x, 0.0, 0.0], [0.0, y, 0.0], [0.0, 0.0, 1.0]]
110}
111
112/// generates a matrix describing shear in image index space
113pub fn shear(x: f64, y: f64) -> Array2<f64> {
114    array![[1.0, x, 0.0], [y, 1.0, 0.0], [0.0, 0.0, 1.0]]
115}