vspline 1.1.0
Generic C++11 Code for Uniform B-Splines
bspline.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 bspline.h
41
42 \brief defines class bspline
43
44 class bspline is an object to contain a b-spline's coefficients and some
45 metadata in one handy package. It also provides easy access to b-spline
46 prefiltering. The idea is that user code establishes a bspline object
47 representing the data at hand and then proceeds to create 'evaluators'
48 to evaluate the spline. You may be reminded of SciPy's bisplrep object,
49 and I admit that SciPy's bspline code has been one of my inspirations.
50
51 It attempts to do 'the right thing' by automatically creating suitable helper
52 objects and parametrization so that the spline does what it's supposed to do.
53 Most users will not need anything else, and using class bspline is quite
54 straightforward. It's quite possible to have a b-spline up and running with
55 a few lines of code without even having to make choices concerning it's
56 parametrization, since there are sensible defaults for everything. At the same
57 time, pretty much everything *can* be parametrized.
58
59 Note that class bspline does not provide evaluation of the spline. To evaluate,
60 objects of class evaluator (see eval.h) are used, which construct from a bspline
61 object and additional parameters, like, whether to calculate the spline's
62 value or it's derivative(s) or whether to use optimizations for special cases.
63
64 While using 'raw' coefficient arrays with an evaluation scheme which applies
65 boundary conditions is feasible and most memory-efficient, it's not so well
66 suited for very fast evaluation, since the boundary treatment needs conditionals,
67 and 'breaks' uniform access, which is especially detrimental when using
68 vectorization. So vspline uses coefficient arrays with a few extra coefficients
69 'framing' or 'bracing' the 'core' coefficients. Since evaluation of the spline
70 looks at a certain small section of coefficients (the evaluator's 'support'),
71 the bracing is chosen so that this lookup will always succeed without having to
72 consider boundary conditions: the brace is set up to make the boundary conditions
73 explicit, and the evaluation can proceed blindly without bounds checking. With
74 large coefficient arrays, the little extra space needed for the brace becomes
75 negligible, but the code for evaluation becomes faster and simpler.
76 In effect, 'bracing' is taken a little bit further than merely providing
77 enough extra coefficients to cover the support: additional coefficients are
78 produced to allow for the spline to be evaluated without bounds checking
79
80 - at the lower and upper limit of the spline's defined range
81
82 - and even slightly beyond those limits: a safeguard against quantization errors
83
84 This makes the code more robust: being very strict about the ends of the
85 defined range can easily result in quantization errors producing out-of-bounds
86 access to the coefficient array, so the slightly wider brace acts as a safeguard.
87 While the 'brace' has a specific size which depends on the parameters (see
88 get_left_brace_size() and get_right_brace_size()) - there may be even more
89 additional coefficients if this is needed (see parameter 'headroom'). All
90 additional coefficients around the core form the spline's 'frame'. So the
91 frame is always at least as large as the brace.
92 Adding the frame around the coefficient array uses extra memory.
93 For low spline dimensions, this does not 'cost much', but with rising
94 dimension, the extra coefficients of the frame take up more and more
95 space, and the method becomes impractical for splines with very many
96 dimensions, which you should keep at the back of your mind if you intend
97 to use vspline for higher-dimensional splines. This is due to the geometrical
98 fact that with rising dimensionality the 'surface' of an object becomes
99 proportionally larger, and the additional coefficients envelope the
100 surface of the coefficient array.
101
102 class bspline handles two views to the coefficients it operates on, these
103 are realized as vigra::MultiArrayViews, and they share the same storage:
104
105 - the 'container' is a view to all coefficients held by the spline, including
106 the extra coefficients in it's 'frame'.
107
108 - the 'core', is a view to a subarray of the container with precisely
109 the same shape as the knot point data over which the spline is calculated.
110 The coefficients in the core correspond 1:1 with the knot point data.
111
112 Probably the most common scenario is that the source data for the spline are
113 available from someplace like a file. Instead of reading the file's contents
114 into memory first and passing the memory to class bspline, there is a more
115 efficient way: a bspline object is set up first, with the specification of
116 the size of the incoming data and the intended mode of operation. The bspline
117 object allocates the memory it will need for the purpose, but doesn't do
118 anything else. The 'empty' bspline object is then 'filled' by the user by
119 putting data into it's 'core' area. Subsequently, prefilter() is called,
120 which converts the data to b-spline coefficients. This way, only one block
121 of memory is used throughout, the initial data are overwritten by the
122 coefficients, operation is in-place and most efficient. This method works
123 well with image import functions from vigraimpex: vigraimpex accepts
124 vigra::MultiArrayViews as target for image loading, so the 'core' of a
125 bspline object can be directly passed to vigraimpex to be filled with
126 image data.
127
128 If this pattern can't be followed, there are alternatives:
129
130 - if data are passed to prefilter(), they will be taken as containing the knot
131 point data, rather than expecting the knot point data to be in the bspline
132 oject's memory already. This can also be used to reuse a bspline object with
133 new data. The source data will not be modified.
134
135 - if a view to an array at least the size of the container array is passed into
136 bspline's constructor, this view is 'adopted' and all operations will use the
137 data it refers to. The caller is responsible for keeping these data alive while
138 the bspline object exists, and relinquishes control over the data, which may be
139 changed by the bspline object.
140
141*/
142
143#ifndef VSPLINE_BSPLINE_H
144#define VSPLINE_BSPLINE_H
145
146#include <limits>
147
148#include "prefilter.h"
149#include "brace.h"
150
151namespace vspline {
152
153/// struct bspline is the object in vspline holding b-spline coefficients.
154/// In a way, the b-spline 'is' it's coefficients, since it is totally
155/// determined by them - while, of course, the 'actual' spline is an
156/// n-dimensional curve. So, even if this is a bit sloppy, I often refer
157/// to the coefficients as 'the spline', and have named struct bspline
158/// so even if it just holds the coefficients.
159///
160/// The coefficients held in a bspline object are 'braced', providing a few
161/// extra extrapolated coefficients around a 'core' area corresponding with
162/// the knot point, or original data. This way, they can be used by vspline's
163/// evaluation code which relies on such a brace being present.
164///
165/// struct bspline is a convenience class which bundles a coefficient array
166/// (and it's creation) with a set of metadata describing the parameters used
167/// to create the coefficients and the resulting data. I have chosen to implement
168/// class bspline so that there is only a minimal set of template arguments,
169/// namely the spline's data type (like pixels etc.) and it's dimension. All
170/// other parameters relevant to the spline's creation are passed in at
171/// construction time. This way, if explicit specialization becomes necessary
172/// (like, to interface to code which can't use templates) the number of
173/// specializations remains manageable. This design decision pertains specifically
174/// to the spline's degree, which could also be implemented as a template argument,
175/// allowing for some optimization by making some members static. Yet going down
176/// this path requires explicit specialization for every spline degree used and
177/// the performance gain I found doing so was hardly measurable, while automatic
178/// testing became difficult and compilation times grew.
179///
180/// class bspline may or may not 'own' the coefficient data it refers to - this
181/// depends on the specific initialization used, but is handled privately by
182/// class b-spline, using a shared_ptr to the data if they are owned, which
183/// makes bspline objects trivially copyable.
184///
185/// The 'metadata part' of the bspline object is coded as a base class, which
186/// is convenient to derive other 'spline-like' classes with the same metadata
187/// structure. class bspline itself adds the coeffcients of the spline and
188/// the storage holding the coefficients.
189
190template < unsigned int _dimension >
192{
193 /// 'suck in' the template argument
194
195 enum { dimension = _dimension } ;
196
197 /// type of a multidimensional index type
198
199 typedef vigra::TinyVector < std::ptrdiff_t , dimension > shape_type ;
200
201 /// nD type for one boundary condition per axis
202
203 typedef typename vigra::TinyVector < bc_code , dimension > bcv_type ;
204
205 /// nD type for one limit condition per axis
206
207 typedef typename vigra::TinyVector < xlf_type , dimension > limit_type ;
208
209 // the metadata of the spline are fixed in the constructor and can't be
210 // modified later on:
211
212 const bcv_type bcv ; // boundary conditions, see common.h
213 const xlf_type tolerance ; // acceptable error
214
215 const shape_type left_frame ; // total width(s) of the left frame
216 const shape_type core_shape ; // shape of core array
217 const shape_type right_frame ; // total width(s) of the right frame
218 const shape_type container_shape ; // shape of core array
219
220 /// lower_limit returns the lower bound of the spline's defined range.
221 /// This is usually 0.0, but with REFLECT boundary condition it's -0.5,
222 /// the lower point of reflection. The lowest coordinate at which the
223 /// spline can be accessed may be lower: even splines have wider support,
224 /// and splines with extra headroom add even more room to manoevre.
225
226 static xlf_type lower_limit ( const bc_code & bc )
227 {
228 xlf_type limit = 0.0L ;
229
230 if ( bc == vspline::REFLECT )
231 limit = -0.5L ;
232
233 return limit ;
234 }
235
236 xlf_type lower_limit ( const int & axis ) const
237 {
238 return lower_limit ( bcv [ axis ] ) ;
239 }
240
242 {
243 limit_type limit ;
244 for ( int d = 0 ; d < dimension ; d++ )
245 limit[d] = lower_limit ( d ) ;
246 return limit ;
247 }
248
249 /// upper_limit returns the upper bound of the spline's defined range.
250 /// This is normally M - 1 if the shape for this axis is M. Splines with
251 /// REFLECT boundary condition use M - 0.5, the upper point of reflection,
252 /// and periodic splines use M. The highest coordinate at which the spline
253 /// may be accessed safely may be higher.
254
255 static xlf_type upper_limit ( const std::size_t & extent ,
256 const bc_code & bc )
257 {
258 xlf_type limit = extent - 1 ;
259
260 if ( bc == vspline::REFLECT )
261 limit += 0.5L ;
262 else if ( bc == vspline::PERIODIC )
263 limit += 1.0L ;
264
265 return limit ;
266 }
267
268 xlf_type upper_limit ( const int & axis ) const
269 {
270 xlf_type limit = core_shape [ axis ] - 1 ;
271
272 if ( bcv [ axis ] == vspline::REFLECT )
273 limit += 0.5L ;
274 else if ( bcv [ axis ] == vspline::PERIODIC )
275 limit += 1.0L ;
276
277 return limit ;
278 }
279
281 {
282 limit_type limit ;
283 for ( int d = 0 ; d < dimension ; d++ )
284 limit[d] = upper_limit ( d ) ;
285 return limit ;
286 }
287
288 /// get_left_brace_size and get_right_brace_size calculate the size of
289 /// the brace vspline puts around the 'core' coefficients to allow evaluation
290 /// inside the defined range (and even slightly beyond) without bounds
291 /// checking. These routines are static to allow user code to establish
292 /// vspline's bracing requirements without creating a bspline object.
293 /// user code might use this information to generate coefficient arrays
294 /// suitable for use with vspline evaluation code, sidestepping use of
295 /// a bspline object.
296
297 static shape_type get_left_brace_size ( int spline_degree , bcv_type bcv )
298 {
299 // we start out with left_brace as large as the support
300 // of the reconstruction kernel
301
302 int support = spline_degree / 2 ;
303 shape_type left_brace ( support ) ;
304
305 // for some situations, we extend the array further along a specific axis
306
307 for ( int d = 0 ; d < dimension ; d++ )
308 {
309 // If the spline is done with REFLECT boundary conditions,
310 // the lower and upper limits are between bounds.
311 // the lower limit in this case is -0.5. When using
312 // floor() or round() on this value, we receive -1,
313 // so we need to extend the left brace.
314 // if rounding could be done so that -0.5 is rounded
315 // towards zero, this brace increase could be omitted
316 // for even splines, but this would also bring operation
317 // close to dangerous terrain: an infinitesimal undershoot
318 // would already produce an out-of-bounds access.
319
320 if ( bcv[d] == vspline::REFLECT )
321 {
322 left_brace[d] ++ ;
323 }
324
325 // for other boundary conditions, the lower limit is 0. for odd
326 // splines, as long as evaluation is at positions >= 0 this is
327 // okay, but as soon as evaluation is tried with a value
328 // even just below 0, we'd have an out-of-bounds access,
329 // with potential memory fault. Rather than requiring
330 // evaluation to never undershoot, we stay on the side
331 // of caution and extend the left brace, so that
332 // quantization errors won't result in a crash.
333 // This is debatable and could be omitted, if it can
334 // be made certain that evaluation will never be tried
335 // at values even infinitesimally below zero.
336 // for even splines, this problem does not exist, since
337 // coordinate splitting is done with std::round.
338
339 else if ( spline_degree & 1 )
340 {
341 left_brace[d]++ ;
342 }
343 }
344 return left_brace ;
345 }
346
347 static shape_type get_right_brace_size ( int spline_degree , bcv_type bcv )
348 {
349 // we start out with right_brace as large as the support
350 // of the reconstruction kernel
351
352 int support = spline_degree / 2 ;
353 shape_type right_brace ( support ) ;
354
355 // for some situations, we extend the array further along a specific axis
356
357 for ( int d = 0 ; d < dimension ; d++ )
358 {
359 // If the spline is done with REFLECT boundary conditions,
360 // the lower and upper limits are between bounds.
361 // So the upper limit is Z + 0.5 where Z is integer.
362 // using floor on this value lands at Z, which is fine,
363 // but using round() (as is done for even splines)
364 // lands at Z+1, so for this case we need to extend
365 // the right brace. If we could use a rounding mode
366 // rounding towards zero, we could omit this extension,
367 // but we'd also be cutting it very fine. Again we stay
368 // on the side of caution.
369
370 if ( bcv[d] == vspline::REFLECT )
371 {
372 if ( ! ( spline_degree & 1 ) )
373 right_brace[d] ++ ;
374 }
375
376 // The upper limit is M-1 for most splines, and M-1+0.5 for
377 // splines with REFLECT BCs. When accessing the spline at
378 // this value, we'd be out of bounds.
379 // For splines done with REFLECT BCs, we have to extend the
380 // right brace to allow access to coordinates in [M-1,M-1+0.5],
381 // there is no other option.
382 // For other splines, We could require the evaluation code
383 // to check and split incoming values of M-1 to M-2, 1.0, but this
384 // would require additional inner-loop code. So we add another
385 // coefficient on the upper side for these as well.
386 // This is debatable, but with the current implementation of the
387 // evaluation it's necessary.
388
389 // So, staying on the side of caution, we add the extra coefficient
390 // for all odd splines.
391
392 if ( spline_degree & 1 )
393 {
394 right_brace[d]++ ;
395 }
396
397 // periodic splines need an extra coefficient on the upper
398 // side, to allow evaluation in [M-1,M]. This interval is
399 // implicit in the original data since the value at M is
400 // equal to the value at 0, but if we want to process without
401 // bounds checking and index manipulations, we must provide
402 // an extra coefficient.
403
404 if ( bcv[d] == vspline::PERIODIC )
405 {
406 right_brace[d]++ ;
407 }
408 }
409 return right_brace ;
410 }
411
412 /// convenience method to caculate the shape of a container array needed to hold
413 /// the coefficients of a spline with the given properties. The arguments are the
414 /// same as those passed to the bspline object's constructor, but this method is
415 /// static, so it can be called on the spline's type and does not need an object.
416 /// I'm including this to make it easier for code which creates the container
417 /// array externally before constructing the bspline object, rather than relying
418 /// on class bspline to allocate it's own storage.
419
420 static shape_type get_container_shape ( int spline_degree ,
421 bcv_type bcv ,
423 int headroom
424 )
425 {
426 auto left_frame = get_left_brace_size ( spline_degree , bcv ) ;
427 left_frame += headroom ;
428
429 auto right_frame = get_right_brace_size ( spline_degree , bcv ) ;
430 right_frame += headroom ;
431
433
434 return container_shape ;
435 }
436
437 /// variant for 1D splines. This variant accepts the shape as a plain long,
438 /// rather than requiring a TinyVector of one long.
439
440 template < typename = std::enable_if < _dimension == 1 > >
441 static long get_container_shape ( int spline_degree ,
443 long core_shape ,
444 int headroom
445 )
446 {
447 return get_container_shape ( spline_degree ,
448 bcv_type ( bc ) ,
450 headroom ) [ 0 ] ;
451 }
452
453 /// The constructor sets up all metrics of the spline from the basic
454 /// properties given for the spline. Here, no defaults are offered,
455 /// because this class is meant as a base class for splines only, and
456 /// the expectation is that no objects of this class will be created.
457
458 bspline_base ( shape_type _core_shape , // shape of knot point data
459 int _spline_degree , // spline degree
460 bcv_type _bcv , // boundary condition
461 xlf_type _tolerance , // acceptable error
462 int headroom
463 )
464 : core_shape ( _core_shape ) ,
465 left_frame ( get_left_brace_size ( _spline_degree , _bcv )
466 + headroom ) ,
467 right_frame ( get_right_brace_size ( _spline_degree , _bcv )
468 + headroom ) ,
469 container_shape ( get_container_shape ( _spline_degree ,
470 _bcv ,
471 _core_shape ,
472 headroom ) ) ,
473 bcv ( _bcv ) ,
474 tolerance ( _tolerance <= 0.0
475 ? std::numeric_limits < xlf_type > :: epsilon()
476 : _tolerance
477 )
478 { }
479
480} ;
481
482/// class bspline now builds on class bspline_base, adding coefficient storage,
483/// while bspline_base provides metadata handling. This separation makes it
484/// easier to generate classes which use the same metadata scheme. One concrete
485/// example is a class to type-erase a spline (not yet part of the library) which
486/// abstracts a spline, hiding the type of it's coefficients. Such a class can
487/// inherit from bspline_base and initialize the base class in the c'tor from
488/// a given bspline object, resulting in a uniform interface to the metadata.
489/// class bspline takes an additional template argument: the value type. This
490/// is the type of the coefficients stored in the spline, and it will typically
491/// be a single fundamental type or a small aggregate - like a vigra::TinyVector.
492/// vspline uses vigra's ExpandElementResult mechanism to inquire for a value
493/// type's elementary type and size, which makes it easy to adapt to new value
494/// types because the mechanism is traits-based.
495
496template < class _value_type , unsigned int _dimension >
498: public bspline_base < _dimension >
499{
500 typedef _value_type value_type ;
501
502 /// number of channels in value_type
503
504 enum { channels = ExpandElementResult < value_type >::size } ;
505
506 /// elementary type of value_type, like float or double
507
508 typedef typename ExpandElementResult < value_type >::type ele_type ;
509
511
512 /// inheritance between templates requires explicit coding of the use
513 /// of base class facilities, hence we have a large-ish section of using
514 /// declarations.
515
516 using typename base_type::shape_type ;
517 using typename base_type::bcv_type ;
518
524
531 using base_type::bcv ;
532
533 /// if the coefficients are owned, an array of this type holds the data:
534
535 typedef vigra::MultiArray < dimension , value_type > array_type ;
536
537 /// data are read and written to vigra MultiArrayViews:
538
539 typedef vigra::MultiArrayView < dimension , value_type > view_type ;
540
541 /// the type of 'this' bspline and of a 'channel view'
542
544
546
547private:
548
549 // _p_coeffs points to a vigra::MultiArray, which is either default-initialized
550 // and contains no data, or holds data which are viewed by 'container'. Using a
551 // std::shared_ptr here has the pleasant side-effect that class bspline objects
552 // can use the default copy and assignment operators without need to worry
553 // about array life-time: The last bspline object going out of scope will
554 // 'take the array with it'.
555 // Keep in mind, though, that bspline objects based on externally allocated
556 // storage (by passing _space in the c'tor) will merely copy the relevant
557 // pointers to the coeffcients (as member variables of the views 'core' and
558 // 'container') and rely on the storage being kept alive while the bspline
559 // object persists.
560
561 std::shared_ptr < array_type > _p_coeffs ;
562
563public:
564
565 view_type container ; // view to coefficient container array (incl. frame)
566 view_type core ; // view to the core part of the coefficient array
567
568 /// boolean flag indicating whether the coefficients were prefiltered or not.
569 /// This must be taken with a grain of salt - it's up to the user to decide
570 /// whether this flag can be trusted. vspline code will set it whenever
571 /// 'prefilter' is called, but since both this flag and the coefficients
572 /// themselves are open to manipulation, external code performing such
573 /// manipulations can decide to ignore the flag.
574 /// Initially, the flag is set to 'false', and vspline does not perform
575 /// prefiltering automatically, so it will remain false until prefilter
576 /// is called or until the user 'manually' sets it to true.
577
579
580 // the degree of the spline is not const, it can be modified to
581 // evaluate splines 'as if' the coefficients had been calculated
582 // for the given degree even if they were calculated for a
583 // different degree - in vspline this is called 'shifting'
584 // the spline. Use the member function shift() rather than
585 // modifying the value directly to avoid a result which won't
586 // compute due to insufficient frame size.
587
588 int spline_degree ; // degree of the spline (3 == cubic spline)
589
590 /// construct a bspline object with appropriate storage space to contain and process an array
591 /// of knot point data with shape _core_shape. Depending on the the other
592 /// parameters passed, more space than _core_shape may be allocated. Once the bspline object
593 /// is ready, usually it is filled with the knot point data and then the prefiltering needs
594 /// to be done. This sequence assures that the knot point data are present in memory only once,
595 /// the prefiltering is done in-place. So the user can create the bspline, fill in data (like,
596 /// from a file), prefilter, and then evaluate.
597 ///
598 /// alternatively, if the knot point data are already manifest elsewhere, they can be passed
599 /// to prefilter(). With this mode of operation, they are 'pulled in' during prefiltering.
600 ///
601 /// It's possible to pass in a view to an array providing space for the coefficients,
602 /// or even the coefficients themselves. This is done via the parameter _space. This has
603 /// to be an array of the same or larger shape than the container array would end up having
604 /// given all the other parameters. This view is then 'adopted' and subsequent processing
605 /// will operate on it's data.
606 ///
607 /// The additional parameter 'headroom' is used to make the 'frame' even wider. This is
608 /// needed if the spline is to be 'shifted' up (evaluated as if it had been prefiltered
609 /// with a higher-degree prefilter) - see shift(). The headroom goes on top of the brace.
610 ///
611 /// While bspline objects allow very specific parametrization, most use cases won't use
612 /// parameters beyond the first few. The only mandatory parameter is, obviously, the
613 /// shape of the knot point data, the original data which the spline is built over.
614 /// This shape 'returns' as the bspline object's 'core' shape. If this is the only
615 /// parameter passed to the constructor, the resulting bspline object will be a
616 /// cubic b-spline with mirror boundary conditions, allocating it's own storage for the
617 /// coefficients.
618 ///
619 /// Note that passing tolerance = 0.0 may increase prefiltering time significantly,
620 /// especially when prefiltering 1D splines, which can't use multithreaded and vectorized
621 /// code in this case. Really, tolerance 0 doesn't produce significantly better results
622 /// than the default, which is a very low value already. The tolerance 0 code is there
623 /// more for completeness' sake, as it actually produces the result of the prefilter
624 /// using the *formula* to calculate the initial causal and anticausal coefficient
625 /// precisely, whereas the small tolerance used by default is so small that it
626 /// roughly mirrors the arithmetic precision which can be achieved with the given
627 /// type, which leads to nearly the same initial coefficients. Oftentimes even the default
628 /// is too conservative - a 'reasonable' value is in the order of magnitude of the noise
629 /// in the signal you're processing. But using the default won't slow things down a great
630 /// deal, since it only results in the initial coefficients being calculated a bit less
631 /// quickly. With nD data, tolerance 0 is less of a problem because the operation will
632 /// still be multithreaded and vectorized.
633
634 bspline ( shape_type _core_shape , // shape of knot point data
635 int _spline_degree = 3 , // spline degree with reasonable default
636 bcv_type _bcv = bcv_type ( MIRROR ) , // boundary conditions and common default
637 xlf_type _tolerance = -1.0 , // acceptable error (-1: automatic)
638 int headroom = 0 , // additional headroom, for 'shifting'
639 view_type _space = view_type() // coefficient storage to 'adopt'
640 )
641 : base_type ( _core_shape ,
642 _spline_degree ,
643 _bcv ,
644 ( _tolerance < 0.0
645 ? std::numeric_limits < ele_type > :: epsilon()
646 : ( _tolerance == 0
647 ? std::numeric_limits < xlf_type > :: epsilon()
648 : _tolerance
649 )
650 ) ,
651 headroom ) ,
652 spline_degree ( _spline_degree ) ,
653 prefiltered ( false )
654 {
655 // now either adopt external memory or allocate memory for the coefficients
656
657 if ( _space.hasData() )
658 {
659 // caller has provided space for the coefficient array. This space
660 // has to be at least as large as the container_shape we have
661 // determined to make sure it's compatible with the other parameters.
662 // With the array having been provided by the caller, it's the caller's
663 // responsibility to keep the data alive as long as the bspline object
664 // is used to access them.
665
666 if ( ! ( allGreaterEqual ( _space.shape() , container_shape ) ) )
667 throw shape_mismatch ( "the intended container shape does not fit into the shape of the storage space passed in" ) ;
668
669 // if the shape matches, we adopt the data in _space.
670 // We take a view to the container_shape-sized subarray only.
671
672 container = _space.subarray ( shape_type() , container_shape ) ;
673
674 // _p_coeffs is made to point to a default-constructed MultiArray,
675 // which holds no data.
676
677 _p_coeffs = std::make_shared < array_type >() ;
678 }
679 else
680 {
681 // _space was default-constructed and has no data.
682 // in this case we allocate a container array and hold a shared_ptr
683 // to it. so we can copy bspline objects without having to worry about
684 // dangling pointers, or who destroys the array.
685
686 _p_coeffs = std::make_shared < array_type > ( container_shape ) ;
687
688 // 'container' is made to refer to a view to this array.
689
690 container = *_p_coeffs ;
691 }
692
693 // finally we set the view to the core area
694
695 core = container.subarray ( left_frame , left_frame + _core_shape ) ;
696 } ;
697
698 /// overloaded constructor for 1D splines. This is useful because if we don't
699 /// provide it, the caller would have to pass TinyVector < T , 1 > instead of T
700 /// for the shape and the boundary condition.
701
702 // KFJ 2018-07-23 Now I'm using enable_if to provide the following
703 // c'tor overload only if dimension == 1. This avoids an error when
704 // declaring explicit specializations: for dimension != 1, the compiler
705 // would try and create this c'tor overload, which mustn't happen.
706 // with the enable_if, if dimension != 1, the code is not considered.
707
708 template < typename = std::enable_if < _dimension == 1 > >
709 bspline ( long _core_shape , // shape of knot point data
710 int _spline_degree = 3 , // spline degree with reasonable default
711 bc_code _bc = MIRROR , // boundary conditions and common default
712 xlf_type _tolerance = -1.0 , // acceptable error (relative to unit pulse)
713 int headroom = 0 , // additional headroom, for 'shifting'
714 view_type _space = view_type() // coefficient storage to 'adopt'
715 )
716 : bspline ( vigra::TinyVector < long , 1 > ( _core_shape ) ,
717 _spline_degree ,
718 bcv_type ( _bc ) ,
719 _tolerance ,
720 headroom ,
721 _space
722 )
723 { } ;
724
725 /// get a bspline object for a single channel of the data. This is lightweight
726 /// and requires the viewed data to remain present as long as the channel view is used.
727 /// the channel view inherits all metrics from it's parent, only the MultiArrayViews
728 /// to the data are different.
729
730 channel_view_type get_channel_view ( const int & channel )
731 {
732 assert ( channel < channels ) ;
733
734 ele_type * base = (ele_type*) ( container.data() ) ;
735 base += channel ;
736 auto stride = container.stride() ;
737 stride *= channels ;
738
739 MultiArrayView < dimension , ele_type >
740 channel_container ( container.shape() , stride , base ) ;
741
742 // KFJ 2022-01-14 the channel view was created with headroom = 0, which
743 // is only correct if the 'mother' spline has headroom == 0. Now the
744 // 'mother' spline's headroom is calculated (because it's not stored as
745 // a member variable) as the difference between the actual left frame's
746 // size and the size as it would have been without additional headroom:
747
748 auto std_left_frame_size = get_left_brace_size ( spline_degree , bcv ) ;
749 auto headroom = left_frame[0] - std_left_frame_size[0] ;
750
751 return channel_view_type ( core.shape() ,
753 bcv ,
754 tolerance ,
755 headroom ,
756 channel_container // coefficient storage to 'adopt'
757 ) ;
758 } ;
759
760 /// if the spline coefficients are already known, they obviously don't need to be
761 /// prefiltered. But in order to be used by vspline's evaluation code, they need to
762 /// be 'braced' - the 'core' coefficients have to be surrounded by more coeffcients
763 /// covering the support the evaluator needs to operate without bounds checking
764 /// inside the spline's defined range. brace() performs this operation. brace()
765 /// assumes the bspline object has been set up with the desired initial parameters,
766 /// so that the boundary conditions and metrics are already known and storage is
767 /// available. brace() can be called for a specific axis, or for the whole container
768 /// by passing -1.
769 /// User code will only need to call 'brace' explicitly if the coefficients were
770 /// modified 'from the outside' - when 'prefilter' is called, 'brace' is called
771 /// automatically.
772
773 void brace ( int axis = -1 ) ///< specific axis, -1: all
774 {
775 if ( axis == -1 )
776 {
779 }
780 else
781 {
783 ( container , bcv[axis] ,
784 left_frame[axis] , right_frame[axis] , axis ) ;
785 }
786 }
787
788 /// prefilter converts the knot point data in the 'core' area into b-spline
789 /// coefficients. Bracing/framing will be applied. Even if the degree of the
790 /// spline is zero or one, prefilter() can be called because it also
791 /// performs the bracing.
792 /// the arithmetic of the prefilter is performed in 'math_ele_type', which
793 /// defaults to the vigra RealPromoted elementary type of the spline's
794 /// value_type. This default ensures that integral knot point data are
795 /// handled appropriately and prefiltering them will only suffer from
796 /// quantization errors, which may be acceptable if the dynamic range
797 /// is sufficiently large.
798 /// 'boost' is an additional factor which will be used to amplify the
799 /// incoming signal. This is intended for cases where the range of the
800 /// signal has to be widened to fill the dynamic range of the signal's
801 /// data type (specifically if it is an integral type). User code has
802 /// to deal with the effects of boosting the signal, the bspline object
803 /// holds no record of the 'boost' being applied. When evaluating a
804 /// spline with boosted coefficients, user code will have to provide
805 /// code to attenuate the resulting signal back into the original
806 /// range; for an easy way of doing so, see vspline::amplify_type,
807 /// which is a type derived from vspline::unary_functor providing
808 /// multiplication with a factor. There is an example of it's application
809 /// in the context of an integer-valued spline in int_spline.cc.
810
811 template < class math_ele_type
812 = typename vigra::NumericTraits < ele_type > :: RealPromote ,
813 size_t vsize
816 int njobs = vspline::default_njobs )
817 {
818 // we assume data are already in 'core' and we operate in-place
819 // prefilter first, passing in BC codes to pick out the appropriate functions to
820 // calculate initial causal and anticausal coefficient, then 'brace' result.
821 // note how, just as in brace(), the whole frame is filled, which may be more
822 // than is strictly needed by the evaluator.
823
825 value_type ,
826 value_type ,
827 math_ele_type ,
828 vsize >
829 ( core ,
830 core ,
831 bcv ,
833 tolerance ,
834 boost ,
835 njobs
836 ) ;
837
838 brace() ;
839 prefiltered = true ;
840 }
841
842 /// If data are passed in, they have to have precisely the shape
843 /// we have set up in core (_core_shape passed into the constructor).
844 /// These data will then be used in place of any data present in the
845 /// bspline object to calculate the coefficients. They won't be looked at
846 /// after prefilter() terminates, so it's safe to pass in a MultiArrayView
847 /// which is destroyed after the call to prefilter() returns. Any data
848 /// residing in the bspline object's memory will be overwritten.
849 /// here, the default math_ele_type ensures that math_ele_type is
850 /// appropriate for both T and ele_type.
851
852 template < typename T ,
853 typename math_ele_type
854 = typename vigra::NumericTraits
855 < typename vigra::PromoteTraits
856 < ele_type , ET<T> > :: Promote
857 > :: RealPromote ,
859 void prefilter ( const vigra::MultiArrayView < dimension , T > & data ,
861 int njobs = vspline::default_njobs
862 )
863 {
864 // if the user has passed in data, they have to have precisely the shape
865 // we have set up in core (_core_shape passed into the constructor).
866 // This can have surprising effects if the container array isn't owned by the
867 // spline but constitutes a view to data kept elsewhere (by passing _space the
868 // to constructor): the data held by whatever constructed the bspline object
869 // will be overwritten with the (prefiltered) data passed in via 'data'.
870 // Whatever data have been in the core will be overwritten.
871
872 if ( data.shape() != core.shape() )
873 throw shape_mismatch
874 ( "when passing data to prefilter, they have to have precisely the core's shape" ) ;
875
876 // prefilter first, passing in BC codes to pick out the appropriate functions to
877 // calculate initial causal and anticausal coefficient, then 'brace' result.
878 // note how, just as in brace(), the whole frame is filled, which may be more
879 // than is strictly needed by the evaluator.
880
882 T ,
883 value_type ,
884 math_ele_type ,
885 vsize >
886 ( data ,
887 core ,
888 bcv ,
890 tolerance ,
891 boost ,
892 njobs
893 ) ;
894
895 brace() ;
896 prefiltered = true ;
897 }
898
899 /// 'shift' will change the interpretation of the data in a bspline object.
900 /// d is taken as a difference to add to the current spline degree. Coefficients
901 /// remain the same, but creating an evaluator from the shifted spline will make
902 /// the evaluator produce data *as if* the coefficients were those of a spline
903 /// of the changed order. Shifting with positive d will efectively blur the
904 /// interpolated signal, shifting with negative d will sharpen it.
905 /// For shifting to work, the spline has to have enough 'headroom', meaning that
906 /// spline_degree + d, the new spline degree, has to be greater or equal to 0
907 /// and smaller than the largest supported spline degree (lower fourties) -
908 /// and, additionally, there has to be a wide-enough brace to allow evaluation
909 /// with the wider kernel of the higher-degree spline's reconstruction filter.
910 /// So if a spline is set up with degree 0 and shifted to degree 5, it has to be
911 /// constructed with an additional headroom of 3 (see the constructor).
912 ///
913 /// shiftable() is called with a desired change of spline_degree. If it
914 /// returns true, interpreting the data in the container array as coefficients
915 /// of a spline with the changed degree is safe. If not, the frame size is
916 /// not sufficient or the resulting degree is invalid and shiftable()
917 /// returns false. Note how the decision is merely technical: if the new
918 /// degree is okay and the *frame* is large enough, the shift will be
919 /// considered permissible.
920
921 // KFJ 2022-07-07 removed test for excession of max_degree: with alternative
922 // basis functions, the maximal spline degree is not necessarily an issue,
923 // so the 'strictly technical' criterion of sufficient frame size is now
924 // used exclusively.
925
926 bool shiftable ( int d ) const
927 {
928 int new_degree = spline_degree + d ;
929 if ( new_degree < 0 ) // || new_degree > vspline_constants::max_degree )
930 return false ;
931
932 shape_type new_left_brace = get_left_brace_size ( new_degree , bcv ) ;
933 shape_type new_right_brace = get_right_brace_size ( new_degree , bcv ) ;
934 if ( allLessEqual ( new_left_brace , left_frame )
935 && allLessEqual ( new_right_brace , right_frame ) )
936 {
937 return true ;
938 }
939
940 return false ;
941 }
942
943 /// shift() actually changes the interpretation of the data. The data
944 /// will be taken to be coefficients of a spline with degree
945 /// spline_degree + d, and the original degree is lost. This operation
946 /// is only performed if it is technically safe (see shiftable()).
947 /// If the shift was performed successfully, this function returns true,
948 /// false otherwise.
949 /// Note that, rather than 'shifting' the b-spline object, it's also
950 /// possible to use a 'shifted' evaluator to produce the same result.
951 /// See class evaluator's constructor.
952
953 bool shift ( int d )
954 {
955 if ( shiftable ( d ) )
956 {
957 spline_degree += d ;
958 return true ;
959 }
960 return false ;
961 }
962
963 /// helper function to pretty-print a bspline object to an ostream
964
965 friend std::ostream & operator<< ( std::ostream & osr , const bspline & bsp )
966 {
967 osr << "dimension:................... " << bsp.dimension << std::endl ;
968 osr << "degree:...................... " << bsp.spline_degree << std::endl ;
969 osr << "boundary conditions:......... " ;
970 for ( auto bc : bsp.bcv )
971 osr << " " << bc_name [ bc ] ;
972 osr << std::endl ;
973 osr << "shape of container array:.... " << bsp.container.shape() << std::endl ;
974 osr << "shape of core:............... " << bsp.core.shape() << std::endl ;
975 osr << "left frame:.................. " << bsp.left_frame << std::endl ;
976 osr << "right frame:................. " << bsp.right_frame << std::endl ;
977 osr << "ownership of data:........... " ;
978 osr << ( bsp._p_coeffs->hasData()
979 ? "bspline object owns data"
980 : "data are owned externally" ) << std::endl ;
981 osr << "container base adress:....... " << bsp.container.data() << std::endl ;
982 osr << "core base adress:............ " << bsp.core.data() << std::endl ;
983 osr << "prefiltered:................. "
984 << ( bsp.prefiltered ? "yes" : "no" ) << std::endl ;
985 return osr ;
986 }
987
988} ;
989
990/// using declaration for a coordinate suitable for bspline, given
991/// elementary type rc_type. This produces the elementary type itself
992/// if the spline is 1D, a TinyVector of rc_type otherwise.
993
994template < class spline_type , typename rc_type >
997
998/// using declaration for a bspline's value type
999
1000template < class spline_type >
1002= typename spline_type::value_type ;
1003
1004} ; // end of namespace vspline
1005
1006#endif // VSPLINE_BSPLINE_H
This file provides code for 'bracing' a b-spline's coefficient array.
@ vsize
Definition: eval.cc:96
Definition: basis.h:79
void prefilter(const vigra::MultiArrayView< dimension, in_value_type > &input, vigra::MultiArrayView< dimension, out_value_type > &output, vigra::TinyVector< bc_code, static_cast< int >(dimension) > bcv, int degree, xlf_type tolerance=std::numeric_limits< math_ele_type > ::epsilon(), xlf_type boost=xlf_type(1), int njobs=default_njobs)
'prefilter' handles b-spline prefiltering for the whole range of acceptable input and output....
Definition: prefilter.h:1082
typename vigra::ExpandElementResult< T > ::type ET
using definition for the 'elementary type' of a type via vigra's ExpandElementResult mechanism....
Definition: common.h:110
void apply(const unary_functor_type &ev, vigra::MultiArrayView< dimension, typename unary_functor_type::out_type > &output, int njobs=vspline::default_njobs, vspline::atomic< bool > *p_cancel=0)
we code 'apply' as a special variant of 'transform' where the output is also used as input,...
Definition: transform.h:531
vspline::canonical_type< rc_type, spline_type::dimension > bspl_coordinate_type
using declaration for a coordinate suitable for bspline, given elementary type rc_type....
Definition: bspline.h:996
const int default_njobs
Definition: multithread.h:220
typename std::conditional< N==1, ET< T >, vigra::TinyVector< ET< T >, N > > ::type canonical_type
produce the 'canonical type' for a given type T. If T is single-channel, this will be the elementary ...
Definition: common.h:225
long double xlf_type
Definition: common.h:102
const std::string bc_name[]
bc_name is for diagnostic output of bc codes
Definition: common.h:84
bc_code
This enumeration is used for codes connected to boundary conditions. There are two aspects to boundar...
Definition: common.h:71
@ REFLECT
Definition: common.h:74
@ PERIODIC
Definition: common.h:73
@ MIRROR
Definition: common.h:72
typename spline_type::value_type bspl_value_type
using declaration for a bspline's value type
Definition: bspline.h:1002
Code to create the coefficient array for a b-spline.
class bracer encodes the entire bracing process. Note that contrary to my initial implementation,...
Definition: brace.h:132
struct bspline is the object in vspline holding b-spline coefficients. In a way, the b-spline 'is' it...
Definition: bspline.h:192
xlf_type lower_limit(const int &axis) const
Definition: bspline.h:236
vigra::TinyVector< std::ptrdiff_t, dimension > shape_type
type of a multidimensional index type
Definition: bspline.h:199
bspline_base(shape_type _core_shape, int _spline_degree, bcv_type _bcv, xlf_type _tolerance, int headroom)
The constructor sets up all metrics of the spline from the basic properties given for the spline....
Definition: bspline.h:458
xlf_type upper_limit(const int &axis) const
Definition: bspline.h:268
static shape_type get_container_shape(int spline_degree, bcv_type bcv, shape_type core_shape, int headroom)
convenience method to caculate the shape of a container array needed to hold the coefficients of a sp...
Definition: bspline.h:420
vigra::TinyVector< bc_code, dimension > bcv_type
nD type for one boundary condition per axis
Definition: bspline.h:203
const shape_type container_shape
Definition: bspline.h:218
static shape_type get_right_brace_size(int spline_degree, bcv_type bcv)
Definition: bspline.h:347
const bcv_type bcv
Definition: bspline.h:212
static long get_container_shape(int spline_degree, vspline::bc_code bc, long core_shape, int headroom)
variant for 1D splines. This variant accepts the shape as a plain long, rather than requiring a TinyV...
Definition: bspline.h:441
vigra::TinyVector< xlf_type, dimension > limit_type
nD type for one limit condition per axis
Definition: bspline.h:207
limit_type lower_limit() const
Definition: bspline.h:241
static xlf_type lower_limit(const bc_code &bc)
lower_limit returns the lower bound of the spline's defined range. This is usually 0....
Definition: bspline.h:226
const shape_type right_frame
Definition: bspline.h:217
static xlf_type upper_limit(const std::size_t &extent, const bc_code &bc)
upper_limit returns the upper bound of the spline's defined range. This is normally M - 1 if the shap...
Definition: bspline.h:255
const shape_type left_frame
Definition: bspline.h:215
limit_type upper_limit() const
Definition: bspline.h:280
const xlf_type tolerance
Definition: bspline.h:213
const shape_type core_shape
Definition: bspline.h:216
static shape_type get_left_brace_size(int spline_degree, bcv_type bcv)
get_left_brace_size and get_right_brace_size calculate the size of the brace vspline puts around the ...
Definition: bspline.h:297
class bspline now builds on class bspline_base, adding coefficient storage, while bspline_base provid...
Definition: bspline.h:499
view_type core
Definition: bspline.h:566
ExpandElementResult< value_type >::type ele_type
elementary type of value_type, like float or double
Definition: bspline.h:508
friend std::ostream & operator<<(std::ostream &osr, const bspline &bsp)
helper function to pretty-print a bspline object to an ostream
Definition: bspline.h:965
vigra::TinyVector< std::ptrdiff_t, dimension > shape_type
type of a multidimensional index type
Definition: bspline.h:199
bspline< value_type, dimension > spline_type
the type of 'this' bspline and of a 'channel view'
Definition: bspline.h:543
_value_type value_type
Definition: bspline.h:500
void prefilter(vspline::xlf_type boost=vspline::xlf_type(1), int njobs=vspline::default_njobs)
prefilter converts the knot point data in the 'core' area into b-spline coefficients....
Definition: bspline.h:815
const shape_type container_shape
Definition: bspline.h:218
static shape_type get_right_brace_size(int spline_degree, bcv_type bcv)
Definition: bspline.h:347
vigra::MultiArrayView< dimension, value_type > view_type
data are read and written to vigra MultiArrayViews:
Definition: bspline.h:539
void prefilter(const vigra::MultiArrayView< dimension, T > &data, vspline::xlf_type boost=vspline::xlf_type(1), int njobs=vspline::default_njobs)
If data are passed in, they have to have precisely the shape we have set up in core (_core_shape pass...
Definition: bspline.h:859
const bcv_type bcv
Definition: bspline.h:212
void brace(int axis=-1)
if the spline coefficients are already known, they obviously don't need to be prefiltered....
Definition: bspline.h:773
bspline(long _core_shape, int _spline_degree=3, bc_code _bc=MIRROR, xlf_type _tolerance=-1.0, int headroom=0, view_type _space=view_type())
overloaded constructor for 1D splines. This is useful because if we don't provide it,...
Definition: bspline.h:709
vigra::MultiArray< dimension, value_type > array_type
if the coefficients are owned, an array of this type holds the data:
Definition: bspline.h:535
const shape_type right_frame
Definition: bspline.h:217
view_type container
Definition: bspline.h:565
bspline(shape_type _core_shape, int _spline_degree=3, bcv_type _bcv=bcv_type(MIRROR), xlf_type _tolerance=-1.0, int headroom=0, view_type _space=view_type())
construct a bspline object with appropriate storage space to contain and process an array of knot poi...
Definition: bspline.h:634
bspline< ele_type, dimension > channel_view_type
Definition: bspline.h:545
bool shiftable(int d) const
'shift' will change the interpretation of the data in a bspline object. d is taken as a difference to...
Definition: bspline.h:926
const shape_type left_frame
Definition: bspline.h:215
channel_view_type get_channel_view(const int &channel)
get a bspline object for a single channel of the data. This is lightweight and requires the viewed da...
Definition: bspline.h:730
const xlf_type tolerance
Definition: bspline.h:213
static shape_type get_left_brace_size(int spline_degree, bcv_type bcv)
get_left_brace_size and get_right_brace_size calculate the size of the brace vspline puts around the ...
Definition: bspline.h:297
bool prefiltered
boolean flag indicating whether the coefficients were prefiltered or not. This must be taken with a g...
Definition: bspline.h:578
bool shift(int d)
shift() actually changes the interpretation of the data. The data will be taken to be coefficients of...
Definition: bspline.h:953
bspline_base< _dimension > base_type
Definition: bspline.h:510
shape mismatch is the exception which is thrown if the shapes of an input array and an output array d...
Definition: common.h:297
with the definition of 'simd_traits', we can proceed to implement 'vector_traits': struct vector_trai...
Definition: vector.h:344