vspline 1.1.0
Generic C++11 Code for Uniform B-Splines
brace.h
Go to the documentation of this file.
1/************************************************************************/
2/* */
3/* vspline - a set of generic tools for creation and evaluation */
4/* of uniform b-splines */
5/* */
6/* Copyright 2015 - 2023 by Kay F. Jahnke */
7/* */
8/* The git repository for this software is at */
9/* */
10/* https://bitbucket.org/kfj/vspline */
11/* */
12/* Please direct questions, bug reports, and contributions to */
13/* */
14/* kfjahnke+vspline@gmail.com */
15/* */
16/* Permission is hereby granted, free of charge, to any person */
17/* obtaining a copy of this software and associated documentation */
18/* files (the "Software"), to deal in the Software without */
19/* restriction, including without limitation the rights to use, */
20/* copy, modify, merge, publish, distribute, sublicense, and/or */
21/* sell copies of the Software, and to permit persons to whom the */
22/* Software is furnished to do so, subject to the following */
23/* conditions: */
24/* */
25/* The above copyright notice and this permission notice shall be */
26/* included in all copies or substantial portions of the */
27/* Software. */
28/* */
29/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND */
30/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES */
31/* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND */
32/* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT */
33/* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
34/* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING */
35/* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR */
36/* OTHER DEALINGS IN THE SOFTWARE. */
37/* */
38/************************************************************************/
39
40/*! \file brace.h
41
42 \brief This file provides code for 'bracing' a b-spline's coefficient array.
43
44 Note that this isn't really user code, it's code used by class vspline::bspline.
45
46 Inspired by libeinspline, I wrote code to 'brace' the spline coefficients. The concept is
47 this: while the IIR filter used to calculate the coefficients has infinite support (though
48 arithmetic precision limits this in real-world applications), the evaluation of the spline
49 at a specific location only looks at a small window of coefficients (compact, finite support).
50 This fact can be exploited by taking note of how large the support area is and providing
51 a few more coefficients in a frame around the 'core' coefficients to allow the evaluation
52 to proceed without having to check for boundary conditions. While the difference is not
53 excessive (the main computational cost is the actual evaluation itself), it's still
54 nice to be able to code the evaluation without boundary checking, which makes the code
55 very straightforward and legible.
56
57 There is another aspect to bracing: In my implementation of vectorized evaluation,
58 the window into the coefficient array used to pick out coefficients to evaluate at
59 a specific location is coded as a set of offsets from it's center. This way,
60 several such windows can be processed in parallel. This mechanism can only function
61 efficiently in a braced coefficient array, since it would otherwise have to give up
62 if any of the windows accessed by the vector of coordinates had members outside the
63 (unbraced) coefficient array and submit the coordinate vector to individual processing.
64 I consider the logic to code this and the loss in performance too much of a bother
65 to go down this path; all my evaluation code uses braced coefficient arrays. Of course
66 the user is free to omit bracing, but then they have to use their own evaluation
67 code.
68
69 What's in the brace? Of course this depends on the boundary conditions chosen.
70 In vspline, I offer code for several boundary conditions, but most have something
71 in common: the original, finite sequence is extrapolated into an infinite periodic
72 signal. With straight PERIODIC boundary conditions, the initial sequence is
73 immediately followed and preceded by copies of itself. The other boundary conditions
74 mirror the signal in some way and then repeat the mirrored signal periodically.
75 Using boundary conditions like these, both the extrapolated signal and the
76 coefficients share the same periodicity and mirroring. There is one exception:
77 'natural' boundary conditions use point-mirroring on the bounds. With this extrapolation,
78 the extrapolated value can *not* be obtained by a coordinate manipulation. The method
79 of bracing the spline does still function, though. So with bracing, we can provide
80 b-splines with 'natural' boundary conditions as well, but here we are limited to
81 evaluation inside the spline's defined range.
82
83 There are two ways of arriving at a coeffcient array: We can start from the
84 extrapolated signal, pick a section large enough to make margin effects vanish
85 (due to limited arithmetic precision), prefilter it and pick out a subsection containing
86 the 'core' coefficients and their support. Alternatively, we can work only on the core
87 coefficients, calculate suitable initial causal and anticausal coeffcients (where the
88 calculation considers the extrapolated signal, which remains implicit), and apply the
89 filter with these known initial coefficients. vspline uses the latter approach.
90 Once the 'core' coefficients are known, the brace is filled.
91
92 The bracing can be performed without any solver-related maths by simply copying
93 (possibly trivially modified) slices of the core coefficients to the margin area.
94
95 Since the bracing mainly requires copying data or trivial maths we can do the operations
96 on higher-dimensional objects, like slices of a volume. To efficiently code these operations
97 we make use of vigra's multi-math facility and it's bindAt array method, which makes
98 these subarrays easily available.
99*/
100
101
102// TODO: while this is convenient, it's not too fast, as it's neither multithreaded nor
103// vectorized. Still in most 'normal' scenarios the execution time is negligible, and since
104// the code is trivial, it autovectorizes well.
105//
106// TODO: there are 'pathological' cases where one brace is larger than the other brace
107// and the width of the core together. These cases can't be handled for all bracing modes
108// and will result in an exception.
109
110#ifndef VSPLINE_BRACE_H
111#define VSPLINE_BRACE_H
112
113#include "common.h"
114
115namespace vspline {
116
117/// class bracer encodes the entire bracing process. Note that contrary
118/// to my initial implementation, class bracer is now used exclusively
119/// for populating the frame around a core area of data. It has no code
120/// to determine which size a brace/frame should have. This is now
121/// determined in class bspline, see especially class bspline's methods
122/// get_left_brace_size(), get_right_brace_size() and setup_metrics().
123
124// KFJ 2018-03-07 cosmetics: changed the template argument list to take
125// template arguments to the MultiArrayView instead of the view's type
126// itself, to narrow the range of acceptable input to MultiArrayViews
127// or types which can be converted to them, and make sure that inside
128// class bracer we only work on MultiArrayViews.
129
130template <unsigned int _dimension , typename _value_type >
131struct bracer
132{
133 typedef _value_type value_type ;
134 enum { dimension = _dimension } ;
135 typedef vigra::MultiArrayView < dimension , value_type > view_type ;
136 typedef typename view_type::difference_type shape_type ;
137
138/// apply the bracing to the array, performing the required copy/arithmetic operations
139/// to the 'frame' around the core. This routine performs the operation along axis 'dim'.
140/// This is also the routine to be used for explicitly extrapolating a signal:
141/// you place the data into the center of a larger array, and pass in the sizes of the
142/// 'empty' space which is to be filled with the extrapolated data.
143///
144/// the bracing is done one-left-one-right, to avoid corner cases as best as posible.
145/// This makes it possible to have signals which are shorter than the brace and still
146/// produce a correct brace for them.
147
148 static void apply ( view_type & a , // containing array
149 bc_code bc , // boundary condition code
150 int lsz , // space to the left which needs to be filled
151 int rsz , // ditto, to the right
152 int axis ) // axis along which to apply bracing
153 {
154 int w = a.shape ( axis ) ; // width of containing array along axis 'axis'
155 int m = w - ( lsz + rsz ) ; // width of 'core' array
156
157 if ( m < 1 ) // has to be at least 1
158 throw shape_mismatch ( "combined brace sizes must be at least one less than container size" ) ;
159
160 if ( m == 1 )
161 {
162 // KFJ 2018-02-10
163 // special case: the core has only shape 1 in this direction.
164 // if this is so, we fill the brace with the single slice in the
165 // middle, no matter what the boundary condition code may say.
166
167 for ( int s = 0 ; s < w ; s++ )
168 {
169 if ( s != lsz )
170 a.bindAt ( axis , s ) = a.bindAt ( axis , lsz ) ;
171 }
172 return ;
173 }
174
175 if ( ( lsz > m + rsz )
176 || ( rsz > m + lsz ) )
177 {
178 // not enough data to fill brace
179 if ( bc == PERIODIC || bc == NATURAL || bc == MIRROR || bc == REFLECT )
180 throw std::out_of_range ( "each brace must be smaller than the sum of it's opposite brace and the core's width" ) ;
181 }
182
183 int l0 = lsz - 1 ; // index of innermost empty slice on the left; like begin()
184 int r0 = lsz + m ; // ditto, on the right
185
186 int lp = l0 + 1 ; // index of leftmost occupied slice (p for pivot)
187 int rp = r0 - 1 ; // index of rightmost occupied slice
188
189 int l1 = -1 ; // index one before outermost empty slice to the left
190 int r1 = w ; // index one after outermost empty slice on the right; like end()
191
192 int lt = l0 ; // index to left target slice
193 int rt = r0 ; // index to right target slice ;
194
195 int ls , rs ; // indices to left and right source slice, will be set below
196
197 int ds = 1 ; // step for source index, +1 == forẃard, used for all mirroring modes
198 // for periodic bracing, it's set to -1.
199
200 switch ( bc )
201 {
202 case PERIODIC :
203 {
204 ls = l0 + m ;
205 rs = r0 - m ;
206 ds = -1 ; // step through source in reverse direction
207 break ;
208 }
209 case NATURAL :
210 case MIRROR :
211 {
212 ls = l0 + 2 ;
213 rs = r0 - 2 ;
214 break ;
215 }
216 case CONSTANT :
217 case REFLECT :
218 {
219 ls = l0 + 1 ;
220 rs = r0 - 1 ;
221 break ;
222 }
223 case ZEROPAD :
224 {
225 break ;
226 }
227 default:
228 {
229 throw not_supported
230 ( "boundary condition not supported by vspline::bracer" ) ;
231 break ;
232 }
233 }
234
235 for ( int i = max ( lsz , rsz ) ; i > 0 ; --i )
236 {
237 if ( lt > l1 )
238 {
239 switch ( bc )
240 {
241 case PERIODIC :
242 case MIRROR :
243 case REFLECT :
244 {
245 // with these three bracing modes, we simply copy from source to target
246 a.bindAt ( axis , lt ) = a.bindAt ( axis , ls ) ;
247 break ;
248 }
249 case NATURAL :
250 {
251 // here, we subtract the source slice from twice the 'pivot'
252 // easiest would be:
253 // a.bindAt ( axis , lt ) = a.bindAt ( axis , lp ) * value_type(2) - a.bindAt ( axis , ls ) ;
254 // but this fails in 1D TODO: why?
255 auto target = a.bindAt ( axis , lt ) ; // get a view to left target slice
256 target = a.bindAt ( axis , lp ) ; // assign value of left pivot slice
257 target *= value_type(2) ; // double that
258 target -= a.bindAt ( axis , ls ) ; // subtract left source slice
259 break ;
260 }
261 case CONSTANT :
262 {
263 // here, we repeat the 'pivot' slice
264 a.bindAt ( axis , lt ) = a.bindAt ( axis , lp ) ;
265 break ;
266 }
267 case ZEROPAD :
268 {
269 // fill with 0
270 a.bindAt ( axis , lt ) = value_type() ;
271 break ;
272 }
273 default :
274 // default: leave untouched
275 break ;
276 }
277 --lt ;
278 ls += ds ;
279 }
280 if ( rt < r1 )
281 {
282 // essentially the same, but with rs instead of ls, etc.
283 switch ( bc )
284 {
285 case PERIODIC :
286 case MIRROR :
287 case REFLECT :
288 {
289 // with these three bracing modes, we simply copy from source to target
290 a.bindAt ( axis , rt ) = a.bindAt ( axis , rs ) ;
291 break ;
292 }
293 case NATURAL :
294 {
295 // here, we subtract the source slice from twice the 'pivot'
296 // the easiest would be:
297 // a.bindAt ( axis , rt ) = a.bindAt ( axis , rp ) * value_type(2) - a.bindAt ( axis , rs ) ;
298 // but this fails in 1D TODO: why?
299 auto target = a.bindAt ( axis , rt ) ; // get a view to right targte slice
300 target = a.bindAt ( axis , rp ) ; // assign value of pivot slice
301 target *= value_type(2) ; // double that
302 target -= a.bindAt ( axis , rs ) ; // subtract source slice
303 break ;
304 }
305 case CONSTANT :
306 {
307 // here, we repeat the 'pivot' slice
308 a.bindAt ( axis , rt ) = a.bindAt ( axis , rp ) ;
309 break ;
310 }
311 case ZEROPAD :
312 {
313 // fill with 0
314 a.bindAt ( axis , rt ) = value_type() ;
315 break ;
316 }
317 default :
318 // default: leave untouched
319 break ;
320 }
321 ++rt ;
322 rs -= ds ;
323 }
324 }
325 }
326
327 /// This overload of 'apply' braces along all axes in one go.
328
329 static void apply
330 ( view_type& a , // target array, containing the core and (empty) frame
331 vigra::TinyVector < bc_code , dimension > bcv , // boundary condition codes
332 shape_type left_corner , // sizes of left braces
333 shape_type right_corner ) // sizes of right braces
334 {
335 for ( int dim = 0 ; dim < dimension ; dim++ )
336 apply ( a , bcv[dim] , left_corner[dim] , right_corner[dim] , dim ) ;
337 }
338} ;
339
340} ; // end of namespace vspline
341
342#endif // VSPLINE_BRACE_H
definitions common to all files in this project, utility code
Definition: basis.h:79
bc_code
This enumeration is used for codes connected to boundary conditions. There are two aspects to boundar...
Definition: common.h:71
@ CONSTANT
Definition: common.h:76
@ NATURAL
Definition: common.h:75
@ REFLECT
Definition: common.h:74
@ PERIODIC
Definition: common.h:73
@ MIRROR
Definition: common.h:72
@ ZEROPAD
Definition: common.h:77
class bracer encodes the entire bracing process. Note that contrary to my initial implementation,...
Definition: brace.h:132
static void apply(view_type &a, vigra::TinyVector< bc_code, dimension > bcv, shape_type left_corner, shape_type right_corner)
This overload of 'apply' braces along all axes in one go.
Definition: brace.h:330
view_type::difference_type shape_type
Definition: brace.h:136
static void apply(view_type &a, bc_code bc, int lsz, int rsz, int axis)
apply the bracing to the array, performing the required copy/arithmetic operations to the 'frame' aro...
Definition: brace.h:148
vigra::MultiArrayView< dimension, value_type > view_type
Definition: brace.h:135
_value_type value_type
Definition: brace.h:133
exception which is thrown if an opertion is requested which vspline does not support
Definition: common.h:307
shape mismatch is the exception which is thrown if the shapes of an input array and an output array d...
Definition: common.h:297