trk_io/
orientation.rs

1/* The MIT License
2
3Copyright (c) 2009-2014 Matthew Brett <matthew.brett@gmail.com>
4Copyright (c) 2010-2013 Stephan Gerhard <git@unidesign.ch>
5Copyright (c) 2006-2014 Michael Hanke <michael.hanke@gmail.com>
6Copyright (c) 2011 Christian Haselgrove <christian.haselgrove@umassmed.edu>
7Copyright (c) 2010-2011 Jarrod Millman <jarrod.millman@gmail.com>
8Copyright (c) 2011-2014 Yaroslav Halchenko <debian@onerussian.com>
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in
18all copies or substantial portions of the Software. */
19
20/* The ideas in this file were taken from the NiBabel project, which is MIT
21licensed. The port to Rust has been done by:
22
23Copyright (c) 2017-2022 Nil Goyette <nil.goyette@gmail.com>
24*/
25
26use nalgebra::{RowVector3, Vector4};
27#[cfg(feature = "nifti_images")]
28use ndarray::{ArrayBase, Axis, DataMut, Dimension, Zip};
29#[cfg(feature = "nifti_images")]
30use nifti::DataElement;
31
32use crate::{Affine, Affine4, Translation};
33
34#[derive(Clone, Copy, Debug, PartialEq)]
35pub enum Direction {
36    Normal,
37    Reversed,
38}
39
40impl Direction {
41    pub fn to_f32(&self) -> f32 {
42        if *self == Direction::Normal {
43            1.0
44        } else {
45            -1.0
46        }
47    }
48}
49
50pub type Orientation = (usize, Direction);
51pub type Orientations = [Orientation; 3];
52
53/// Axis direction codes for affine `affine`
54pub fn affine_to_axcodes(affine: &Affine) -> String {
55    let orientations = io_orientations(affine);
56    orientations_to_axcodes(orientations)
57}
58
59/// Orientation of input axes in terms of output axes for `affine`
60///
61/// Valid for an affine transformation from `p` dimensions to `q` dimensions
62/// (`affine.shape == (q + 1, p + 1)`).
63///
64/// The calculated orientations can be used to transform associated arrays to
65/// best match the output orientations. If `p` > `q`, then some of the output
66/// axes should be considered dropped in this orientation.
67pub fn io_orientations(affine: &Affine) -> Orientations {
68    // Extract the underlying rotation, zoom, shear matrix
69    let rzs2 = affine.component_mul(affine);
70    let mut zooms = RowVector3::new(
71        (rzs2[0] + rzs2[1] + rzs2[2]).sqrt(),
72        (rzs2[3] + rzs2[4] + rzs2[5]).sqrt(),
73        (rzs2[6] + rzs2[7] + rzs2[8]).sqrt(),
74    );
75
76    // Zooms can be zero, in which case all elements in the column are zero,
77    // and we can leave them as they are
78    zooms.apply(|z| {
79        if *z == 0.0 {
80            *z = 1.0
81        }
82    });
83
84    #[rustfmt::skip]
85    let rs = Affine::new(
86        affine[0] / zooms[0], affine[3] / zooms[1], affine[6] / zooms[2],
87        affine[1] / zooms[0], affine[4] / zooms[1], affine[7] / zooms[2],
88        affine[2] / zooms[0], affine[5] / zooms[1], affine[8] / zooms[2],
89    );
90
91    // Transform below is polar decomposition, returning the closest shearless
92    // matrix R to RS
93    let svd = rs.svd(true, true);
94    let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap());
95
96    // Threshold the singular values to determine the rank.
97    let tol = s.as_slice().iter().cloned().fold(0.0, f32::max) * 3.0 * f32::EPSILON;
98
99    let s = Affine::from_rows(&[s.transpose(), s.transpose(), s.transpose()]);
100    let u = u.zip_map(&s, |u, s| if s > tol { u } else { 0.0 });
101    let v_t = v_t.zip_map(&s, |v, s| if s > tol { v } else { 0.0 });
102
103    // The matrix R is such that np.dot(R, R.T) is projection onto the columns
104    // of P[.., keep] and np.dot(R.T, R) is projection onto the rows of
105    // Qs[keep]. R (== np.dot(R, np.eye(p))) gives rotation of the unit input
106    // vectors to output coordinates. Therefore, the row index of abs max
107    // R[.., N], is the output axis changing most as input axis N changes. In
108    // case there are ties, we choose the axes iteratively, removing used axes
109    // from consideration as we go
110    let mut r = u * v_t;
111
112    let mut orientations = [(0, Direction::Normal), (0, Direction::Normal), (0, Direction::Normal)];
113    for c in 0..3 {
114        let mut argmax = 0;
115        let mut max = 0.0;
116        let mut sign_max = 0.0;
117        for (i, e) in r.column(c).iter().enumerate() {
118            let e_abs = e.abs();
119            if e_abs > max {
120                argmax = i;
121                max = e_abs;
122                sign_max = *e;
123            }
124        }
125
126        if sign_max >= 0.0 {
127            orientations[c] = (argmax, Direction::Normal);
128        } else {
129            orientations[c] = (argmax, Direction::Reversed);
130        }
131
132        // Remove the identified axis from further consideration, by zeroing
133        // out the corresponding row in R
134        for e in r.column_mut(c).iter_mut() {
135            *e = 0.0;
136        }
137    }
138
139    orientations
140}
141
142/// Convert `orientations` to labels for axis directions
143///
144/// The "identity" orientation is RAS. Thus, calling this function with
145///
146/// `[(0, Direction::Normal), (1, Direction::Normal), (2, Direction::Normal)]`
147///
148/// would return "RAS".
149pub fn orientations_to_axcodes(orientations: Orientations) -> String {
150    let labels = [['R', 'L'], ['A', 'P'], ['S', 'I']];
151    orientations
152        .into_iter()
153        .map(|(axis, direction)| labels[axis][(direction == Direction::Reversed) as usize])
154        .collect()
155}
156
157/// Convert axis codes `axcodes` to an orientation
158///
159/// The "identity" orientation is RAS. Thus, calling this function with "RAS" would return
160///
161/// `[(0, Direction::Normal), (1, Direction::Normal), (2, Direction::Normal)]`
162///
163/// If the caller has a different default orientation, like LAS, he must reverse the `Direction` of
164/// the `0` axis.
165pub fn axcodes_to_orientations(axcodes: &str) -> Orientations {
166    let labels = [('R', 'L'), ('A', 'P'), ('S', 'I')];
167    let mut orientations = [(0, Direction::Normal), (0, Direction::Normal), (0, Direction::Normal)];
168    for (code_idx, code) in axcodes.chars().enumerate() {
169        for (label_idx, codes) in labels.iter().enumerate() {
170            if code == codes.0 {
171                orientations[code_idx] = (label_idx, Direction::Normal);
172            } else if code == codes.1 {
173                orientations[code_idx] = (label_idx, Direction::Reversed);
174            }
175        }
176    }
177    orientations
178}
179
180/// Return the orientation that transforms from `start_orientations` to `end_orientations`.
181pub fn orientations_transform(
182    start_orientations: &Orientations,
183    end_orientations: &Orientations,
184) -> Orientations {
185    let mut result = [(0, Direction::Normal), (0, Direction::Normal), (0, Direction::Normal)];
186    for (end_in_idx, &(ref end_out_idx, ref end_flip)) in end_orientations.iter().enumerate() {
187        for (start_in_idx, &(ref start_out_idx, ref start_flip)) in
188            start_orientations.iter().enumerate()
189        {
190            if end_out_idx == start_out_idx {
191                if start_flip == end_flip {
192                    result[start_in_idx] = (end_in_idx, Direction::Normal)
193                } else {
194                    result[start_in_idx] = (end_in_idx, Direction::Reversed)
195                }
196                break;
197            }
198        }
199    }
200    result
201}
202
203#[cfg(feature = "nifti_images")]
204/// Apply transformations implied by `orientations` to the first n axes of the array `arr`.
205pub fn apply_orientation<S, A, D>(
206    mut arr: ArrayBase<S, D>,
207    orientations: Orientations,
208) -> ArrayBase<S, D>
209where
210    S: DataMut<Elem = A>,
211    A: DataElement,
212    D: Dimension,
213{
214    // Apply orientation transformations
215    for (axis, &(_, direction)) in orientations.iter().enumerate() {
216        if direction == Direction::Reversed {
217            Zip::from(arr.lanes_mut(Axis(axis))).for_each(|mut arr| {
218                let n = arr.len();
219                for i in 0..n / 2 {
220                    let tmp = arr[n - 1 - i];
221                    arr[n - 1 - i] = arr[i];
222                    arr[i] = tmp;
223                }
224            });
225        }
226    }
227
228    // Orientation indicates the transpose that has occurred - we reverse it
229    // The following block is simply an argsort of 3 numbers.
230    let mut x = (orientations[0].0, 0);
231    let mut y = (orientations[1].0, 1);
232    let mut z = (orientations[2].0, 2);
233    if x > y {
234        std::mem::swap(&mut x, &mut y);
235    }
236    if y > z {
237        std::mem::swap(&mut y, &mut z);
238    }
239    if x > y {
240        std::mem::swap(&mut x, &mut y);
241    }
242
243    let mut axes = arr.raw_dim();
244    axes[0] = x.1;
245    axes[1] = y.1;
246    axes[2] = z.1;
247    for i in 3..arr.ndim() {
248        axes[i] = i;
249    }
250    arr.permuted_axes(axes)
251}
252
253/// Affine transform reversing transforms implied in `orientations`
254///
255/// Imagine you have an array `arr` of shape `shape`, and you apply the
256/// transforms implied by `orientations`, to get `tarr`. `tarr` may have a
257/// different shape `shape_prime`. This routine returns the affine that will
258/// take an array coordinate for `tarr` and give you the corresponding array
259/// coordinate in `arr`.
260pub fn inverse_orientations_affine(orientations: &Orientations, dim: [i16; 3]) -> Affine4 {
261    let mut undo_reorder = Affine4::zeros();
262    for (i, &(j, _)) in orientations.iter().enumerate() {
263        undo_reorder[(i, j)] = 1.0;
264    }
265    undo_reorder[(3, 3)] = 1.0;
266
267    let center = Translation::new(
268        -(dim[0] - 1) as f32 / 2.0,
269        -(dim[1] - 1) as f32 / 2.0,
270        -(dim[2] - 1) as f32 / 2.0,
271    );
272    let mut undo_flip = Affine4::from_diagonal(&Vector4::new(
273        orientations[0].1.to_f32(),
274        orientations[1].1.to_f32(),
275        orientations[2].1.to_f32(),
276        1.0,
277    ));
278    undo_flip[(0, 3)] = undo_flip[(0, 0)] * center[0] - center[0];
279    undo_flip[(1, 3)] = undo_flip[(1, 1)] * center[1] - center[1];
280    undo_flip[(2, 3)] = undo_flip[(2, 2)] * center[2] - center[2];
281
282    undo_flip * undo_reorder
283}