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