1 #include "ni_support.h"
2 #include "ni_splines.h"
3 #include <math.h>
4 
5 
6 int
get_spline_interpolation_weights(double x,int order,double * weights)7 get_spline_interpolation_weights(double x, int order, double *weights)
8 {
9     int i;
10     double y, z, t;
11 
12     /* Convert x to the delta to the middle knot. */
13     x -= floor(order & 1 ? x : x + 0.5);
14     y = x;
15     z = 1.0 - x;
16 
17     switch (order) {
18         case 1:
19             /* 0 <= x < 1*/
20             weights[0] = 1.0 - x;
21             break;
22         case 2:
23             /* -0.5 < x <= 0.5 */
24             weights[1] = 0.75 - x * x;
25             /* For weights[0] we'd normally do:
26              *
27              *   y = 1 + x  # 0.5 < y <= 1.5
28              *   yy = 1.5 - y  # yy = 0.5 - x
29              *   weights[0] = 0.5 * yy * yy
30              *
31              * So we set y = 0.5 - x directly instead.
32              */
33             y = 0.5 - x;
34             weights[0] = 0.5 * y * y;
35             break;
36         case 3:
37             /* y = x, 0 <= y < 1 */
38             weights[1] = (y * y * (y - 2.0) * 3.0 + 4.0) / 6.0;
39             /* z = 1 - x, 0 < z <= 1 */
40             weights[2] = (z * z * (z - 2.0) * 3.0 + 4.0) / 6.0;
41             /*
42              * For weights[0] we would normally do:
43              *
44              *   y += 1.0  # y = 1.0 + x, 1 <= y < 2
45              *   yy = 2.0 - y  # yy = 1 - x
46              *   weights[0] = yy * yy * yy / 6.0
47              *
48              * But we already have yy in z.
49              */
50             weights[0] = z * z * z / 6.0;
51             break;
52         case 4:
53             /* -0.5 < x <= 0.5 */
54             t = x * x;
55             weights[2] = t * (t * 0.25 - 0.625) + 115.0 / 192.0;
56             /* y = 1 + x, 0.5 < y <= 1.5 */
57             y = 1.0 + x;
58             weights[1] = y * (y * (y * (5.0 - y) / 6.0 - 1.25) + 5.0 / 24.0) +
59                          55.0 / 96.0;
60             /* z = 1 - x, 0.5 <= z < 1.5 */
61             weights[3] = z * (z * (z * (5.0 - z) / 6.0 - 1.25) + 5.0 / 24.0) +
62                          55.0 / 96.0;
63             /*
64              * For weights[0] we would normally do:
65              *
66              *   y += 1.0  # y = 2.0 + x, 1.5 <= y < 2.5
67              *   yy = 2.5 - y  # yy = 0.5 - x
68              *  weights[0] = yy**4 / 24.0
69              *
70              * So we set y = 0.5 - x directly instead.
71              */
72             y = 0.5 - x;
73             t = y * y;
74             weights[0] = t * t / 24.0;
75             break;
76         case 5:
77             /* y = x, 0 <= y < 1 */
78             t = y * y;
79             weights[2] = t * (t * (0.25 - y / 12.0) - 0.5) + 0.55;
80             /* z = 1 - x, 0 < z <= 1 */
81             t = z * z;
82             weights[3] = t * (t * (0.25 - z / 12.0) - 0.5) + 0.55;
83             /* y = 1 + x, 1 <= y < 2 */
84             y += 1.0;
85             weights[1] = y * (y * (y * (y * (y / 24.0 - 0.375) + 1.25) - 1.75)
86                               + 0.625) + 0.425;
87             /* z = 2 - x, 1 < z <= 2 */
88             z += 1.0;
89             weights[4] = z * (z * (z * (z * (z / 24.0 - 0.375) + 1.25) - 1.75)
90                               + 0.625) + 0.425;
91             /* For weights[0] we would normally do:
92              *
93              *   y += 1.0  # y = 2.0 + x, 2 <= y < 3
94              *   yy = 3.0 - y  # yy = 1.0 - x
95              *   weights[0] = yy**5 / 120.0
96              *
97              * So we set y = 2.0 - y = 1.0 - x directly instead.
98              */
99             y = 1.0 - x;
100             t = y * y;
101             weights[0] = y * t * t / 120.0;
102             break;
103         default:
104             return 1; /* Unsupported spline order. */
105     }
106 
107     /* All interpolation weights add to 1.0, so use it for the last one. */
108     weights[order] = 1.0;
109     for (i = 0; i < order; ++i) {
110         weights[order] -= weights[i];
111     }
112 
113     return 0;
114 }
115 
116 
117 int
get_filter_poles(int order,int * npoles,double * poles)118 get_filter_poles(int order, int *npoles, double *poles)
119 {
120     *npoles = order / 2;
121     /*
122      * If this assert is triggered, someone added more orders here but
123      * MAX_SPLIINE_FILTER_POLES was not kept in sync.
124      */
125     assert(*npoles <= MAX_SPLINE_FILTER_POLES);
126 
127     switch (order) {
128         case 2:
129             /* sqrt(8.0) - 3.0 */
130             poles[0] = -0.171572875253809902396622551580603843;
131             break;
132         case 3:
133             /* sqrt(3.0) - 2.0 */
134             poles[0] = -0.267949192431122706472553658494127633;
135             break;
136         case 4:
137             /* sqrt(664.0 - sqrt(438976.0)) + sqrt(304.0) - 19.0 */
138             poles[0] = -0.361341225900220177092212841325675255;
139             /* sqrt(664.0 + sqrt(438976.0)) - sqrt(304.0) - 19.0 */
140             poles[1] = -0.013725429297339121360331226939128204;
141             break;
142         case 5:
143             /* sqrt(67.5 - sqrt(4436.25)) + sqrt(26.25) - 6.5 */
144             poles[0] = -0.430575347099973791851434783493520110;
145             /* sqrt(67.5 + sqrt(4436.25)) - sqrt(26.25) - 6.5 */
146             poles[1] = -0.043096288203264653822712376822550182;
147             break;
148         default:
149             return 1; /* Unsupported order. */
150     };
151 
152     return 0;
153 }
154 
155 
156 typedef void (init_fn)(npy_double*, const npy_intp, const double);
157 
158 
159 static void
_init_causal_mirror(double * c,const npy_intp n,const double z)160 _init_causal_mirror(double *c, const npy_intp n, const double z)
161 {
162     npy_intp i;
163     double z_i = z;
164     const double z_n_1 = pow(z, n - 1);
165 
166     c[0] = c[0] + z_n_1 * c[n - 1];
167     for (i = 1; i < n - 1; ++i) {
168         c[0] += z_i * (c[i] + z_n_1 * c[n - 1 - i]);
169         z_i *= z;
170     }
171     c[0] /= 1 - z_n_1 * z_n_1;
172 }
173 
174 
175 static void
_init_anticausal_mirror(double * c,const npy_intp n,const double z)176 _init_anticausal_mirror(double *c, const npy_intp n, const double z)
177 {
178     c[n - 1] = (z * c[n - 2] + c[n - 1]) * z / (z * z - 1);
179 }
180 
181 
182 static void
_init_causal_wrap(double * c,const npy_intp n,const double z)183 _init_causal_wrap(double *c, const npy_intp n, const double z)
184 {
185     npy_intp i;
186     double z_i = z;
187 
188     for (i = 1; i < n; ++i) {
189         c[0] += z_i * c[n - i];
190         z_i *= z;
191     }
192     c[0] /= 1 - z_i; /* z_i = pow(z, n) */
193 }
194 
195 
196 static void
_init_anticausal_wrap(double * c,const npy_intp n,const double z)197 _init_anticausal_wrap(double *c, const npy_intp n, const double z)
198 {
199     npy_intp i;
200     double z_i = z;
201 
202     for (i = 0; i < n - 1; ++i) {
203         c[n - 1] += z_i * c[i];
204         z_i *= z;
205     }
206     c[n - 1] *= z / (z_i - 1); /* z_i = pow(z, n) */
207 }
208 
209 
210 static void
_init_causal_reflect(double * c,const npy_intp n,const double z)211 _init_causal_reflect(double *c, const npy_intp n, const double z)
212 {
213     npy_intp i;
214     double z_i = z;
215     const double z_n = pow(z, n);
216     const double c0 = c[0];
217 
218     c[0] = c[0] + z_n * c[n - 1];
219     for (i = 1; i < n; ++i) {
220         c[0] += z_i * (c[i] + z_n * c[n - 1 - i]);
221         z_i *= z;
222     }
223     c[0] *= z / (1 - z_n * z_n);
224     c[0] += c0;
225 }
226 
227 
228 void
_init_anticausal_reflect(double * c,const npy_intp n,const double z)229 _init_anticausal_reflect(double *c, const npy_intp n, const double z)
230 {
231     c[n - 1] *= z / (z - 1);
232 }
233 
234 
235 /*
236  * The application of the filter is performed in two passes over the
237  * coefficient array, one forward and the other backward. For a
238  * detailed discussion of the method see e.g.:
239  *
240  * Unser, Michael, Akram Aldroubi, and Murray Eden. "Fast B-spline
241  * transforms for continuous image representation and interpolation."
242  * IEEE Transactions on pattern analysis and machine intelligence 13.3
243  * (1991): 277-285.
244  *
245  * A key part of the process is initializing the first coefficient for
246  * each pass, which depends on the boundary conditions chosen to extend
247  * the input image beyond its boundaries. The method to initialize these
248  * values for the NI_EXTEND_MIRROR mode is discussed in the above paper.
249  * For NI_EXTEND_WRAP and NI_EXTEND_REFLECT, the unpublished method was
250  * obtained from a private communication with Dr. Philippe Thévenaz.
251  */
252 static void
_apply_filter(double * c,npy_intp n,double z,init_fn * causal_init,init_fn * anticausal_init)253 _apply_filter(double *c, npy_intp n, double z, init_fn *causal_init,
254               init_fn *anticausal_init)
255 {
256     npy_intp i;
257 
258     causal_init(c, n, z);
259     for (i = 1; i < n; ++i) {
260         c[i] += z * c[i - 1];
261     }
262     anticausal_init(c, n, z);
263     for (i = n - 2; i >= 0; --i) {
264         c[i] = z * (c[i + 1] - c[i]);
265     }
266 }
267 
268 
269 static void
_apply_filter_gain(double * c,npy_intp n,const double * zs,int nz)270 _apply_filter_gain(double *c, npy_intp n, const double *zs, int nz)
271 {
272     double gain = 1.0;
273 
274     while (nz--) {
275         const double z = *zs++;
276         gain *= (1.0 - z) * (1.0 - 1.0 / z);
277     }
278 
279     while (n--) {
280         *c++ *= gain;
281     }
282 }
283 
284 
285 void
apply_filter(double * coefficients,const npy_intp len,const double * poles,int npoles,NI_ExtendMode mode)286 apply_filter(double *coefficients, const npy_intp len, const double *poles,
287              int npoles, NI_ExtendMode mode)
288 {
289     init_fn *causal = NULL;
290     init_fn *anticausal = NULL;
291 
292     //Note: This switch statement should match the settings used for
293 	//      the spline_mode variable in NI_GeometricTransform
294     switch(mode) {
295         case NI_EXTEND_GRID_CONSTANT:
296         case NI_EXTEND_CONSTANT:
297         case NI_EXTEND_MIRROR:
298         case NI_EXTEND_WRAP:
299             causal = &_init_causal_mirror;
300             anticausal = &_init_anticausal_mirror;
301             break;
302         case NI_EXTEND_GRID_WRAP:
303             causal = &_init_causal_wrap;
304             anticausal = &_init_anticausal_wrap;
305             break;
306         case NI_EXTEND_NEAREST:
307         case NI_EXTEND_REFLECT:
308             causal = &_init_causal_reflect;
309             anticausal = &_init_anticausal_reflect;
310             break;
311         default:
312             assert(0); /* We should never get here. */
313     }
314 
315     _apply_filter_gain(coefficients, len, poles, npoles);
316 
317     while (npoles--) {
318         _apply_filter(coefficients, len, *poles++, causal, anticausal);
319     }
320 }
321