Skip to main content

ndarray_ndimage/interpolation/
zoom_shift.rs

1use std::ops::{Add, Sub};
2
3use ndarray::{s, Array2, Array3, ArrayRef3, ArrayViewMut1, Zip};
4use num_traits::{FromPrimitive, Num, ToPrimitive};
5
6use crate::{array_like, pad, round_ties_even, spline_filter, BorderMode, PadMode};
7
8/// Shift an array.
9///
10/// The array is shifted using spline interpolation of the requested order. Points outside the
11/// boundaries of the input are filled according to the given mode.
12///
13/// * `data` - A 3D array of the data to shift.
14/// * `shift` - The shift along the axes.
15/// * `order` - The order of the spline.
16/// * `mode` - The mode parameter determines how the input array is extended beyond its boundaries.
17/// * `prefilter` - Determines if the input array is prefiltered with spline_filter before
18///   interpolation. The default is `true`, which will create a temporary `f64` array of filtered
19///   values if `order > 1`. If setting this to `false`, the output will be slightly blurred if
20///   `order > 1`, unless the input is prefiltered.
21pub fn shift<A>(
22    data: &ArrayRef3<A>,
23    shift: [f64; 3],
24    order: usize,
25    mode: BorderMode<A>,
26    prefilter: bool,
27) -> Array3<A>
28where
29    A: Copy + Num + FromPrimitive + PartialOrd + ToPrimitive,
30{
31    let dim = [data.dim().0, data.dim().1, data.dim().2];
32    let shift = shift.map(|s| -s);
33    run_zoom_shift(data, dim, [1.0, 1.0, 1.0], shift, order, mode, prefilter)
34}
35
36/// Zoom an array.
37///
38/// The array is zoomed using spline interpolation of the requested order.
39///
40/// * `data` - A 3D array of the data to zoom
41/// * `zoom` - The zoom factor along the axes.
42/// * `order` - The order of the spline.
43/// * `mode` - The mode parameter determines how the input array is extended beyond its boundaries.
44/// * `prefilter` - Determines if the input array is prefiltered with spline_filter before
45///   interpolation. The default is `true`, which will create a temporary `f64` array of filtered
46///   values if `order > 1`. If setting this to `false`, the output will be slightly blurred if
47///   `order > 1`, unless the input is prefiltered.
48pub fn zoom<A>(
49    data: &ArrayRef3<A>,
50    zoom: [f64; 3],
51    order: usize,
52    mode: BorderMode<A>,
53    prefilter: bool,
54) -> Array3<A>
55where
56    A: Copy + Num + FromPrimitive + PartialOrd + ToPrimitive,
57{
58    let mut o_dim = data.raw_dim();
59    for (ax, (&ax_len, zoom)) in data.shape().iter().zip(zoom.iter()).enumerate() {
60        o_dim[ax] = round_ties_even(ax_len as f64 * zoom) as usize;
61    }
62    let o_dim = [o_dim[0], o_dim[1], o_dim[2]];
63
64    let mut nom = data.raw_dim();
65    let mut div = o_dim.clone();
66    for ax in 0..data.ndim() {
67        nom[ax] -= 1;
68        div[ax] -= 1;
69    }
70    let zoom = [
71        nom[0] as f64 / div[0] as f64,
72        nom[1] as f64 / div[1] as f64,
73        nom[2] as f64 / div[2] as f64,
74    ];
75
76    run_zoom_shift(data, o_dim, zoom, [0.0, 0.0, 0.0], order, mode, prefilter)
77}
78
79fn run_zoom_shift<A>(
80    data: &ArrayRef3<A>,
81    odim: [usize; 3],
82    zooms: [f64; 3],
83    shifts: [f64; 3],
84    order: usize,
85    mode: BorderMode<A>,
86    prefilter: bool,
87) -> Array3<A>
88where
89    A: Copy + Num + FromPrimitive + PartialOrd + ToPrimitive,
90{
91    let idim = [data.dim().0, data.dim().1, data.dim().2];
92    let mut out = array_like(&data, odim, A::zero());
93    if prefilter && order > 1 {
94        // We need to allocate and work on filtered data
95        let (data, nb_prepad) = match mode {
96            BorderMode::Nearest => {
97                let padded = pad(data, &[[12, 12]], PadMode::Edge);
98                (spline_filter(&padded, order, mode), 12)
99            }
100            _ => (spline_filter(data, order, mode), 0),
101        };
102        let reslicer = ZoomShiftReslicer::new(idim, odim, zooms, shifts, order, mode, nb_prepad);
103        Zip::indexed(&mut out).for_each(|idx, o| {
104            *o = A::from_f64(reslicer.interpolate(&data, idx)).unwrap();
105        });
106    } else {
107        // We can use the &data as-is
108        let reslicer = ZoomShiftReslicer::new(idim, odim, zooms, shifts, order, mode, 0);
109        Zip::indexed(&mut out).for_each(|idx, o| {
110            *o = A::from_f64(reslicer.interpolate(data, idx)).unwrap();
111        });
112    }
113    out
114}
115
116/// Zoom shift transformation (only scaling and translation).
117struct ZoomShiftReslicer {
118    order: usize,
119    offsets: [Vec<isize>; 3],
120    edge_offsets: [Array2<isize>; 3],
121    is_edge_case: [Vec<bool>; 3],
122    splvals: [Array2<f64>; 3],
123    zeros: [Vec<bool>; 3],
124    cval: f64,
125}
126
127impl ZoomShiftReslicer {
128    /// Build all necessary data to call `interpolate`.
129    pub fn new<A>(
130        idim: [usize; 3],
131        odim: [usize; 3],
132        zooms: [f64; 3],
133        shifts: [f64; 3],
134        order: usize,
135        mode: BorderMode<A>,
136        nb_prepad: isize,
137    ) -> ZoomShiftReslicer
138    where
139        A: Copy + ToPrimitive,
140    {
141        let offsets = [vec![0; odim[0]], vec![0; odim[1]], vec![0; odim[2]]];
142        let is_edge_case = [vec![false; odim[0]], vec![false; odim[1]], vec![false; odim[2]]];
143        let (edge_offsets, splvals) = if order > 0 {
144            let dim0 = (odim[0], order + 1);
145            let dim1 = (odim[1], order + 1);
146            let dim2 = (odim[2], order + 1);
147            let e = [Array2::zeros(dim0), Array2::zeros(dim1), Array2::zeros(dim2)];
148            let s = [Array2::zeros(dim0), Array2::zeros(dim1), Array2::zeros(dim2)];
149            (e, s)
150        } else {
151            // We do not need to allocate when order == 0
152            let e = [Array2::zeros((0, 0)), Array2::zeros((0, 0)), Array2::zeros((0, 0))];
153            let s = [Array2::zeros((0, 0)), Array2::zeros((0, 0)), Array2::zeros((0, 0))];
154            (e, s)
155        };
156        let zeros = [vec![false; odim[0]], vec![false; odim[1]], vec![false; odim[2]]];
157        let cval = match mode {
158            BorderMode::Constant(cval) => cval.to_f64().unwrap(),
159            _ => 0.0,
160        };
161
162        let mut reslicer =
163            ZoomShiftReslicer { order, offsets, edge_offsets, is_edge_case, splvals, zeros, cval };
164        reslicer.build_arrays(idim, odim, zooms, shifts, order, mode, nb_prepad);
165        reslicer
166    }
167
168    fn build_arrays<A>(
169        &mut self,
170        idim: [usize; 3],
171        odim: [usize; 3],
172        zooms: [f64; 3],
173        shifts: [f64; 3],
174        order: usize,
175        mode: BorderMode<A>,
176        nb_prepad: isize,
177    ) where
178        A: Copy,
179    {
180        // Modes without an anlaytic prefilter or explicit prepadding use mirror extension
181        let spline_mode = match mode {
182            BorderMode::Constant(_) | BorderMode::Wrap => BorderMode::Mirror,
183            _ => mode,
184        };
185        let iorder = order as isize;
186        let idim = [
187            idim[0] as isize + 2 * nb_prepad,
188            idim[1] as isize + 2 * nb_prepad,
189            idim[2] as isize + 2 * nb_prepad,
190        ];
191        let nb_prepad = nb_prepad as f64;
192
193        for axis in 0..3 {
194            let splvals = &mut self.splvals[axis];
195            let offsets = &mut self.offsets[axis];
196            let edge_offsets = &mut self.edge_offsets[axis];
197            let is_edge_case = &mut self.is_edge_case[axis];
198            let zeros = &mut self.zeros[axis];
199            let len = idim[axis] as f64;
200            for from in 0..odim[axis] {
201                let mut to = (from as f64 + shifts[axis]) * zooms[axis] + nb_prepad;
202                match mode {
203                    BorderMode::Nearest => {}
204                    _ => to = map_coordinates(to, idim[axis] as f64, mode),
205                };
206                if to > -1.0 {
207                    if order > 0 {
208                        build_splines(to, &mut splvals.row_mut(from), order);
209                    }
210                    if order & 1 == 0 {
211                        to += 0.5;
212                    }
213
214                    let start = to.floor() as isize - iorder / 2;
215                    offsets[from] = start;
216                    if start < 0 || start + iorder >= idim[axis] {
217                        is_edge_case[from] = true;
218                        for o in 0..=order {
219                            let x = (start + o as isize) as f64;
220                            let idx = map_coordinates(x, len, spline_mode) as isize;
221                            edge_offsets[(from, o)] = idx - start;
222                        }
223                    }
224                } else {
225                    zeros[from] = true;
226                }
227            }
228        }
229    }
230
231    /// Spline interpolation with up-to 8 neighbors of a point.
232    pub fn interpolate<A>(&self, data: &ArrayRef3<A>, start: (usize, usize, usize)) -> f64
233    where
234        A: ToPrimitive + Add<Output = A> + Sub<Output = A> + Copy,
235    {
236        if self.zeros[0][start.0] || self.zeros[1][start.1] || self.zeros[2][start.2] {
237            return self.cval;
238        }
239
240        // Order = 0
241        // We do not want to go further because
242        // - it would be uselessly slower
243        // - self.splvals is empty so it would crash (although we could fill it with 1.0)
244        if self.edge_offsets[0].is_empty() {
245            let x = self.offsets[0][start.0] as usize;
246            let y = self.offsets[1][start.1] as usize;
247            let z = self.offsets[2][start.2] as usize;
248            return data[(x, y, z)].to_f64().unwrap();
249        }
250
251        // Linear interpolation use a nxnxn block. This is simple enough, but we must adjust this
252        // block when the `start` is near the edges.
253        let n = self.order + 1;
254        let valid_index = |original_offset, is_edge, start, d: usize, v| {
255            (original_offset + if is_edge { self.edge_offsets[d][(start, v)] } else { v as isize })
256                as usize
257        };
258
259        let original_offset_x = self.offsets[0][start.0];
260        let is_edge_x = self.is_edge_case[0][start.0];
261        let mut xs = [0; 6];
262        let original_offset_y = self.offsets[1][start.1];
263        let is_edge_y = self.is_edge_case[1][start.1];
264        let mut ys = [0; 6];
265        let original_offset_z = self.offsets[2][start.2];
266        let is_edge_z = self.is_edge_case[2][start.2];
267        let mut zs = [0; 6];
268        for i in 0..n {
269            xs[i] = valid_index(original_offset_x, is_edge_x, start.0, 0, i);
270            ys[i] = valid_index(original_offset_y, is_edge_y, start.1, 1, i);
271            zs[i] = valid_index(original_offset_z, is_edge_z, start.2, 2, i);
272        }
273
274        let mut t = 0.0;
275        for (z, &idx_z) in zs[..n].iter().enumerate() {
276            let spline_z = self.splvals[2][(start.2, z)];
277            for (y, &idx_y) in ys[..n].iter().enumerate() {
278                let spline_yz = self.splvals[1][(start.1, y)] * spline_z;
279                for (x, &idx_x) in xs[..n].iter().enumerate() {
280                    let spline_xyz = self.splvals[0][(start.0, x)] * spline_yz;
281                    t += data[(idx_x, idx_y, idx_z)].to_f64().unwrap() * spline_xyz;
282                }
283            }
284        }
285        t
286    }
287}
288
289fn build_splines(to: f64, spline: &mut ArrayViewMut1<f64>, order: usize) {
290    let x = to - if order & 1 == 1 { to } else { to + 0.5 }.floor();
291    match order {
292        1 => spline[0] = 1.0 - x,
293        2 => {
294            spline[0] = 0.5 * (0.5 - x).powi(2);
295            spline[1] = 0.75 - x * x;
296        }
297        3 => {
298            let z = 1.0 - x;
299            spline[0] = z * z * z / 6.0;
300            spline[1] = (x * x * (x - 2.0) * 3.0 + 4.0) / 6.0;
301            spline[2] = (z * z * (z - 2.0) * 3.0 + 4.0) / 6.0;
302        }
303        4 => {
304            let t = x * x;
305            let y = 1.0 + x;
306            let z = 1.0 - x;
307            spline[0] = (0.5 - x).powi(4) / 24.0;
308            spline[1] = y * (y * (y * (5.0 - y) / 6.0 - 1.25) + 5.0 / 24.0) + 55.0 / 96.0;
309            spline[2] = t * (t * 0.25 - 0.625) + 115.0 / 192.0;
310            spline[3] = z * (z * (z * (5.0 - z) / 6.0 - 1.25) + 5.0 / 24.0) + 55.0 / 96.0;
311        }
312        5 => {
313            let y = 1.0 - x;
314            let t = y * y;
315            spline[0] = y * t * t / 120.0;
316            let y = x + 1.0;
317            spline[1] = y * (y * (y * (y * (y / 24.0 - 0.375) + 1.25) - 1.75) + 0.625) + 0.425;
318            let t = x * x;
319            spline[2] = t * (t * (0.25 - x / 12.0) - 0.5) + 0.55;
320            let z = 1.0 - x;
321            let t = z * z;
322            spline[3] = t * (t * (0.25 - z / 12.0) - 0.5) + 0.55;
323            let z = z + 1.0;
324            spline[4] = z * (z * (z * (z * (z / 24.0 - 0.375) + 1.25) - 1.75) + 0.625) + 0.425;
325        }
326        _ => panic!("order must be between 1 and 5"),
327    }
328    spline[order] = 1.0 - spline.slice(s![..order]).sum();
329}
330
331fn map_coordinates<A>(mut idx: f64, len: f64, mode: BorderMode<A>) -> f64 {
332    match mode {
333        BorderMode::Constant(_) => {
334            if idx < 0.0 || idx >= len {
335                idx = -1.0;
336            }
337        }
338        BorderMode::Nearest => {
339            if idx < 0.0 {
340                idx = 0.0;
341            } else if idx >= len {
342                idx = len - 1.0;
343            }
344        }
345        BorderMode::Mirror => {
346            let s2 = 2.0 * len - 2.0;
347            if idx < 0.0 {
348                idx = s2 * (-idx / s2).floor() + idx;
349                idx = if idx <= 1.0 - len { idx + s2 } else { -idx };
350            } else if idx >= len {
351                idx -= s2 * (idx / s2).floor();
352                if idx >= len {
353                    idx = s2 - idx;
354                }
355            }
356        }
357        BorderMode::Reflect => {
358            let s2 = 2.0 * len;
359            if idx < 0.0 {
360                if idx < -s2 {
361                    idx = s2 * (-idx / s2).floor() + idx;
362                }
363                idx = if idx < -len { idx + s2 } else { -idx - 1.0 };
364            } else if idx >= len {
365                idx -= s2 * (idx / s2).floor();
366                if idx >= len {
367                    idx = s2 - idx - 1.0;
368                }
369            }
370        }
371        BorderMode::Wrap => {
372            let s = len - 1.0;
373            if idx < 0.0 {
374                idx += s * ((-idx / s).floor() + 1.0);
375            } else if idx >= len {
376                idx -= s * (idx / s).floor();
377            }
378        }
379    };
380    idx
381}