vspline 1.1.0
Generic C++11 Code for Uniform B-Splines
eval.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 eval.h
41
42 \brief code to evaluate uniform b-splines
43
44 This body of code contains class evaluator and auxilliary classes which are
45 needed for it's smooth operation.
46
47 The evaluation is a reasonably straightforward process: A subset of the coefficient
48 array, containing coefficients 'near' the point of interest, is picked out, and
49 a weighted summation over this subset produces the result of the evaluation.
50 The complex bit is to have the right coefficients in the first place
51 (this is what prefiltering does), and to use the appropriate weights on
52 the coefficient window. For b-splines, there is an efficient method to
53 calculate the weights by means of a matrix multiplication, which is easily
54 extended to handle b-spline derivatives as well. Since this code lends itself
55 to a generic implementation, and it can be parametrized by the spline's order,
56 and since the method performs well, I use it here in preference to the code which
57 P. Thevenaz uses (which is, for the orders of splines it encompasses, the matrix
58 multiplication written out with a few optimizations, like omitting multiplications
59 with zero, and slightly more concise calculation of powers). The weight generation
60 code is in basis.h.
61
62 Evaluation of a b-spline seems to profit more from vectorization than prefiltering,
63 especially for float data. On my system, I found single-precision
64 operation was about three to four times as fast as unvectorized code (AVX2).
65
66 The central class of this file is class evaluator. evaluator objects are set up to
67 provide evaluation of a specific b-spline. Once they are set up they don't change and
68 effectively become pure functors. The evaluation methods typically take their arguments
69 per reference. The details of the evaluation variants, together with explanations of
70 specializations used for extra speed, can be found with the individual evaluation
71 routines. 'Ordinary' call syntax via operator() is also provided for convenience.
72
73 What do I mean by the term 'pure' functor? It's a concept from functional programming.
74 It means that calling the functor will not have any effect on the functor itself - it
75 can't change once it has been constructed. This has several nice effects: it can
76 potentially be optimized very well, it is thread-safe, and it will play well with
77 functional programming concepts - and it's conceptually appealing.
78
79 Code using class evaluator will probably use it at some core place where it is
80 part of some processing pipeline. An example would be an image processing program:
81 one might have some outer loop generating arguments (typically simdized types)
82 which are processed one after the other to yield a result. The processing will
83 typically have several stages, like coordinate generation and transformations,
84 then use class evaluator to pick an interpolated intermediate result, which is
85 further processed by, say, colour or data type manipulations before finally
86 being stored in some target container. The whole processing pipeline can be
87 coded to become a single functor, with one of class evaluator's eval-type
88 routines embedded somewhere in the middle, and all that's left is code to
89 efficiently handle the source and destination to provide arguments to the
90 pipeline - like the code in transform.h. And since this code is made to
91 provide the data feeding and storing, the only coding needed is for the pipeline,
92 which is where the 'interesting' stuff happens.
93
94 class evaluator is the 'front' class for evaluation, the implementation
95 of the functionality is in class inner_evaluator. User code will typically not
96 use class inner_evaluator, which lives in namespace vspline::detail.
97
98 The code in this file concludes by providing factory functions to obtain
99 evaluators for vspline::bspline objects. These factory functions produce
100 objects which are type-erased (see vspline::grok_type) wrappers around the
101 evaluators, which hide what's inside and simply provide evaluation of the
102 spline at given coordinates. These objects also provide operator(), so that
103 they can be used like functions.
104
105 Passing vectorized coordinates results in vectorized results, where the specific
106 types for the vectorized input and output is gleaned from vspline::vector_traits.
107 If Vc is used and the fundamental data types can be made into a Vc::SimdArray,
108 the data types for vectorized input/output will be Vc::SimdArrays (or
109 vigra::TinyVectors of Vc::SimdArrays for multichannel data). Otherwise, vspline's
110 SIMD emulation will be used, which replaces Vc::SimdArray with vspline::simd_type,
111 which is an autovectorization-friendly type with similar performance. Since the
112 objects produced by the factory functions are derived from vspline::unary_functor,
113 they can be fed to the functions in transform.h, like any other
114 vspline::unary_functor.
115
116 If you use vspline::transform and relatives, vectorization is done automatically:
117 the transform routines will inquire for the functor's vectorized signature which
118 is encoded as it's in_v and out_v types, which are - normally - results of querying
119 vspline::vector_traits. The data are then deinterleaved into the vectorized input
120 type, fed to the functor, and the vectorized result is interleaved into target
121 memory. class evaluator has all the relevant attributes and capabilites, so using
122 it with transform and relatives is easy and you need not be aware of any of the
123 'vector magic' going on internally - nor of the automatic multithreading. See
124 transform.h for more on the topic.
125*/
126
127#ifndef VSPLINE_EVAL_H
128#define VSPLINE_EVAL_H
129
130#include <array>
131
132#include "basis.h"
133#include "bspline.h"
134#include "unary_functor.h"
135#include "map.h"
136
137// While vspline still uses the c++11 dialect, Here I use a c++14 feature.
138// I picked the code from https://gist.github.com/ntessore/dc17769676fb3c6daa1f
139
140namespace std14
141{
142 template<typename T, T... Ints>
144 {
145 typedef T value_type;
146 static constexpr std::size_t size() { return sizeof...(Ints); }
147 };
148
149 template<std::size_t... Ints>
150 using index_sequence = integer_sequence<std::size_t, Ints...>;
151
152 template<typename T, std::size_t N, T... Is>
153 struct make_integer_sequence : make_integer_sequence<T, N-1, N-1, Is...> {};
154
155 template<typename T, T... Is>
156 struct make_integer_sequence<T, 0, Is...> : integer_sequence<T, Is...> {};
157
158 template<std::size_t N>
160
161 template<typename... T>
163} ;
164
165namespace vspline {
166
167/// If there are several differently-typed basis functors to be
168/// combined in a multi_bf_type object, we can erase their type,
169/// just like grok_type does for vspline::unary_functors.
170/// grokking a basis functor may cost a little bit of performance
171/// but it makes the code to handle multi_bf_types simple: instead
172/// of having to cope for several, potentially differently-typed
173/// per-axis functors there is only one type - which *may* be a
174/// bf_grok_type if the need arises to put differently-typed
175/// basis functors into the multi_bf_type.
176/// With this mechanism, the code to build evaluators can be kept
177/// simple (handling only one uniform type of basis functor used
178/// for all axes) and still use different basis functors.
179
180template < typename _delta_et ,
181 typename _target_et = _delta_et ,
182 std::size_t _vsize
185{
186 static const std::size_t degree ; // degree of the basis function
187
188 typedef _delta_et delta_et ; // elementary type of delta (like, float)
189 typedef _target_et target_et ; // elementary type of weight array
190 enum { vsize = _vsize } ; // lane count
191
192 // erased type of the basis function: a function taking a pointer
193 // to target_et, where the weights will be deposited, and a const
194 // reference to delta_et, to calculate the weights from.
195
196 typedef std::function
197 < void ( target_et * , const delta_et & ) > e_eval_t ;
198
199 // for the erased type of the vectorized basis function, the argument
200 // types are generated via alias template vspline::simdized_type
201
204
205 // and this is the erased type of the vectorized basis function:
206
207 typedef std::function
208 < void ( target_vt * , const delta_vt & ) > v_eval_t ;
209
210 // bf_grok_type holds two const references, one to the scalar form and
211 // one to the vectorized form of the basis function, both type-erased
212 // to hide the concrete implementation
213
216
217 // the 'straightforward' c'tor accepts two suitable std::functions
218
219 bf_grok_type ( const e_eval_t & _e_eval ,
220 const v_eval_t & _v_eval )
221 : e_eval ( _e_eval ) ,
222 v_eval ( _v_eval )
223 { }
224
225 // alternatively, an object may be passed in, which can be called
226 // with the signatures the two std::functions would take. This
227 // object's corresponding operator() overloads are lambda-captured
228 // and used to initialize e_eval and v_eval.
229
230 template < class grokkee_type >
231 bf_grok_type ( const grokkee_type & grokkee )
232 : e_eval ( [grokkee] ( target_et * p_trg , const delta_et & delta )
233 { grokkee ( p_trg , delta ) ; } ) ,
234 v_eval ( [grokkee] ( target_vt * p_trg , const delta_vt & delta )
235 { grokkee ( p_trg , delta ) ; } )
236 { }
237
238 // finally, the two operator() overloads for bf_grok_t: they take
239 // the signature of e_eval and v_eval and delegate to them.
240
241 void operator() ( target_et * p_trg , const delta_et & delta ) const
242 {
243 e_eval ( p_trg , delta ) ;
244 }
245
246 void operator() ( target_vt * p_trg , const delta_vt & delta ) const
247 {
248 v_eval ( p_trg , delta ) ;
249 }
250} ;
251
252// bf_grok is a factory function to create a bf_grok_type object
253// from a 'grokkee' object with suitable operator() overloads.
254
255template < typename _grokkee_t ,
256 typename _delta_et = float ,
257 typename _target_et = _delta_et ,
258 std::size_t _vsize
260bf_grok_type < _delta_et , _target_et , _vsize >
261bf_grok ( const _grokkee_t & grokkee )
262{
264}
265
266/// When several basis functors have to be passed to an evaluator,
267/// it's okay to pass a container type like a std::array or std::vector.
268/// All of these basis functors have to be of the same type, but using
269/// the method given above ('grokking' basis functors) it's possible
270/// to 'slot in' differently-typed basis functors - the functionality
271/// required by the evaluation code is provided by the 'grokked'
272/// functors and their inner workings remain opaque.
273
274/// For convenience, vspline provides class multi_bf_type, which
275/// inherits from std::array and passes the spline degree and,
276/// optionally, other arguments, through to the c'tors of the
277/// individual basis functors. With this construct, the c'tor
278/// signature for 'standard' b-splines (using vspline::basis_functor)
279/// can be left as it is, and the multi_bf_type holding the
280/// basis functors builds the basis functor with the given degree
281/// and derivative specification. The rather complex machinations
282/// needed to 'roll out' these arguments to the individual per-axis
283/// basis functors does not require user interaction.
284
285/// class multi_bf_type holds a set of basis functors, one for each
286/// dimension. While most of vspline uses vigra::TinyVectors to hold
287/// such n-dimensional objects, here we use std::array, because we
288/// need aggregate initialization, while vigra::TinyVector uses element
289/// assignment.
290/// The code is a bit tricky: the second c'tor is the one which is
291/// called from user code and takes an arbitrary argument sequence.
292/// To 'roll out' the initialization into an initializer list, we
293/// pass an index_sequence to the private first c'tor and use the
294/// expansion of the index sequence as the first value in a comma
295/// separated list of expressions, effectively discarding it.
296/// The variable argument list is expanded for each call to the
297/// basis functor's c'tor, so the unspecialized template initializes
298/// all basis functors with the same values. this is fine for cases
299/// where all basis functions are meant to do the same thing, but for
300/// b-splines, we have to consider the derivative specification,
301/// which is a per-axis argument. So for that case (see below) we
302/// need a specialization.
303
304template < typename bf_type , int ndim >
306: public std::array < bf_type , ndim >
307{
308 public:
309
310 typedef std::array < bf_type , ndim > base_type ;
311
312 const std::size_t degree ;
313
314 private:
315
316 template < std::size_t ... indices , typename ... targs >
318 int _degree ,
319 targs ... args )
320 : std::array < bf_type , ndim >
321 { ( indices , bf_type ( _degree , args ... ) ) ... } ,
322 degree ( _degree )
323 { }
324
325 public:
326
327 template < typename ... targs >
328 multi_bf_type ( int _degree , targs ... args )
330 _degree , args ... )
331 { }
332
333 using base_type::base_type ;
334} ;
335
336/// homogeneous_mbf_type can be used for cases where all basis functors
337/// are the same. The evaluation code uses operator[] to pick the functor
338/// for each axis, so here we merely override operator[] to always
339/// yield a const reference to the same basis functor.
340
341template < typename bf_type >
343{
344 const bf_type bf ;
345
347 : bf ( _bf )
348 { }
349
350 const bf_type & operator[] ( std::size_t i ) const
351 {
352 return bf ;
353 }
354} ;
355
356/// For b-spline processing, we use a multi_bf_type of b-spline basis
357/// functors (class vspline::basis_functor). We can't use the
358/// unspecialized template for this basis functor, because it takes the
359/// derivative specification, which is specified per-axis, so we need
360/// to 'pick it out' from the derivative specification and pass the
361/// per-axis value to the per-axis basis function c'tors.
362/// So here, instead of merely using the index_sequence to produce
363/// a sequence of basis function c'tor calls and ignoring the indices,
364/// we use the indices to pick out the per-axis derivative specs.
365
366template < int ndim , typename math_type >
367struct multi_bf_type < vspline::basis_functor < math_type > , ndim >
368: public std::array < vspline::basis_functor < math_type > , ndim >
369{
370 private:
371
372 template < std::size_t ... indices >
373 multi_bf_type ( int _degree ,
374 const vigra::TinyVector < int , ndim > & _dspec ,
376 : std::array < vspline::basis_functor < math_type > , ndim >
378 ( _degree , _dspec [ indices ] ) ... }
379 { }
380
381 public:
382
383 multi_bf_type ( int _degree ,
384 const vigra::TinyVector < int , ndim > & _dspec )
385 : multi_bf_type ( _degree , _dspec ,
386 std14::make_index_sequence<ndim>() )
387 { }
388} ;
389
390namespace detail
391{
392
393/// 'inner_evaluator' implements evaluation of a uniform b-spline,
394/// or some other spline-like construct relying on basis functions
395/// which can provide sets of weights for given deltas.
396/// While class evaluator (below, after namespace detail ends)
397/// provides objects derived from vspline::unary_functor which
398/// are meant to be used by user code, here we have a 'workhorse'
399/// object to which 'evaluator' delegates. This code 'rolls out'
400/// the per-axis weights the basis functor produces to the set
401/// of coefficients relevant to the current evaluation locus
402/// (the support window). We rely on a few constraints:
403/// - we require all basis functors to provide degree+1 weights
404/// - the cross product of the per-axis weights is applied to
405/// the coefficient window (the kernel is separable).
406///
407/// TODO: investigate generalization to variable degree basis
408/// functors and nD kernels which aren't separable, like RBFs
409///
410/// The template arguments are, first, the elementary types (e.t.)
411/// of the types involved, then several non-type template arguments
412/// fixing aggregate sizes. inner_evaluator only uses 'synthetic'
413/// types devoid of any specific meaning, so while class evaluator
414/// might accept types like 'std::complex<float>' or 'double'
415/// class inner_evaluator would instead accept the synthetic types
416/// vigra::TinyVector<float,2> and vigra::TinyVector<double,1>.
417///
418/// Note the name system used for the types. first prefixes:
419/// - ic: integral coordinate
420/// - rc: real coordinate
421/// - ofs: offset in memory
422/// - cf: b-spline coefficient
423/// - math: used for calculations
424/// - trg: type for result ('target') of the evaluation
425/// an infix of 'ele' refers to a type's elementary type.
426/// suffix 'type' is for unvectorized types, while suffix 'v'
427/// is used for simdized types, see below.
428///
429/// User code will not usually create and handle objects of class
430/// inner_evaluator, but to understand the code, it's necessary to
431/// know the meaning of the template arguments for class inner_evaluator:
432///
433/// - _ic_ele_type: e.t. of integral coordinates. integral coordinates occur
434/// when incoming real coordinates are split into an integral part and a
435/// remainder. Currently, only int is used.
436///
437/// - _rc_ele_type: e.t. of real coordinates. This is used for incoming real
438/// coordinates and the remainder mentioned above
439///
440/// - _ofs_ele_type: e.t. for offsets (in memory). This is used to encode
441/// the location of specific coefficients relative to the coefficients'
442/// origin in memory. Currently, only int is used.
443///
444/// - _cf_ele_type: elementary type of b-spline coefficients. While in most
445/// cases, this type will be the same as the elementary type of the knot
446/// point data the spline is built over, it may also be different. See
447/// class bspline's prefilter method.
448///
449/// - _math_ele_type: e.t. for mathematical operations. All arithmetic
450/// inside class inner_evaluator is done with this elementary type.
451/// It's used for weight generation, coefficients are cast to it, and
452/// only after the result is complete, it's cast to the 'target type'
453///
454/// - _trg_ele_type: e.t. of target (result). Since class inner_evaluator
455/// normally receives it's 'target' per reference, this template argument
456/// fixes the target's type. This way, after the arithmetic is done, the
457/// result is cast to the target type and then assigned to the target
458/// location.
459///
460/// - _dimension: number of dimensions of the spline, and therefore the
461/// number of components in incoming coordinates. If the spline is 1D,
462/// coordinates will still be contained in vigra::TinyVectors, with
463/// only one component.
464///
465/// - _channels: number of channels per coefficient. So, when working on
466/// RGB pixels, the number of channels would be three. If the spline
467/// is over fundamentals (float, double...), _channels is one and the
468/// type used here for coefficients is a vigra::TinyVector with one
469/// element.
470///
471/// - _specialize: class inner_evaluator has specialized code for
472/// degree-0 and degree-1 b-splines, aka nearest neighbour and linear
473/// interpolation. This specialized code can be activated by passing
474/// 0 or 1 here, respectively. All other values will result in the use
475/// of general b-spline evaluation, which can handle degree-0 and
476/// degree-1 splines as well, but less efficiently.
477///
478/// - _mbf_type: This defines the basis functors. For b-splines, this
479/// is a multi_bf_type of as many vspline::basis_functors as the
480/// spline's dimensions. Making this type a template argument opens
481/// the code up to the use of arbitrary basis functions, as long as all
482/// the basis functors are of the same type. To use differently-typed
483/// basis functors, erase their type using bf_grok_type (see above)
484
485template < typename _ic_ele_type , // e.t. of integral coordinates
486 typename _rc_ele_type , // e.t. of real coordinates
487 typename _ofs_ele_type , // e.t. for offsets (in memory)
488
489 typename _cf_ele_type , // elementary type of coefficients
490 typename _math_ele_type , // e.t. for mathematical operations
491 typename _trg_ele_type , // e.t. of target (result)
492
493 unsigned int _dimension , // dimensionality of the spline
494 unsigned int _channels , // number of channels per coefficient
495 int _specialize , // specialize for NN, linear, general
496 typename _mbf_type
498 >
500{
501 // make sure math_ele_type is a floating point type
502
503 static_assert ( std::is_floating_point < _math_ele_type > :: value ,
504 "class evaluator requires a floating point math_ele_type" ) ;
505
506 // pull in the template arguments in order to allow other code to
507 // inquire about the type system
508
509 typedef _ic_ele_type ic_ele_type ;
510 typedef _rc_ele_type rc_ele_type ;
511 typedef _ofs_ele_type ofs_ele_type ;
512
513 typedef _cf_ele_type cf_ele_type ;
514 typedef _math_ele_type math_ele_type ;
515 typedef _trg_ele_type trg_ele_type ;
516
517 enum { dimension = _dimension } ;
518 enum { level = dimension - 1 } ;
519 enum { channels = _channels } ;
520 enum { specialize = _specialize } ;
521
522 // define the 'synthetic' unvectorized types, which are always
523 // TinyVectors, possibly with only one element. Note how this
524 // process of 'synthesizing' the types is in a way the opposite
525 // process of what's done in class evaluator, where the template
526 // arguments are 'taken apart' to get their elementary types.
527
528 typedef vigra::TinyVector < ic_ele_type , dimension > ic_type ;
529 typedef vigra::TinyVector < rc_ele_type , dimension > rc_type ;
530 typedef ofs_ele_type ofs_type ; // TODO: superfluous?
531
532 typedef vigra::TinyVector < cf_ele_type , channels > cf_type ;
533 typedef vigra::TinyVector < math_ele_type , channels > math_type ;
534 typedef vigra::TinyVector < trg_ele_type , channels > trg_type ;
535
536 typedef vigra::TinyVector < std::ptrdiff_t , dimension > shape_type ;
537 typedef vigra::TinyVector < int , dimension > derivative_spec_type ;
538 typedef _mbf_type mbf_type ;
539
540private:
541
542 typedef typename vigra::MultiArrayView < 1 , const cf_ele_type * > :: const_iterator
543 cf_pointer_iterator ;
544
545 /// Initially I was using a template argument for this flag, but it turned out
546 /// that using a const bool set at construction time performs just as well.
547 /// Since this makes using class evaluator easier, I have chosen to go this way.
548
549 const bool even_spline_degree ;
550
551 /// memory location and layout of the spline's coefficients. Note that the
552 /// pointer points to the elementary type, and the stride is given in units
553 /// of the elementary type as well (hence the 'e' after the underscore).
554
555 const cf_ele_type * const cf_ebase ;
556 const shape_type cf_estride ;
557
558 /// cf_pointers holds the sum of cf_ebase and window offsets. This produces a small
559 /// performance gain: instead of passing the coefficients' base address (cf_ebase)
560 /// and the series of offsets (cf_offsets) into the workhorse evaluation code and
561 /// adding successive offsets to cf_ebase, we do the addition in the constructor
562 /// and save the offsetted pointers.
563
564 vigra::MultiArray < 1 , std::ptrdiff_t > cf_offsets ;
565 vigra::MultiArray < 1 , const cf_ele_type * > cf_pointers ;
566
567public:
568
569 // wgt holds a set of weight functors, one for each dimension. This object
570 // is passed in via the c'tor, and we're not looking in, just copying it.
571 // See the comments with multi_bf_type for a description of this type.
572
573 const mbf_type wgt ;
574
575 const int spline_degree ;
576 const int spline_order ;
577
578 /// size of the window of coefficients contributing to a single evaluation.
579 /// This equals 'spline_order' to the power of 'dimension'.
580
581 const int window_size ;
582
583 /// split function. This function is used to split incoming real coordinates
584 /// into an integral and a remainder part, which are used at the core of the
585 /// evaluation. selection of even or odd splitting is done via the const bool
586 /// flag 'even_spline_degree'. My initial implementation had this flag as a
587 /// template argument, but this way it's more flexible and there seems to
588 /// be no runtime penalty. This method delegates to the free function templates
589 /// even_split and odd_split, respectively, which are defined in basis.h.
590
591 template < class IT , class RT >
592 void split ( const RT& input , IT& select , RT& tune ) const
593 {
594 if ( even_spline_degree )
595 even_split ( input , select , tune ) ;
596 else
597 odd_split ( input , select , tune ) ;
598 }
599
600 const int & get_order() const
601 {
602 return spline_order ;
603 }
604
605 const int & get_degree() const
606 {
607 return spline_degree ;
608 }
609
610 const shape_type & get_estride() const
611 {
612 return cf_estride ;
613 }
614
615 /// inner_evaluator only has a single constructor, which takes these arguments:
616 ///
617 /// - _cf_ebase: pointer to the origin of the coefficient array, expressed
618 /// as a pointer to the coefficients' elementary type. 'origin' here means
619 /// the memory location coinciding with the origin of the knot point data,
620 /// which coincides with a bspline object's 'core', not the origin of a
621 /// bspline object's 'container'. Nevertheless, the data have to be suitably
622 /// 'braced' - evaluation may well fail (spectacularly) if the brace is
623 /// absent, please refer to class bspline's documentation.
624 ///
625 /// - _cf_estride: the stride(s) of the coefficient array, expressed in units
626 /// of the coefficients' elementary type.
627 ///
628 /// - _spline_degree: the degree of the b-spline. this can be up to 45
629 /// currently. See the remarks on 'shifting' in the documentation of class
630 /// evaluator below.
631 ///
632
633 inner_evaluator ( const cf_ele_type * const _cf_ebase ,
634 const shape_type & _cf_estride ,
635 int _spline_degree ,
636 const mbf_type & _wgt
637 )
638 : cf_ebase ( _cf_ebase ) ,
639 cf_estride ( _cf_estride ) ,
640 spline_degree ( _spline_degree ) ,
641 wgt ( _wgt ) ,
642 even_spline_degree ( ! ( _spline_degree & 1 ) ) ,
643 spline_order ( _spline_degree + 1 ) ,
644 window_size ( std::pow ( _spline_degree + 1 , int(dimension) ) )
645 {
646 // The evaluation forms a weighted sum over a window of the coefficient array.
647 // The sequence of offsets we calculate here is the set of pointer differences
648 // from the central element in that window to each element in the window. It's
649 // another way of coding this window, where all index calculations have already
650 // been done beforehand rather than performing them during the traversal of the
651 // window by means of stride/shape arithmetic.
652
653 // we want to iterate over all nD indexes in a window which has equal extent
654 // of spline_order in all directions (like the reconstruction filter's kernel),
655 // relative to a point which is spline_degree/2 away from the origin along
656 // every axis. This sounds complicated but is really quite simple: For a cubic
657 // b-spline over 2D data we'd get
658
659 // (-1,-1) , (0,-1), (1,-1), (2,-1)
660 // (-1, 0) , (0, 0), (1, 0), (2, 0)
661 // (-1, 1) , (0, 1), (1, 1), (2, 1)
662 // (-1, 2) , (0, 2), (1, 2), (2, 2)
663
664 // for the indexes, which are subsequently multiplied with the strides and
665 // summed up to obtain 1D offsets instead of nD coordinates. So if the coefficient
666 // array has strides (10,100) and the coefficients are single-channel, the sequence
667 // of offsets generated is
668
669 // -110, -100, -90, -80, // which is -1 * 10 + -1 * 100, 0 * 10 + -1 * 100 ...
670 // -10, 0, 10, 20,
671 // 90, 100, 110, 120,
672 // 190, 200, 210, 220
673
674 shape_type window_shape ( spline_order ) ;
675 vigra::MultiCoordinateIterator<dimension> mci ( window_shape ) ;
676
677 // cf_pointers will hold the sums of cf_ebase and the offsets into the window
678 // of participating coefficients. Now there is only one more information
679 // that's needed to localize the coefficient access during evaluation,
680 // namely an additional offset specific to the locus of the evaluation. This is
681 // generated from the integral part of the incoming coordinates during the
682 // evaluation and varies with each evaluation - the DDA (data defined access).
683 // This locus-specific offset originates as the integral part of the coordinate,
684 // (which is an nD integral coordinate) 'condensed' into an offset by multiplying
685 // it with coefficient array's stride and summimg up. During the evaluation,
686 // the coefficients in the window relevant to the current evaluation can now
687 // be accessed by combining two values: a pointer from 'cf_pointers' and the
688 // locus-specific offset. So rather than using a pointer and a set of indexes
689 // we use a set of pointers and an index to the same effect. Why do it this way?
690 // because of vectorization. If we want to process a vector of loci, this is the
691 // most efficient way of coding the operation, since all calculations which do not
692 // depend on the locus are already done here in the constructor, and the vector
693 // of offsets generated from the vector of loci can be used directly as a gather
694 // operand for accessing the coefficients. This gather operand can remain in a
695 // register throughout the entire evaluation, only the base pointer this gather
696 // operand is used with changes in the course of the evaluation. The problem
697 // with this approach is the fact that the vector of offsets is not regular in any
698 // predictable way and may well access memory locations which are far apart.
699 // Luckily this is the exception, and oftentimes access will be to near memory,
700 // which is in cache already.
701
702 cf_pointers = vigra::MultiArray < 1 , const cf_ele_type * > ( window_size ) ;
703 cf_offsets = vigra::MultiArray < 1 , std::ptrdiff_t > ( window_size ) ;
704
705 auto ofs_target = cf_offsets.begin() ;
706 auto target = cf_pointers.begin() ;
707
708 for ( int i = 0 ; i < window_size ; i++ )
709 {
710 // offsets are calculated by multiplying indexes with the coefficient array's
711 // strides and summing up. So now we have offsets instead of nD indices.
712 // By performing this addition now rather than passing in both the pointer and
713 // the offsets, we can save a few cycles and reduce register pressure.
714
715 // Note how we subtract spline_degree/2 to obtain indexes which are relative
716 // to the window's center. Annoying aside: the subtraction of spline_degree/2
717 // from *mci yields double (vigra's result type for the right scalar
718 // subtraction which accepts only double as the scalar), so the product with
719 // cf_estride and the result of 'sum' are also double. Hence I can't code
720 // 'auto offset'. We keep a record of the offset and of it's sum with cf_ebase,
721 // so we can choose which 'flavour' we want 'further down the line'
722
723 std::ptrdiff_t offset = sum ( ( *mci - spline_degree / 2 ) * cf_estride ) ;
724 *ofs_target = offset ;
725 *target = cf_ebase + offset ;
726
727 // increment the iterators
728
729 ++mci ;
730 ++ofs_target ;
731 ++target ;
732 }
733 }
734
735 /// obtain_weights calculates the weights to be applied to a section
736 /// of the coefficients from the fractional parts of the split coordinates.
737 /// What is calculated here is the evaluation of the spline's basis function
738 /// at dx, dx+/-1 ... but doing it naively is computationally expensive,
739 /// as the evaluation of the spline's basis function at arbitrary values has
740 /// to look at the value, find out the right interval, and then calculate
741 /// the value with the appropriate function. But we always have to calculate
742 /// the basis function for *all* intervals anyway, and the method used here
743 /// performs this tasks efficiently using a vector/matrix multiplication.
744 /// If the spline is more than 1-dimensional, we need a set of weights for
745 /// every dimension. The weights are accessed through a 2D MultiArrayView.
746 /// For every dimension, there are spline_order weights. Note that this
747 /// code will process unvectorized and vectorized data alike - hence the
748 /// template arguments.
749 /// note that wgt[axis] contains a 'basis_functor' object (see basis.h)
750 /// which encodes the generation of the set of weights.
751
752 template < typename nd_rc_type , typename weight_type >
753 void obtain_weights ( vigra::MultiArrayView < 2 , weight_type > & weight ,
754 const nd_rc_type & c ) const
755 {
756 auto ci = c.cbegin() ;
757 for ( int axis = 0 ; axis < dimension ; ++ci , ++axis )
758 wgt[axis] ( weight.data() + axis * spline_order , *ci ) ;
759 }
760
761 template < typename weight_type >
762 void obtain_weights ( vigra::MultiArrayView < 2 , weight_type > & weight ) const
763 {
764 for ( int axis = 0 ; axis < dimension ; ++axis )
765 wgt[axis] ( weight.data() + axis * spline_order ) ;
766 }
767
768 /// obtain weight for a single axis
769
770 template < typename rc_type , typename weight_type >
771 void obtain_weights ( weight_type * p_weight ,
772 const int & axis ,
773 const rc_type & c ) const
774 {
775 wgt[axis] ( p_weight , c ) ;
776 }
777
778 template < typename weight_type >
779 void obtain_weights ( weight_type * p_weight ,
780 const int & axis ) const
781 {
782 wgt[axis] ( p_weight ) ;
783 }
784
785private:
786
787 // next we have collateral code which we keep private.
788 // TODO some of the above could also be private.
789
790 // to be able to use the same code to access the coefficients, no matter
791 // if the operation is vectorized or not, we provide 'load' functions
792 // which encapsulate the memory access. This allows us to uniformly handle
793 // vectorized and unvectorized data: the remainder of the code processes
794 // unvectorized and vectorized data alike, and only when it comes to
795 // fetching the coefficients from memory we need specialized code for
796 // the memory access.
797
798 // KFJ 2018-03-15 changed the load functions to expect a pointer to
799 // cf_ele_type, access memory via this pointer, then convert to some
800 // given target type, rather than load to some type derived from
801 // cf_ele_type. We know the coefficients are always cf_ele_type, but
802 // we usually want math_type for processing. And we don't want the
803 // calling code having to be 'aware' of what cf_ele_type is at all.
804 // With this change, the template argument introducing the coefficient
805 // type could go from the eval code, and ATD via the arguments works.
806
807 // Note that since inner_evaluator uniformly handles data as TinyVectors,
808 // 'target' in the load functions below is always a TinyVector, possibly
809 // with only one element: we're using 'synthetic' types.
810
811 /// load function for vigra::TinyVectors of fundamental T
812
813 template < typename T , int N >
814 static inline void load ( vigra::TinyVector < T , N > & target ,
815 const cf_ele_type * const mem ,
816 const int & index )
817 {
818 for ( int i = 0 ; i < N ; i++ )
819 target[i] = T ( mem [ index + i ] ) ;
820 }
821
822 // KFJ 2018-05-08 with the automatic use of vectorization the
823 // distinction whether cf_ele_type is 'vectorizable' or not
824 // is no longer needed: simdized_type will be a Vc::SimdArray
825 // if possible, a vspline::simd_type otherwise.
826
827 // dispatch, depending on whether cf_ele_type is the same as
828 // what target contains. Usually the target will hold
829 // 'math_ele_type', but for degree-0 splines, where the result
830 // is directly derived from the coefficients, target holds
831 // 'trg_ele_type'. We have the distinct cases first and the
832 // dispatching routine below.
833
834 template < typename target_t , typename index_t >
835 static void load ( target_t & target ,
836 const cf_ele_type * const mem ,
837 const index_t & indexes ,
838 std::true_type
839 )
840 {
841 static const size_t sz = index_t::size() ;
842 for ( int e = 0 ; e < target_t::static_size ; e++ )
843 {
844 // directly gather to 'target'
845 target[e].gather ( mem + e , indexes ) ;
846 }
847 }
848
849 template < typename target_t , typename index_t >
850 static void load ( target_t & target ,
851 const cf_ele_type * const mem ,
852 const index_t & indexes ,
853 std::false_type
854 )
855 {
856 static const size_t sz = index_t::size() ;
858 for ( int e = 0 ; e < target_t::static_size ; e++ )
859 {
860 // gather to 'help' and 'assign' to target, which affects
861 // the necessary type transformation
862 help.gather ( mem + e , indexes ) ;
863 vspline::assign ( target[e] , help ) ;
864 }
865 }
866
867 /// dispatch function for vectorized loads. We look at one criterion:
868 /// - is cf_ele_type the same type as what the target object contains?
869
870 template < typename target_t , typename index_t >
871 static void inline load
872 ( target_t & target ,
873 const cf_ele_type * const mem ,
874 const index_t & indexes )
875 {
876 typedef typename target_t::value_type::value_type target_ele_type ;
877
878 load ( target , mem , indexes ,
879 std::integral_constant
880 < bool ,
881 std::is_same < cf_ele_type , target_ele_type > :: value
882 > ()
883 ) ;
884 }
885
886 /// _eval is the workhorse routine and implements the recursive arithmetic
887 /// needed to evaluate the spline. First the weights for the current dimension
888 /// are obtained from the weights object passed in. Once the weights are known,
889 /// they are successively multiplied with the results of recursively calling
890 /// _eval for the next lower dimension and the products are summed up to produce
891 /// the result value. The scheme of using a recursive evaluation has several
892 /// benefits: it needs no explicit intermediate storage of partial sums
893 /// (uses the stack instead) and it makes the process dimension-agnostic in an
894 /// elegant way. Therefore, the code is also thread-safe. note that this routine
895 /// is used for operation on braced splines, with the sequence of offsets to be
896 /// visited fixed at the evaluator's construction.
897
898 template < int level , class math1_type , class offset_type >
899 struct _eval
900 {
901 inline
902 void operator() ( const offset_type & locus ,
903 cf_pointer_iterator & cfp_iter ,
904 const vigra::MultiArrayView < 2 , math1_type > & weight ,
905 vigra::TinyVector < math1_type , channels > & sum
906 ) const
907 {
908 const math1_type w ( weight ( 0 , level ) ) ;
909
910 // recursively call _eval for the next lower level, receiving
911 // the result in 'sum', and apply the first weight to it
912
913 _eval < level - 1 , math1_type , offset_type >()
914 ( locus , cfp_iter , weight , sum ) ;
915
916 for ( int d = 0 ; d < channels ; d++ )
917 sum[d] *= w ;
918
919 // to pick up the result of further recursive calls:
920
921 vigra::TinyVector < math1_type , channels > subsum ;
922
923 // now keep calling _eval for the next lower level, receiving
924 // the result in 'subsum', and apply the corresponding weight.
925 // Then add the weighted subsum to 'sum'.
926
927 for ( int i = 1 ; i < weight.shape ( 0 ) ; i++ )
928 {
929 const math1_type w ( weight ( i , level ) ) ;
930
931 _eval < level - 1 , math1_type , offset_type >()
932 ( locus , cfp_iter , weight , subsum ) ;
933
934 // KFJ 2019-02-12 tentative use of fma
935
936#ifdef USE_FMA
937 for ( int d = 0 ; d < channels ; d++ )
938 sum[d] = fma ( subsum[d] , w , sum[d] ) ;
939#else
940 for ( int d = 0 ; d < channels ; d++ )
941 subsum[d] *= w ;
942
943 sum += subsum ;
944#endif
945 }
946 }
947 } ;
948
949 /// at level 0 the recursion ends, now we finally apply the weights for axis 0
950 /// to the window of coefficients. Note how cfp_iter is passed in per reference.
951 /// This looks wrong, but it's necessary: When, in the course of the recursion,
952 /// the level 0 routine is called again, it needs to access the next bunch of
953 /// spline_order coefficients.
954 ///
955 /// Just incrementing the reference saves us incrementing higher up.
956 /// This is the point where we access the spline's coefficients. Since _eval works
957 /// for vectorized and unvectorized data alike, this access is coded as a call to
958 /// 'load' which provides a uniform syntax for the memory access. The implementation
959 /// of the load routines used here is just above.
960 ///
961 /// The access to the coefficients is a bit difficult to spot: they are accessed
962 /// via cfp_iter. cfp_iter iterates over an array of readily offsetted pointers.
963 /// These pointers point to all elements in a window of coefficients centered at
964 /// the coefficients' base address. By adding 'locus' to one of these pointers,
965 /// the resulting pointer now points to the element of a specific window of
966 /// coefficients, namely that window where the coefficient subset for the current
967 /// evaluation is located. locus may be a SIMD type, in which case it refers to
968 /// several windows. 'locus' is the offset produced from the integral part of the
969 /// coordinate(s) passed in, so it is the datum which provides the localization
970 /// of the DDA, while the pointers coming from cfp_iter are constant throughout
971 /// the evaluator's lifetime.
972
973 template < class math1_type , class offset_type >
974 struct _eval < 0 , math1_type , offset_type >
975 {
976 inline
977 void operator() ( const offset_type & locus ,
978 cf_pointer_iterator & cfp_iter ,
979 const vigra::MultiArrayView < 2 , math1_type > & weight ,
980 vigra::TinyVector < math1_type , channels > & sum
981 ) const
982 {
983 typedef vigra::TinyVector < math1_type , channels > math_type ;
984
985 const math1_type w ( weight ( 0 , 0 ) ) ;
986
987 // initialize 'sum' by 'loading' a coefficient (or a set of
988 // coefficients if we're running vector code) - then apply
989 // the first (set of) weight(s).
990
991 load ( sum , *cfp_iter , locus ) ;
992
993 for ( int d = 0 ; d < channels ; d++ )
994 sum[d] *= w ;
995
996 ++cfp_iter ;
997
998 // now keep on loading coefficients, apply corresponding weights
999 // and add the weighted coefficients to 'sum'
1000
1001 for ( int i = 1 ; i < weight.shape ( 0 ) ; i++ )
1002 {
1003 const math1_type w ( weight ( i , 0 ) ) ;
1004
1005 math_type help ;
1006 load ( help , *cfp_iter , locus ) ;
1007 ++cfp_iter ;
1008
1009 // KFJ 2019-02-12 tentative use of fma
1010
1011#ifdef USE_FMA
1012 for ( int d = 0 ; d < channels ; d++ )
1013 sum[d] = fma ( help[d] , w , sum[d] ) ;
1014#else
1015 for ( int d = 0 ; d < channels ; d++ )
1016 help[d] *= w ;
1017
1018 sum += help ;
1019#endif
1020 }
1021 }
1022
1023 } ;
1024
1025 /// specialized code for degree-1 b-splines, aka linear interpolation.
1026 /// here, there is no gain to be had from working with precomputed
1027 /// per-axis weights, the weight generation is trivial. So the
1028 /// specialization given here is faster than using the general _eval
1029 /// sequence, which otherwise works just as well for degree-1 splines.
1030
1031 template < int level , class math1_type , class offset_type >
1032 struct _eval_linear
1033 {
1034 inline
1035 void operator() ( const offset_type& locus ,
1036 cf_pointer_iterator & cfp_iter ,
1037 const vigra::TinyVector < math1_type , dimension > & tune ,
1038 vigra::TinyVector < math1_type , channels > & sum
1039 ) const
1040 {
1041 const math1_type wl ( math1_type(1) - tune [ level ] ) ;
1042 const math1_type wr ( tune [ level ] ) ;
1043
1044 _eval_linear < level - 1 , math1_type , offset_type >()
1045 ( locus , cfp_iter , tune , sum ) ;
1046
1047 for ( int d = 0 ; d < channels ; d++ )
1048 sum[d] *= wl ;
1049
1050 vigra::TinyVector < math1_type , channels > subsum ;
1051
1052 _eval_linear < level - 1 , math1_type , offset_type >()
1053 ( locus , cfp_iter , tune , subsum ) ;
1054
1055 // KFJ 2019-02-12 tentative use of fma
1056
1057#ifdef USE_FMA
1058 for ( int d = 0 ; d < channels ; d++ )
1059 sum[d] = fma ( subsum[d] , wr , sum[d] ) ;
1060#else
1061 for ( int d = 0 ; d < channels ; d++ )
1062 subsum[d] *= wr ;
1063
1064 sum += subsum ;
1065#endif
1066 }
1067 } ;
1068
1069 /// again, level 0 terminates the recursion, again accessing the spline's
1070 /// coefficients with the 'load' function defined above.
1071
1072 template < class math1_type , class offset_type >
1073 struct _eval_linear < 0 , math1_type , offset_type >
1074 {
1075 inline
1076 void operator() ( const offset_type & locus ,
1077 cf_pointer_iterator & cfp_iter ,
1078 const vigra::TinyVector < math1_type , dimension > & tune ,
1079 vigra::TinyVector < math1_type , channels > & sum
1080 ) const
1081 {
1082 const math1_type wl ( math1_type(1) - tune [ 0 ] ) ;
1083 const math1_type wr ( tune [ 0 ] ) ;
1084
1085 load ( sum , *cfp_iter , locus ) ;
1086 ++cfp_iter ;
1087
1088 for ( int d = 0 ; d < channels ; d++ )
1089 sum[d] *= wl ;
1090
1091 vigra::TinyVector < math1_type , channels > help ;
1092
1093 load ( help , *cfp_iter , locus ) ;
1094 ++cfp_iter ;
1095
1096 // KFJ 2019-02-12 tentative use of fma
1097
1098#ifdef USE_FMA
1099 for ( int d = 0 ; d < channels ; d++ )
1100 sum[d] = fma ( help[d] , wr , sum[d] ) ;
1101#else
1102 for ( int d = 0 ; d < channels ; d++ )
1103 help[d] *= wr ;
1104
1105 sum += help ;
1106#endif
1107 }
1108 } ;
1109
1110public:
1111
1112 // next we have the code which is called from 'outside'. In this section,
1113 // incoming coordinates are split into their integral and remainder part.
1114 // The remainder part is used to obtain the weights to apply to the spline
1115 // coefficients. The resulting data are then fed to the workhorse code above.
1116 // We have several specializations here depending on the degree of the spline.
1117
1118 /// the 'innermost' eval routine is called with offset(s) and weights. This
1119 /// routine is public because it is used from outside (namely by grid_eval).
1120 /// In this final delegate we call the workhorse code in class _eval
1121
1122 // TODO: what if the spline is degree 0 or 1? for these cases, grid_eval
1123 // should not pick this general-purpose routine
1124
1125 template < class result_type , class math1_type , class offset_type >
1126 inline
1127 void eval ( const offset_type& select ,
1128 const vigra::MultiArrayView < 2 , math1_type > & weight ,
1129 result_type & result
1130 ) const
1131 {
1132 // we need an *instance* of this iterator because it's passed into _eval
1133 // by reference and manipulated by the code there:
1134
1135 cf_pointer_iterator cfp_iter = cf_pointers.begin() ;
1136
1137 // now we can call the recursive _eval routine yielding the result
1138 // as math_type.
1139
1140 typedef vigra::TinyVector < math1_type , channels > math_type ;
1141 math_type _result ;
1142
1143 _eval < level , math1_type , offset_type >()
1144 ( select , cfp_iter , weight , _result ) ;
1145
1146 // finally, we assign to result, casting to 'result_type'. If _result
1147 // and result are of the same type, the compiler will optimize the
1148 // intermediate _result away.
1149
1150 vspline::assign ( result , _result ) ;
1151 }
1152
1153private:
1154
1155 /// 'penultimate' eval starts from an offset to a coefficient window; here
1156 /// the nD integral index to the coefficient window has already been
1157 /// 'condensed' into a 1D offset into the coefficient array's memory.
1158 /// Here we have the specializations affected by the template argument 'specialize'
1159 /// which activates more efficient code for degree 0 (nearest neighbour) and degree 1
1160 /// (linear interpolation) splines. I draw the line here; one might add further
1161 /// specializations, but from degree 2 onwards the weights are reused several times
1162 /// so looking them up in a small table (as the general-purpose code for unspecialized
1163 /// operation does) should be more efficient (TODO test).
1164 ///
1165 /// we have three variants, depending on 'specialize'. first is the specialization
1166 /// for nearest-neighbour interpolation, which doesn't delegate further, since the
1167 /// result can be obtained directly by gathering from the coefficients:
1168 ///
1169 /// dispatch for nearest-neighbour interpolation (degree 0)
1170 /// this is trivial: we pick the coefficient(s) at position 'select' and directly
1171 /// convert to result_type, since the coefficient 'window' in this case has width 1
1172 /// in every direction, the 'weight' to apply is 1. The general-purpose code below
1173 /// would iterate over this width-1 window and apply the weight, which makes it
1174 /// slower since both is futile here.
1175
1176 template < class result_type , class math1_type , class offset_type >
1177 inline
1178 void eval ( const offset_type& select ,
1179 const vigra::TinyVector < math1_type , dimension > & tune ,
1180 result_type & result ,
1181 std::integral_constant < int , 0 >
1182 ) const
1183 {
1184 load ( result , cf_ebase , select ) ;
1185 }
1186
1187 /// eval dispatch for linear interpolation (degree 1)
1188 /// again, we might use the general-purpose code below for this situation,
1189 /// but since the weights for linear interpolation are trivially computable,
1190 /// being 'tune' and 1 - 'tune', we use specialized workhorse code in
1191 /// _eval_linear, see there.
1192
1193 template < class result_type , class math1_type , class offset_type >
1194 inline
1195 void eval ( const offset_type & select ,
1196 const vigra::TinyVector < math1_type , dimension > & tune ,
1197 result_type & result ,
1198 std::integral_constant < int , 1 > ) const
1199 {
1200 cf_pointer_iterator cfp_iter = cf_pointers.begin() ;
1201 typedef vigra::TinyVector < math1_type , channels > math_type ;
1202
1203 math_type _result ;
1204
1205 _eval_linear < level , math1_type , offset_type >()
1206 ( select , cfp_iter , tune , _result ) ;
1207
1208 vspline::assign ( result , _result ) ;
1209 }
1210
1211 /// eval dispatch for arbitrary spline degrees
1212 /// here we have the general-purpose routine which works for arbitrary
1213 /// spline degrees (as long as the code in basis.h can provide, which is
1214 /// currently up to degree 45). Here, the weights are calculated by accessing
1215 /// the b-spline basis function. With the weights at hand, we delegate to
1216 /// an overload of 'eval' accepting weights, see there.
1217
1218 template < class result_type ,
1219 class math1_type ,
1220 class offset_type ,
1221 int arbitrary_spline_degree >
1222 inline
1223 void eval ( const offset_type& select ,
1224 const vigra::TinyVector < math1_type , dimension > & tune ,
1225 result_type & result ,
1226 std::integral_constant < int , arbitrary_spline_degree > ) const
1227 {
1228 using allocator_t
1229 = typename vspline::allocator_traits < math1_type > :: type ;
1230
1231 // 'weight' is a 2D vigra::MultiArray of math1_type:
1232 // TODO: can this instantiation be tweaked in any way, like
1233 // by making sure the memory is neither dynamically (re)allocated
1234 // nor zero-initialized? This is inner-loop code after all...
1235
1236 // TODO: to provide aligned memory for hwy simd_type:
1237
1238// HWY_ALIGN math1_type data [ spline_order * dimension ] ;
1239//
1240// vigra::MultiArrayView < 2 , math1_type > weight
1241// ( vigra::Shape2 ( spline_order , dimension ) , data ) ;
1242
1243 vigra::MultiArray < 2 , math1_type , allocator_t > weight
1244 ( vigra::Shape2 ( spline_order , dimension ) ) ;
1245
1246 // obtain_weights fills the array, that's why previous initialization
1247 // with zero is futile:
1248
1249 obtain_weights ( weight , tune ) ;
1250
1251 eval ( select , weight , result ) ;
1252 }
1253
1254public:
1255
1256 /// while class evaluator accepts the argument signature of a
1257 /// vspline::unary_functor, class inner_evaluator uses 'synthetic'
1258 /// types, which are always TinyVectors - possibly of just one element.
1259 /// This simplifies the code, since the 'singular' arguments don't have
1260 /// to be treated separately. The data are just the same in memory and
1261 /// class evaluator simply reinterpret_casts the arguments it receives
1262 /// to the 'synthetic' types. Another effect of moving to the 'synthetic'
1263 /// types is to 'erase' their type: any 'meaning' they may have, like
1264 /// std::complex etc., is removed - they are treated as 'bunches' of
1265 /// a fundamental type or a vector. The synthetic types are built using
1266 /// a combination of two template template arguments: 'bunch' and 'vector':
1267 /// - 'bunch', which forms an aggregate of several of a given
1268 /// type, like a vigra::TinyVector, which is currently the
1269 /// only template used for the purpose.
1270 /// - 'vector', which represents an aggregate of several fundamentals
1271 /// of equal type which will be processed with SIMD logic. Currently,
1272 /// the templates used for the purpose are vspline::simd_type
1273 /// (simulating SIMD operations with ordinary scalar code),
1274 /// Vc::SimdArray, which is a 'proper' SIMD type, and
1275 /// vspline::scalar, which is used for unvectorized data.
1276 /// Note how 'vsize' is a template argument to this function, and
1277 /// not a template argument to class inner_evaluator. This is more
1278 /// flexible, the calling code can process any vsize with the same
1279 /// inner_evaluator.
1280 /// the last template argument is used to differentiate between
1281 /// 'normal' operation with real coordinates and access with discrete
1282 /// coordinates. Weight generation for discrete coordinates is easier,
1283 /// because when they are split into an integral part and a remainder,
1284 /// the remainder is always zero. From degree 2 up we still need to
1285 /// calculate weights for the evaluation, but we can use a simplified
1286 /// method for obtaining the weights. For degree 0 and 1, we need no
1287 /// weights at all, and so we can directly load and return the spline
1288 /// coefficients, which is just like the code for the nearest-neighbour
1289 /// evaluation variants, minus the coordinate splitting.
1290 /// First in line is the overload taking real coordinates:
1291
1292 template < template < typename , int > class bunch ,
1293 template < typename , size_t > class vector ,
1294 size_t vsize >
1295 inline
1296 void eval ( const bunch < vector < rc_ele_type , vsize > , dimension >
1297 & coordinate ,
1298 bunch < vector < trg_ele_type , vsize > , channels >
1299 & result ,
1300 std::false_type // not a discrete coordinate
1301 ) const
1302 {
1303 // derive the 'vectorized' types, depending on 'vector' and the
1304 // elementary types
1305
1306 typedef vector < ic_ele_type , vsize > ic_ele_v ;
1307 typedef vector < rc_ele_type , vsize > rc_ele_v ;
1308 typedef vector < math_ele_type , vsize > math_ele_v ;
1309 typedef vector < ofs_ele_type , vsize > ofs_ele_v ;
1310 typedef vector < trg_ele_type , vsize > trg_ele_v ;
1311
1312 // perform the coordinate split
1313
1314 bunch < ic_ele_v , dimension > select ;
1315 bunch < rc_ele_v , dimension > _tune ;
1316
1317 split ( coordinate , select , _tune ) ;
1318
1319 // convert the remainders to math_type
1320
1321 bunch < math_ele_v , dimension > tune ;
1322 vspline::assign ( tune , _tune ) ;
1323
1324 // 'condense' the discrete nD coordinates into offsets
1325
1326 ofs_ele_v origin = select[0] * ic_ele_type ( cf_estride [ 0 ] ) ;
1327 for ( int d = 1 ; d < dimension ; d++ )
1328 origin += select[d] * ic_ele_type ( cf_estride [ d ] ) ;
1329
1330 // delegate, dispatching on 'specialize'
1331
1332 eval ( origin , tune , result ,
1333 std::integral_constant < int , specialize > () ) ;
1334 }
1335
1336 /// first ieval overload taking discrete coordinates, implying a
1337 /// 'delta' of zero.
1338 /// this overload is for 'uspecialized' evaluation. Here we use
1339 /// the call to obtain_weights without a delta, which simply takes
1340 /// only the first line of the weight matrix, which is precisely
1341 /// what obtain_weights would produce with a delta of zero.
1342
1343 template < template < typename , int > class bunch ,
1344 template < typename , size_t > class vector ,
1345 size_t vsize >
1346 inline
1347 void ieval ( const bunch < vector < ic_ele_type , vsize > , dimension >
1348 & select ,
1349 bunch < vector < trg_ele_type , vsize > , channels >
1350 & result ,
1351 std::false_type
1352 ) const
1353 {
1354 typedef vector < math_ele_type , vsize > math_ele_v ;
1355
1356 using allocator_t
1357 = typename vspline::allocator_traits < math_ele_v > :: type ;
1358
1359 vigra::MultiArray < 2 , math_ele_v , allocator_t > weight
1360 ( vigra::Shape2 ( spline_order , dimension ) ) ;
1361
1362 obtain_weights ( weight ) ;
1363
1364 typedef vector < ofs_ele_type , vsize > ofs_ele_v ;
1365
1366 ofs_ele_v origin = select[0] * ic_ele_type ( cf_estride [ 0 ] ) ;
1367 for ( int d = 1 ; d < dimension ; d++ )
1368 origin += select[d] * ic_ele_type ( cf_estride [ d ] ) ;
1369
1370 eval ( origin , weight , result ) ;
1371 }
1372
1373 /// second ieval overload taking discrete coordinates, implying a
1374 /// 'delta' of zero.
1375 /// this overload is for evaluators specialized to 0 or 1, where we
1376 /// can simply load the coefficients and don't need weights: the
1377 /// support is so narrow that we needn't consider neighbouring
1378 /// coefficients
1379
1380 template < template < typename , int > class bunch ,
1381 template < typename , size_t > class vector ,
1382 size_t vsize >
1383 inline
1384 void ieval ( const bunch < vector < ic_ele_type , vsize > , dimension >
1385 & select ,
1386 bunch < vector < trg_ele_type , vsize > , channels >
1387 & result ,
1388 std::true_type
1389 ) const
1390 {
1391 typedef vector < ofs_ele_type , vsize > ofs_ele_v ;
1392
1393 // 'condense' the discrete nD coordinates into offsets
1394
1395 ofs_ele_v origin = select[0] * ic_ele_type ( cf_estride [ 0 ] ) ;
1396 for ( int d = 1 ; d < dimension ; d++ )
1397 origin += select[d] * ic_ele_type ( cf_estride [ d ] ) ;
1398
1399 // load the data
1400
1401 load ( result , cf_ebase , origin ) ;
1402 }
1403
1404 /// this overload is taken for discrete coordinates, and dispatches
1405 /// again, depending on the evaluator's 'specialize' template argument,
1406 /// to one of two variants of 'ieval', above
1407
1408 template < template < typename , int > class bunch ,
1409 template < typename , size_t > class vector ,
1410 size_t vsize >
1411 inline
1412 void eval ( const bunch < vector < ic_ele_type , vsize > , dimension >
1413 & select ,
1414 bunch < vector < trg_ele_type , vsize > , channels >
1415 & result ,
1416 std::true_type
1417 ) const
1418 {
1419 static const bool just_load = ( specialize == 0 || specialize == 1 ) ;
1420
1421 ieval < bunch , vector , vsize > ( select , result ,
1422 std::integral_constant < bool , just_load > () ) ;
1423 }
1424
1425 /// initial dispatch on whether incoming coordinates are discrete or real
1426
1427 template < template < typename , int > class bunch ,
1428 template < typename , size_t > class vector ,
1429 size_t vsize >
1430 inline
1431 void eval ( const bunch < vector < rc_ele_type , vsize > , dimension >
1432 & select ,
1433 bunch < vector < trg_ele_type , vsize > , channels >
1434 & result
1435 ) const
1436 {
1437 // check the coordinate's elementary type. Is it discrete?
1438 // dispatch accordingly.
1439
1440 static const bool take_discrete_coordinates
1441 = std::is_integral < rc_ele_type > :: value ;
1442
1443 eval < bunch , vector , vsize > ( select , result ,
1444 std::integral_constant < bool , take_discrete_coordinates > () ) ;
1445 }
1446
1447} ; // end of inner_evaluator
1448
1449} ; // namespace detail
1450
1451// we define the default fundamental type used for mathematical operations.
1452// starting out with the 'better' of coordinate_type's and value_type's
1453// elementary type, we take this type's RealPromote type to make sure we're
1454// operating in some real type. With a real math_type we can operate on
1455// integral coefficients/values and only suffer from quantization errors,
1456// so provided the dynamic range of the integral values is 'sufficiently'
1457// large, this becomes an option - as opposed to operating in an integral
1458// type which is clearly not an option with the weights being in [0..1].
1459
1460template < typename coordinate_type ,
1461 typename value_type
1462 >
1464typename vigra::NumericTraits
1465 < typename vigra::PromoteTraits
1466 < typename vigra::ExpandElementResult < coordinate_type > :: type ,
1467 typename vigra::ExpandElementResult < value_type > :: type
1468 > :: Promote
1469 > :: RealPromote ;
1470
1471/// tag class used to identify all vspline::evaluator instantiations
1472
1474
1475/// class evaluator encodes evaluation of a spline-like object. This is a
1476/// generalization of b-spline evaluation, which is the default if no other
1477/// basis functions are specified. Technically, a vspline::evaluator is a
1478/// vspline::unary_functor, which has the specific capability of evaluating
1479/// a specific spline-like object. This makes it a candidate to be passed
1480/// to the functions in transform.h, like remap() and transform(), and it
1481/// also makes it suitable for vspline's functional constructs like chaining,
1482/// mapping, etc.
1483///
1484/// While creating and using vspline::evaluators is simple enough, especially from
1485/// vspline::bspline objects, there are also factory functions producing objects capable
1486/// of evaluating a b-spline. These objects are wrappers around a vspline::evaluator,
1487/// please see the factory functions make_evaluator() and make_safe_evaluator() at the
1488/// end of this file.
1489///
1490/// If you don't want to concern yourself with the details, the easiest way is to
1491/// have a bspline object handy and use one of the factory functions, assigning the
1492/// resulting functor to an auto variable:
1493///
1494/// // given a vspline::bspline object 'bspl'
1495/// // create an object (a functor) which can evaluate the spline
1496/// auto ev = vspline::make_safe_evaluator ( bspl ) ;
1497/// // which can be used like this:
1498/// auto value = ev ( real_coordinate ) ;
1499///
1500/// The evaluation relies on 'braced' coefficients, as they are provided by
1501/// a vspline::bspline object. While the most general constructor will accept
1502/// a raw pointer to coefficients (enclosed in the necessary 'brace'), this will rarely
1503/// be used, and an evaluator will be constructed from a bspline object. To create an
1504/// evaluator directly, the specific type of evaluator has to be established by providing
1505/// the relevant template arguments. We need at least two types: the 'coordinate type'
1506/// and the 'value type':
1507///
1508/// - The coordinate type is encoded as a vigra::TinyVector of some real data type -
1509/// doing image processing, the typical type would be a vigra::TinyVector < float , 2 >.
1510/// fundamental real types are also accepted (for 1D splines). There is also specialized
1511/// code for incoming discrete coordinates, which can be evaluated more quickly, So if
1512/// the caller only evaluates at discrete locations, it's good to actually pass the
1513/// discrete coordinates in, rather than converting them to some real type and passing
1514/// the real equivalent.
1515///
1516/// - The value type will usually be either a fundamental real data type such as 'float',
1517/// or a vigra::TinyVector of such an elementary type. Other data types which can be
1518/// handled by vigra's ExpandElementResult mechanism should also work. When processing
1519/// colour images, your value type would typically be a vigra::TinyVector < float , 3 >.
1520/// You can also process integer-valued data, in which case you may suffer from
1521/// quantization errors, so you should make sure your data cover the dynamic range of
1522/// the integer type used as best as possible (like by using the 'boost' parameter when
1523/// prefiltering the b-spline). Processing of integer-valued data is done using floating
1524/// point arithmetics internally, so the quantization error occurs when the result is
1525/// ready and assigned to an integer target type; if the target data type is real, the
1526/// result is precise (within arithmetic precision).
1527///
1528/// you can choose the data type which is used internally to do computations. per default,
1529/// this will be a real type 'appropriate' to the operation, but you're free to pick some
1530/// other type. Note that picking integer types for the purpose is *not* allowed.
1531/// The relevant template argument is 'math_ele_type'.
1532///
1533/// Note that class evaluator operates with 'native' spline coordinates, which run with
1534/// the coefficient array's core shape, so typically from 0 to M-1 for a 1D spline over
1535/// M values. Access with different coordinates is most easily done by 'chaining' class
1536/// evaluator objects to other vspline::unary_functor objects providing coordinate
1537/// translation, see unary_functor.h, map.h and domain.h.
1538///
1539/// The 'native' coordinates can be thought of as an extension of the discrete coordinates
1540/// used to index the spline's knot points. Let's assume you have a 1D spline over knot
1541/// points in an array 'a'. While you can index 'a' with discrete coordinates like 0, 1, 2...
1542/// you can evaluate the spline at real coordinates like 0.0, 1.5, 7.8. If a real coordinate
1543/// has no fractional part, evaluation of the spline at this coordinate will produce the
1544/// knot point value at the index which is equal to the real coordinate, so the interpolation
1545/// criterion is fulfilled. vspline does not (currently) provide code for non-uniform
1546/// splines. For such splines, the control points are not unit-spaced, and possibly also
1547/// not equally spaced. To evaluate such splines, it's necessary to perform a binary search
1548/// in the control point vector to locate the interval containing the coordinate in question,
1549/// subtract the interval's left hand side, then divide by the interval's length. This value
1550/// constitutes the 'fractional part' or 'delta' used for evaluation, while the interval's
1551/// ordinal number yields the integral part. Evaluation can then proceed as for uniform
1552/// splines. A popular subtype of such non-uniform splines are NURBS (non-uniform rational
1553/// b-splines), which have the constraints that they map a k-D manifold to a k+1-D space,
1554/// apply weights to the control points and allow coinciding control points. At the core
1555/// of a NURBS evaluation there is the same evaluation of a uniform spline, but all the
1556/// operations going on around that are considerable - especially the binary search to
1557/// locate the correct knot point interval (with it's many conditionals and memory
1558/// accesses) is very time-consuming.
1559///
1560/// While the template arguments specify coordinate and value type for unvectorized
1561/// operation, the types for vectorized operation are inferred from them, using vspline's
1562/// vector_traits mechanism. The width of SIMD vectors to be used can be chosen explicitly.
1563/// This is not mandatory - if omitted, a default value is picked.
1564///
1565/// With the evaluator's type established, an evaluator of this type can be constructed by
1566/// passing a vspline::bspline object to it's constructor. Usually, the bspline object will
1567/// contain data of the same value type, and the spline has to have the same number of
1568/// dimensions as the coordinate type. Alternatively, coefficients can be passed in as a
1569/// pointer into a field of suitably braced coefficients. It's okay for the spline to hold
1570/// coefficients of a different type: they will be cast to math_type during the evaluation.
1571///
1572/// I have already hinted at the evaluation process used, but here it is again in a nutshell:
1573/// The coordinate at which the spline is to be evaluated is split into it's integral part
1574/// and a remaining fraction. The integral part defines the location where a window from the
1575/// coefficient array is taken, and the fractional part defines the weights to use in calculating
1576/// a weighted sum over this window. This weighted sum represents the result of the evaluation.
1577/// Coordinate splitting is done with the method split(), which picks the appropriate variant
1578/// (different code is used for odd and even splines)
1579///
1580/// The generation of the weights to be applied to the window of coefficients is performed
1581/// by employing weight functors from basis.h. What's left to do is to bring all the components
1582/// together, which happens in class inner_evaluator. The workhorse code in the subclass _eval
1583/// takes care of performing the necessary operations recursively over the dimensions of the
1584/// spline.
1585///
1586/// a vspline::evaluator is technically a vspline::unary_functor. This way, it can be directly
1587/// used by constructs like vspline::chain and has a consistent interface which allows code
1588/// using evaluators to query it's specifics. Since evaluation uses no conditionals on the
1589/// data path, the whole process can be formulated as a set of templated member functions
1590/// using vectorized types or unvectorized types, so the code itself is vector-agnostic.
1591/// This makes for a nicely compact body of code inside class inner_evaluator, at the cost of
1592/// having to provide a bit of collateral code to make data access syntactically uniform,
1593/// which is done with inner_evaluator's 'load' method.
1594///
1595/// The evaluation strategy is to have all dependencies of the evaluation except for the actual
1596/// coordinates taken care of by the constructor - and immutable for the evaluator's lifetime.
1597/// The resulting object has no state which is modified after construction, making it thread-safe.
1598/// It also constitutes a 'pure' functor in a functional-programming sense, because it has
1599/// no mutable state and no side-effects, as can be seen by the fact that the 'eval' methods
1600/// are all marked const.
1601///
1602/// By providing the evaluation in this way, it becomes easy for calling code to integrate
1603/// the evaluation into more complex functors. Consider, for example, code which generates
1604/// coordinates with a functor, then evaluates a b-spline at these coordinates,
1605/// and finally subjects the resultant values to some postprocessing. All these processing
1606/// steps can be bound into a single functor, and the calling code can be reduced to polling
1607/// this functor until it has provided the desired number of output values. vspline has a
1608/// large body of code to this effect: the family of 'transform' functions. These functions
1609/// accept functors and 'wield' them over arrays of data. The functors *may* be of class
1610/// evaluator, but it's much nicer to have a generalized function which can use any
1611/// functor with the right signature, because it widens the scope of the transform faimily
1612/// to deal with any functor producing an m-dimensional output from an n-dimensional input.
1613/// vspline's companion program, pv, uses this method to create complex pixel pipelines
1614/// by 'chaining' functors, and then, finally, uses functions from vspline's transform
1615/// family to 'roll out' the pixel pipeline code over large arrays of data.
1616///
1617/// An aside: vspline::unary_functor, from which class evaluator inherits, provides
1618/// convenience code to use unary_functor objects with 'normal' function syntax. So if
1619/// you have an evaluator e, you can write code like y = e ( x ), which is equivalent
1620/// to the notation I tend to use: e ( x , y ) - this two-argument form, where the first
1621/// argumemt is a const reference to the input and the second one a reference to the output
1622/// has some technical advantages (easier ATD).
1623///
1624/// While the 'unspecialized' evaluator will try and do 'the right thing' by using general
1625/// purpose code fit for all eventualities, for time-critical operation there are
1626/// specializations which can be used to make the code faster:
1627///
1628/// - template argument 'specialize' can be set to 0 to forcibly use (more efficient) nearest
1629/// neighbour interpolation, which has the same effect as simply running with degree 0 but avoids
1630/// code which isn't needed for nearest neighbour interpolation (like the application of weights,
1631/// which is futile under the circumstances, the weight always being 1.0).
1632/// specialize can also be set to 1 for explicit n-linear interpolation. There, the weight
1633/// generation is very simple: the 1D case considers only two coefficients, which have weights
1634/// w and 1-w, respectively. This is better coded explicitly - the 'general' weight generation
1635/// code produces just the same weights, but not quite as quickly.
1636/// Any other value for 'specialize' will result in the general-purpose code being used.
1637///
1638/// Note how the default number of vector elements is fixed by picking the value
1639/// which vspline::vector_traits considers appropriate. There should rarely be a need to
1640/// choose a different number of vector elements: evaluation will often be the most
1641/// computationally intensive part of a processing chain, and therefore this choice is
1642/// sensible. But it's not mandatory. Just keep in mind that when building processing
1643/// pipelines with vspline, all their elements must use the *same* vectorization width.
1644/// When you leave it to the defaults to set 'vsize', you may get functors which differ in
1645/// vsize, and when you try to chain them, the code won't compile. So keep this in mind:
1646/// When building complex functors, pass an explicit value for vsize to all component
1647/// functors.
1648///
1649/// So here are the template arguments to class evaluator again, where the first two
1650/// are mandatory, while the remainder have defaults:
1651///
1652/// - _coordinate_type: type of a coordinate, where the spline is to be
1653/// evaluated. This can be either a fundamental type like float or double,
1654/// or a vigra::TinyVector of as many elements as the spline has dimensions.
1655/// discrete coordinates are okay and produce specialized, faster code.
1656///
1657/// - _trg_type: this is the data type the evaluation will produce as it's
1658/// result. While internally all arithmetic is done in 'math_type', the
1659/// internal result is cast to _trg_type when it's ready. _trg_type may be
1660/// a fundamental type or any type known to vigra's ExpandElementResult
1661/// mechanism, like vigra::TinyVector. It has to have as many channels as
1662/// the coefficients of the spline (or the knot point data).
1663///
1664/// - _vsize: width of SIMD vectors to use for vectorized operation.
1665/// While class inner_evaluator takes this datum as a template argument to
1666/// it's eval routine, here it's a template argument to the evaluator class.
1667/// This is so because class evaluator inherits from vspline::unary_functor,
1668/// which also requires a specific vector size, because otherwise type
1669/// erasure using std::functions would not be possible. While you may
1670/// choose arbitrary _vsize, only small multiples of the hardware vector
1671/// width of the target machine will produce most efficient code. Passing
1672/// 1 here will result in unvectorized code.
1673///
1674/// - specialize can be used to employ more efficient code for degree-0 and
1675/// degree-1 splines (aka nearest-neighbour and linear interpolation). Pass
1676/// 0 for degree 0, 1 for degree 1 and -1 for any other degree.
1677///
1678/// - _math_ele_type: elementary type to use for arithemtics in class
1679/// inner_evaluator. While in most cases default_math_type will be just right,
1680/// the default may be overridden. _math_ele_type must be a real data type.
1681///
1682/// - cf_type: data type of the coefficients of the spline. Normally this
1683/// will be the same as _trg_type above, but this is not mandatory. The
1684/// coefficients will be converted to math_type once they have been loaded
1685/// from memory, and all arithmetic is done in math_type, until finally the
1686/// result is converted to _trg_type.
1687///
1688/// - _mbf_type: This defines the basis functors. For b-splines, this
1689/// is a multi_bf_type of as many vspline::basis_functors as the
1690/// spline's dimensions. Making this type a template argument opens
1691/// the code up to the use of arbitrary basis functions, as long as all
1692/// the basis functors are of the same type. To use differently-typed
1693/// basis functors, erase their type using bf_grok_type (see above)
1694
1695template < typename _coordinate_type ,
1696 typename _trg_type ,
1697 size_t _vsize = vspline::vector_traits < _trg_type > :: size ,
1698 int _specialize = -1 ,
1699 typename _math_ele_type
1701 typename _cf_type = _trg_type ,
1702 typename _mbf_type
1704 vigra::ExpandElementResult
1705 < _coordinate_type > :: size
1706 >
1707 >
1709: public bspline_evaluator_tag ,
1710 public unary_functor < _coordinate_type , _trg_type , _vsize > ,
1711 public vspline::callable
1712 < evaluator < _coordinate_type , _trg_type , _vsize ,
1713 _specialize , _math_ele_type , _cf_type , _mbf_type > ,
1714 _coordinate_type ,
1715 _trg_type ,
1716 _vsize
1717 >
1718{
1719
1720public:
1721
1722 // pull in the template arguments
1723
1724 typedef _coordinate_type coordinate_type ;
1725 typedef _cf_type cf_type ;
1726 typedef _math_ele_type math_ele_type ;
1727 typedef _trg_type trg_type ;
1728 typedef _mbf_type mbf_type ;
1729
1730 // we figure out the elementary types and some enums which we'll
1731 // use to specify the type of 'inner_evaluator' we'll use. This is
1732 // the 'analytic' part of dealing with the types, inner_evaluator
1733 // does the 'synthetic' part.
1734
1735 typedef int ic_ele_type ;
1736 typedef int ofs_ele_type ;
1737
1741
1742 enum { vsize = _vsize } ;
1743
1744 // we want to access facilities of the base class (vspline::unary_functor)
1745 // so we use a typedef for the base class. class evaluator's property of
1746 // being derived from vspline::unary_functor provides it's 'face' to
1747 // calling code, while it's inner_evaluator provides the implementation of
1748 // it's capabilities.
1749
1751
1753 enum { level = dimension - 1 } ;
1755 enum { specialize = _specialize } ;
1756
1757 // now we can define the type of the 'inner' evaluator.
1758 // we pass all elementary types we intend to use, plus the number of
1759 // dimensions and channels, and the 'specialize' parameter which activates
1760 // specialized code for degree-0 and -1 splines.
1761
1763 rc_ele_type ,
1764 ofs_ele_type ,
1765 cf_ele_type ,
1767 trg_ele_type ,
1768 dimension ,
1769 channels ,
1770 specialize ,
1772
1773 // class evaluator has an object of this type as it's sole member. Note that
1774 // this member is 'const': it's created when the evaluator object is created,
1775 // and immutable afterwards. This allows the compiler to optimize well, and
1776 // it makes class evaluator a 'pure' functor in a functional-programming sense.
1777
1779
1780private:
1781
1782 /// 'feeder' function. This is private, since it performs potentially
1783 /// dangerous reinterpret_casts which aren't meant for 'the public',
1784 /// but only for use by the 'eval' methods below, which provide the
1785 /// interface expected of a vspline::unary_functor.
1786 /// The cast reinterprets the arguments as the corresponding
1787 /// 'synthetic' types, using the templates 'bunch' and 'vector'.
1788 /// The reinterpreted data are fed to 'inner'.
1789
1790 template < template < typename , int > class bunch ,
1791 template < typename , size_t > class vector ,
1792 size_t VSZ ,
1793 typename in_type ,
1794 typename out_type >
1795 inline
1796 void feed ( const in_type & _coordinate ,
1797 out_type & _result ) const
1798 {
1799 typedef bunch < vector < rc_ele_type , VSZ > , dimension > rc_t ;
1800
1801 typedef bunch < vector < trg_ele_type , VSZ > , channels > trg_t ;
1802
1803 auto const & coordinate
1804 = reinterpret_cast < rc_t const & >
1805 ( _coordinate ) ;
1806
1807 auto & result
1808 = reinterpret_cast < trg_t & >
1809 ( _result ) ;
1810
1811 inner.template eval < bunch , vector , VSZ >
1812 ( coordinate , result ) ;
1813 }
1814
1815public:
1816
1817 /// unvectorized evaluation function. this is delegated to 'feed'
1818 /// above, which reinterprets the arguments as the 'synthetic' types
1819 /// used by class inner_evaluator.
1820
1821 inline
1822 void eval ( const typename base_type::in_type & _coordinate ,
1823 typename base_type::out_type & _result ) const
1824 {
1825 feed < vigra::TinyVector , vspline::scalar , 1 >
1826 ( _coordinate , _result ) ;
1827 }
1828
1829 /// vectorized evaluation function. This is enabled only if vsize > 1
1830 /// to guard against cases where vsize is 1. Without the enable_if, we'd
1831 /// end up with two overloads with the same signature if vsize is 1.
1832 /// Again we delegate to 'feed' to reinterpret the arguments, this time
1833 /// passing vspline::simdized_type for 'vector'.
1834
1835 template < typename = std::enable_if < ( vsize > 1 ) > >
1836 inline
1837 void eval ( const typename base_type::in_v & _coordinate ,
1838 typename base_type::out_v & _result ) const
1839 {
1840 feed < vigra::TinyVector , vspline::simdized_type , vsize >
1841 ( _coordinate , _result ) ;
1842 }
1843
1844 typedef vigra::TinyVector < std::ptrdiff_t , dimension > shape_type ;
1845 typedef vigra::TinyVector < int , dimension > derivative_spec_type ;
1846
1847 /// class evaluator's constructors are used to initialize 'inner'.
1848 /// This first constructor overload will rarely be used by calling
1849 /// code; the commonly used overload is the next one down taking a
1850 /// vspline::bspline object.
1851 /// we create the 'multi_bf_type' object here in the c'tor, and
1852 /// pass it on to inner's c'tor. This way we can declare the copy
1853 /// in inner_evaluator const. Earlier versions of this code did pass
1854 /// the derivative specification through to inner_evaluator, but with
1855 /// the generalization to process arbitrary basis functions, this does
1856 /// not make sense anymore, so now the multi_bf_type is built and passed
1857 /// here.
1858
1859 evaluator ( const cf_ele_type * const cf_ebase ,
1860 const shape_type & cf_estride ,
1861 int spline_degree ,
1863 )
1864 : inner ( cf_ebase ,
1865 cf_estride ,
1866 spline_degree ,
1867 mbf_type ( spline_degree , derivative )
1868 )
1869 { } ;
1870
1871 /// This c'tor overload takes a const reference to a multi_bf_type
1872 /// object providing the basis functions.
1873 // TODO: infer spline degree from mbf
1874
1875 evaluator ( const cf_ele_type * const cf_ebase ,
1876 const shape_type & cf_estride ,
1877 int spline_degree ,
1878 const mbf_type & mbf )
1879 : inner ( cf_ebase ,
1880 cf_estride ,
1881 spline_degree ,
1882 mbf
1883 )
1884 { } ;
1885
1886 /// constructor taking a vspline::bspline object, and, optionally,
1887 /// a specification for derivatives of the spline and 'shift'.
1888 /// derivative: pass values other than zero here for an axis for which you
1889 /// want the derivative. Note that passing non-zero values for several axes
1890 /// at the same time will likely not give you the result you intend: The
1891 /// evaluation proceeds from the highest dimension to the lowest (z..y..x),
1892 /// obtaining the weights for the given axis by calling the basis_functor
1893 /// assigned to that axis. If any of these basis_functor objects provides
1894 /// weights to calculate a derivative, subsequent processing for another
1895 /// axis with a basis_functor yielding weights for derivatives would calculate
1896 /// the derivative of the derivative, which is not what you would normally want.
1897 /// So for multidimensional data, use a derivative specification only for one
1898 /// axis. If necessary, calculate several derivatives separately (each with
1899 /// their own evaluator), then multiply. See gsm.cc for an example.
1900
1902 derivative_spec_type derivative = derivative_spec_type ( 0 ) ,
1903 int shift = 0
1904 )
1905 : evaluator ( (cf_ele_type*) ( bspl.core.data() ) ,
1906 channels * bspl.core.stride() ,
1907 bspl.spline_degree + shift ,
1908 derivative
1909 )
1910 {
1911 // while the general constructor above has already been called,
1912 // we haven't yet made certain that a requested shift has resulted
1913 // in a valid evaluator. We check this now and throw an exception
1914 // if the shift was illegal.
1915
1916 if ( ! bspl.shiftable ( shift ) )
1917 throw not_supported
1918 ( "insufficient frame size. the requested shift can not be performed." ) ;
1919 } ;
1920
1921 /// This c'tor overload takes a const reference to a multi_bf_type
1922 /// object providing the basis functions.
1923
1925 const mbf_type & mbf ,
1926 int shift = 0
1927 )
1928 : evaluator ( (cf_ele_type*) ( bspl.core.data() ) ,
1929 channels * bspl.core.stride() ,
1930 bspl.spline_degree + shift ,
1931 mbf
1932 )
1933 {
1934 // while the general constructor above has already been called,
1935 // we haven't yet made certain that a requested shift has resulted
1936 // in a valid evaluator. We check this now and throw an exception
1937 // if the shift was illegal.
1938
1939 if ( ! bspl.shiftable ( shift ) )
1940 throw not_supported
1941 ( "insufficient frame size. the requested shift can not be performed." ) ;
1942 } ;
1943} ; // end of class evaluator
1944
1945/// alias template to make the declaration of non-bspline evaluators
1946/// easier. Note that we fix the 'specialze' template parameter at -1
1947/// (no specialization) and pass the mbf type argument in third position.
1948/// 'abf' stands for 'alternative basis function'. One example of such
1949/// an alternative basis function is vspline::area_basis_functor - see
1950/// basis.h.
1951
1952template < typename _coordinate_type ,
1953 typename _trg_type ,
1954 typename _mbf_type ,
1955 size_t _vsize = vspline::vector_traits < _trg_type > :: size ,
1956 typename _math_ele_type
1957 = default_math_type < _coordinate_type , _trg_type > ,
1958 typename _cf_type = _trg_type
1959 >
1960using abf_evaluator = evaluator < _coordinate_type ,
1961 _trg_type ,
1962 _vsize ,
1963 -1 ,
1964 _math_ele_type ,
1965 _cf_type ,
1966 _mbf_type
1967 > ;
1968
1969// in the next section we have the collateral code needed to implement
1970// the factory functions make_evaluator() and make_safe_evaluator().
1971// This code uses class evaluator as a vspline::unary_functor, so the
1972// objects which are produced by the factory functions can only handle
1973// a fixed vsize, in contrast to class inner_evaluator, which can
1974// process 'synthetic' arguments with a wider spectrum.
1975
1976namespace detail
1977{
1978
1979/// helper object to create a type-erased vspline::evaluator for
1980/// a given bspline object. The evaluator is specialized to the
1981/// spline's degree, so that degree-0 splines are evaluated with
1982/// nearest neighbour interpolation, degree-1 splines with linear
1983/// interpolation, and all other splines with general b-spline
1984/// evaluation. The resulting vspline::evaluator is 'grokked' to
1985/// erase it's type to make it easier to handle on the receiving
1986/// side: build_ev will always return a vspline::grok_type, not
1987/// one of the several possible evaluators which it produces
1988/// initially. Why the type erasure? Because a function can only
1989/// return one distinct type. With specialization for degree-0,
1990/// degre-1 and arbitrary spline degrees, there are three distinct
1991/// types of evaluator to take care of. If they are to be returned
1992/// as a common type, type erasure is the only way.
1993
1994// KFJ 2019-07-11 bug fix:
1995// changed conditionals in build_ev and build_safe_ev to consider
1996// the sum of bspl.spline_degree and shift, instead of merely
1997// bspl.spline_degree. The specialization has to take the shift
1998// into account.
1999
2000template < typename spline_type ,
2001 typename rc_type ,
2002 size_t _vsize ,
2003 typename math_ele_type ,
2004 typename result_type
2005 >
2007{
2009 result_type ,
2010 _vsize >
2011 operator() ( const spline_type & bspl ,
2012 vigra::TinyVector<int,spline_type::dimension> dspec
2013 = vigra::TinyVector<int,spline_type::dimension> ( 0 ) ,
2014 int shift = 0
2015 )
2016 {
2018 typedef bspl_value_type < spline_type > value_type ;
2019
2020 if ( bspl.spline_degree + shift == 0 )
2021 return vspline::grok
2023 < crd_t , result_type , _vsize , 0 ,
2024 math_ele_type , value_type >
2025 ( bspl , dspec , shift )
2026 ) ;
2027 else if ( bspl.spline_degree + shift == 1 )
2028 return vspline::grok
2030 < crd_t , result_type , _vsize , 1 ,
2031 math_ele_type , value_type >
2032 ( bspl , dspec , shift )
2033 ) ;
2034 else
2035 return vspline::grok
2037 < crd_t , result_type , _vsize , -1 ,
2038 math_ele_type , value_type >
2039 ( bspl , dspec , shift )
2040 ) ;
2041 }
2042} ;
2043
2044/// helper object to create a vspline::mapper object with gate types
2045/// matching a bspline's boundary conditions and extents matching the
2046/// spline's lower and upper limits. Please note that these limits
2047/// depend on the boundary conditions and are not always simply
2048/// 0 and N-1, as they are for, say, mirror boundary conditions.
2049/// see lower_limit() and upper_limit() in vspline::bspline.
2050///
2051/// gate types are inferred from boundary conditions like this:
2052///
2053/// PERIODIC -> periodic_gate
2054/// MIRROR, REFLECT -> mirror_gate
2055/// all other boundary conditions -> clamp_gate
2056///
2057/// The mapper object is chained to an evaluator, resulting in
2058/// a functor providing safe access to the evaluator. The functor
2059/// is subsequently 'grokked' to produce a uniform return type.
2060///
2061/// Please note that this is only one possible way of dealing with
2062/// out-of-bounds coordinates: they are mapped into the defined range
2063/// in a way that is coherent with the boundary conditions. If you
2064/// need other methods you'll have to build your own functional
2065/// construct.
2066///
2067/// While build_ev (above) had three distinct types to deal with,
2068/// here, the number of potential types is even larger: every distinct
2069/// boundary condition along every distinct axis will result in a specfic
2070/// type of 'gate' object. So again we use type erasure to provide a
2071/// common return type, namely vspline::grok_type.
2072
2073template < int level ,
2074 typename spline_type ,
2075 typename rc_type ,
2076 size_t _vsize ,
2077 typename math_ele_type ,
2078 typename result_type ,
2079 class ... gate_types >
2081{
2083 result_type ,
2084 _vsize >
2085 operator() ( const spline_type & bspl ,
2086 gate_types ... gates ,
2087 vigra::TinyVector<int,spline_type::dimension> dspec
2088 = vigra::TinyVector<int,spline_type::dimension> ( 0 ) ,
2089 int shift = 0
2090 )
2091 {
2092 // find out the spline's lower and upper limit for the current level
2093
2094 rc_type lower ( bspl.lower_limit ( level ) ) ;
2095 rc_type upper ( bspl.upper_limit ( level ) ) ;
2096
2097 // depending on the spline's boundary condition for the current
2098 // level, construct an appropriate gate object and recurse to
2099 // the next level. If the core's shape along this axis (level)
2100 // is 1, always clamp to zero. Note how for BC NATURAL the coordinate
2101 // is also clamped, because we can't produce the point-mirrored
2102 // continuation of the signal with a coordinate manipulation.
2103
2104 auto bc = bspl.bcv [ level ] ;
2105
2106 if ( bspl.core.shape ( level ) == 1 )
2107 {
2108 bc = vspline::CONSTANT ;
2109 lower = upper = rc_type ( 0 ) ;
2110 }
2111
2112 switch ( bc )
2113 {
2114 case vspline::PERIODIC:
2115 {
2116 auto gt = vspline::periodic < rc_type , _vsize >
2117 ( lower , upper ) ;
2118 return build_safe_ev < level - 1 , spline_type , rc_type , _vsize ,
2119 math_ele_type , result_type ,
2120 decltype ( gt ) , gate_types ... >()
2121 ( bspl , gt , gates ... , dspec , shift ) ;
2122 break ;
2123 }
2124 case vspline::MIRROR:
2125 case vspline::REFLECT:
2126 {
2127 auto gt = vspline::mirror < rc_type , _vsize >
2128 ( lower , upper ) ;
2129 return build_safe_ev < level - 1 , spline_type , rc_type , _vsize ,
2130 math_ele_type , result_type ,
2131 decltype ( gt ) , gate_types ... >()
2132 ( bspl , gt , gates ... , dspec , shift ) ;
2133 break ;
2134 }
2135 default:
2136 {
2137 auto gt = vspline::clamp < rc_type , _vsize >
2138 ( lower , upper , lower , upper ) ;
2139 return build_safe_ev < level - 1 , spline_type , rc_type , _vsize ,
2140 math_ele_type , result_type ,
2141 decltype ( gt ) , gate_types ... >()
2142 ( bspl , gt , gates ... , dspec , shift ) ;
2143 break ;
2144 }
2145 }
2146 }
2147} ;
2148
2149/// at level -1, there are no more axes to deal with, here the recursion
2150/// ends and the actual mapper object is created. Specializing on the
2151/// spline's degree (0, 1, or indeterminate), an evaluator is created
2152/// and chained to the mapper object. The resulting functor is grokked
2153/// to produce a uniform return type, which is returned to the caller.
2154
2155template < typename spline_type ,
2156 typename rc_type ,
2157 size_t _vsize ,
2158 typename math_ele_type ,
2159 typename result_type ,
2160 class ... gate_types >
2161struct build_safe_ev < -1 , spline_type , rc_type , _vsize ,
2162 math_ele_type , result_type , gate_types ... >
2163{
2165 result_type ,
2166 _vsize >
2167 operator() ( const spline_type & bspl ,
2168 gate_types ... gates ,
2169 vigra::TinyVector<int,spline_type::dimension> dspec
2170 = vigra::TinyVector<int,spline_type::dimension> ( 0 ) ,
2171 int shift = 0
2172 )
2173 {
2175 typedef bspl_value_type < spline_type > value_type ;
2176
2177 if ( bspl.spline_degree + shift == 0 )
2178 return vspline::grok
2180 < crd_t , _vsize , gate_types... > ( gates ... )
2182 < crd_t , result_type , _vsize , 0 ,
2183 math_ele_type , value_type >
2184 ( bspl , dspec , shift )
2185 ) ;
2186 else if ( bspl.spline_degree + shift == 1 )
2187 return vspline::grok
2189 < crd_t , _vsize , gate_types... > ( gates ... )
2191 < crd_t , result_type , _vsize , 1 ,
2192 math_ele_type , value_type >
2193 ( bspl , dspec , shift )
2194 ) ;
2195 else
2196 return vspline::grok
2198 < crd_t , _vsize , gate_types... > ( gates ... )
2200 < crd_t , result_type , _vsize , -1 ,
2201 math_ele_type , value_type >
2202 ( bspl , dspec , shift )
2203 ) ;
2204 }
2205} ;
2206
2207} ; // namespace detail
2208
2209/// make_evaluator is a factory function, producing a functor
2210/// which provides access to an evaluator object. Evaluation
2211/// using the resulting object is *not* intrinsically safe,
2212/// it's the user's responsibility not to pass coordinates
2213/// which are outside the spline's defined range. If you need
2214/// safe access, see 'make_safe_evaluator' below. 'Not safe'
2215/// in this context means that evaluation at out-of-bounds
2216/// locations may result in a memory fault or produce wrong or
2217/// undefined results. Note that vspline's bspline objects
2218/// are set up as to allow evaluation at the lower and upper
2219/// limit of the spline and will also tolerate values 'just
2220/// outside' the bounds to guard against quantization errors.
2221/// see vspline::bspline for details.
2222///
2223/// The evaluator will be specialized to the spline's degree:
2224/// degree 0 splines will be evaluated with nearest neighbour,
2225/// degree 1 splines with linear interpolation, all other splines
2226/// will use general b-spline evaluation.
2227///
2228/// This function returns the evaluator wrapped in an object which
2229/// hides it's type. This object only 'knows' what coordinates it
2230/// can take and what values it will produce. The extra level of
2231/// indirection may cost a bit of performance, but having a common type
2232/// simplifies handling. The wrapped evaluator also provides operator().
2233///
2234/// So, if you have a vspline::bspline object 'bspl', you can use this
2235/// factory function like this:
2236///
2237/// auto ev = make_evaluator ( bspl ) ;
2238/// typedef typename decltype(ev)::in_type coordinate_type ;
2239/// coordinate_type c ;
2240/// auto result = ev ( c ) ;
2241///
2242/// make_evaluator requires one template argument: spline_type, the
2243/// type of the vspline::bspline object you want to have evaluated.
2244/// Optionally, you can specify the elementary type for coordinates
2245/// (use either float or double) and the vectorization width. The
2246/// latter will only have an effect if vectorization is used and
2247/// the spline's data type can be vectorized. Per default, the
2248/// vectorization width will be inferred from the spline's value_type
2249/// by querying vspline::vector_traits, which tries to provide a
2250/// 'good' choice. Note that a specific evaluator will only be capable
2251/// of processing vectorized coordinates of precisely the _vsize it
2252/// has been created with. A recent addition to the template argument
2253/// list is 'math_ele_type', which allows you to pick a different type
2254/// for internal processing than the default. The default is a real
2255/// type 'appropriate' to the data in the spline.
2256///
2257/// Note that the object created by this factory function will
2258/// only work properly if you evaluate coordinates of the specific
2259/// 'rc_type' used. If you create it with the default rc_type, which
2260/// is float (and usually sufficiently precise for a coordinate), you
2261/// can't evaluate double precision coordinates with it.
2262///
2263/// On top of the bspline object, you can optionally pass a derivative
2264/// specification and a shift value, which are simply passed through
2265/// to the evaluator's constructor, see there for the meaning of these
2266/// optional parameters.
2267///
2268/// While the declaration of this function looks frightfully complex,
2269/// using it is simple: in most cases it's simply
2270///
2271/// auto ev = make_evaluator ( bspl ) ;
2272///
2273/// For an explanation of the template arguments, please see
2274/// make_safe_evaluator() below, which takes the same template args.
2275
2276// KFJ 2019-02-03 modified rc_type's default to be the real promote
2277// of the spline's elementary type
2278
2279template < class spline_type ,
2280 typename rc_type = typename
2281 vigra::NumericTraits
2282 < typename spline_type::ele_type > :: RealPromote ,
2283 size_t _vsize = vspline::vector_traits
2284 < typename spline_type::value_type > :: size ,
2285 typename math_ele_type
2287 rc_type > ,
2288 typename result_type = typename spline_type::value_type >
2290 result_type ,
2291 _vsize >
2293 vigra::TinyVector<int,spline_type::dimension> dspec
2294 = vigra::TinyVector<int,spline_type::dimension> ( 0 ) ,
2295 int shift = 0
2296 )
2297{
2298 typedef typename spline_type::value_type value_type ;
2299 typedef typename vigra::ExpandElementResult < value_type > :: type ele_type ;
2300 enum { vsize = _vsize } ;
2301 return detail::build_ev < spline_type , rc_type , _vsize ,
2302 math_ele_type , result_type >()
2303 ( bspl , dspec , shift ) ;
2304}
2305
2306/// make_safe_evaluator is a factory function, producing a functor
2307/// which provides safe access to an evaluator object. This functor
2308/// will map incoming coordinates into the spline's defined range,
2309/// as given by the spline with it's lower_limit and upper_limit
2310/// methods, honoring the bspline objects's boundary conditions.
2311/// So if, for example, the spline is periodic, all incoming
2312/// coordinates are valid and will be mapped to the first period.
2313/// Note the use of lower_limit and upper_limit. These values
2314/// also depend on the spline's boundary conditions, please see
2315/// class vspline::bspline for details. If there is no way to
2316/// meaningfully fold a coordinate into the defined range, the
2317/// coordinate is clamped to the nearest limit.
2318///
2319/// The evaluator will be specialized to the spline's degree:
2320/// degree 0 splines will be evaluated with nearest neighbour,
2321/// degree 1 splines with linear interpolation, all other splines
2322/// will use general b-spline evaluation.
2323///
2324/// This function returns the functor wrapped in an object which
2325/// hides it's type. This object only 'knows' what coordinates it
2326/// can take and what values it will produce. The extra level of
2327/// indirection costs a bit of performance, but having a common type
2328/// simplifies handling: the type returned by this function only
2329/// depends on the spline's data type, the coordinate type and
2330/// the vectorization width.
2331///
2332/// Also note that the object created by this factory function will
2333/// only work properly if you evaluate coordinates of the specific
2334/// 'rc_type' used. If you create it with the default rc_type, which
2335/// is float (and usually sufficiently precise for a coordinate), you
2336/// can't evaluate double precision coordinates with it.
2337///
2338/// On top of the bspline object, you can optionally pass a derivative
2339/// specification and a shift value, which are simply passed through
2340/// to the evlauator's constructor, see there for the meaning of these
2341/// optional parameters.
2342///
2343/// While the declaration of this function looks frightfully complex,
2344/// using it is simple: in most cases it's simply
2345///
2346/// auto ev = make_safe_evaluator ( bspl ) ;
2347///
2348/// The first template argument, spline_type, is the type of a
2349/// vspline::bspline object. This template argument has no default,
2350/// since it determines the dimensionality and the coefficient type.
2351/// But since the first argument to this factory function is of
2352/// this type, spline_type can be fixed via ATD, so it can be
2353/// omitted.
2354///
2355/// The second template argument, rc_type, can be used to pick a
2356/// different elementary type for the coordinates the evaluator will
2357/// accept. In most cases the default, float, will be sufficient.
2358///
2359/// The next template argument, _vsize, fixes the vectorization width.
2360/// Per default, this will be what vspline deems appropriate for the
2361/// spline's coefficient type.
2362///
2363/// math_ele_type can be used to specify a different fundamental type
2364/// to be used for arithemtic operations during evaluation. The default
2365/// used here is a real type of at least the precision of the coordinates
2366/// or the spline's coefficients, but you may want to raise precision
2367/// here, for example by passing 'double' while your data are all float.
2368///
2369/// Finally you can specify a result type. Per default the result will
2370/// be of the same type as the spline's coefficients, but you may want
2371/// a different value here - a typical example would be a spline with
2372/// integral coefficients, where you might prefer to get the result in,
2373/// say, float to avoid quantization errors on the conversion from the
2374/// 'internal' result (which is in math_type) to the output.
2375
2376template < class spline_type ,
2377 typename rc_type = typename
2378 vigra::NumericTraits
2379 < typename spline_type::ele_type > :: RealPromote ,
2380 size_t _vsize = vspline::vector_traits
2381 < typename spline_type::value_type > :: size ,
2382 typename math_ele_type
2384 rc_type > ,
2385 typename result_type = typename spline_type::value_type
2386 >
2388 result_type ,
2389 _vsize >
2391 vigra::TinyVector<int,spline_type::dimension> dspec
2392 = vigra::TinyVector<int,spline_type::dimension> ( 0 ) ,
2393 int shift = 0
2394 )
2395{
2396 typedef typename spline_type::value_type value_type ;
2397 typedef typename vigra::ExpandElementResult < value_type > :: type ele_type ;
2398 enum { vsize = _vsize } ;
2400 spline_type ,
2401 rc_type ,
2402 _vsize ,
2403 math_ele_type ,
2404 result_type >() ( bspl , dspec , shift ) ;
2405} ;
2406
2407} ; // end of namespace vspline
2408
2409#endif // VSPLINE_EVAL_H
Code to calculate the value of the B-spline basis function and it's derivatives.
vspline::basis_functor< long double > bf_type
basis_sample.cc - access to the b-spline basis functions
Definition: basis_sample.cc:76
defines class bspline
vigra::TinyVector< float, 2 > coordinate_type
Definition: ca_correct.cc:107
vspline::bspline< pixel_type, 2 > spline_type
Definition: ca_correct.cc:111
class evaluator encodes evaluation of a spline-like object. This is a generalization of b-spline eval...
Definition: eval.h:1718
evaluator(const cf_ele_type *const cf_ebase, const shape_type &cf_estride, int spline_degree, const mbf_type &mbf)
This c'tor overload takes a const reference to a multi_bf_type object providing the basis functions.
Definition: eval.h:1875
ET< coordinate_type > rc_ele_type
Definition: eval.h:1738
void eval(const typename base_type::in_v &_coordinate, typename base_type::out_v &_result) const
vectorized evaluation function. This is enabled only if vsize > 1 to guard against cases where vsize ...
Definition: eval.h:1837
const inner_type inner
Definition: eval.h:1778
_math_ele_type math_ele_type
Definition: eval.h:1726
detail::inner_evaluator< ic_ele_type, rc_ele_type, ofs_ele_type, cf_ele_type, math_ele_type, trg_ele_type, dimension, channels, specialize, mbf_type > inner_type
Definition: eval.h:1771
evaluator(const cf_ele_type *const cf_ebase, const shape_type &cf_estride, int spline_degree, derivative_spec_type derivative=derivative_spec_type(0))
class evaluator's constructors are used to initialize 'inner'. This first constructor overload will r...
Definition: eval.h:1859
_coordinate_type coordinate_type
Definition: eval.h:1724
_mbf_type mbf_type
Definition: eval.h:1728
ET< trg_type > trg_ele_type
Definition: eval.h:1740
_trg_type trg_type
Definition: eval.h:1727
evaluator(const vspline::bspline< cf_type, dimension > &bspl, derivative_spec_type derivative=derivative_spec_type(0), int shift=0)
constructor taking a vspline::bspline object, and, optionally, a specification for derivatives of the...
Definition: eval.h:1901
vigra::TinyVector< std::ptrdiff_t, dimension > shape_type
Definition: eval.h:1844
unary_functor< coordinate_type, trg_type, vsize > base_type
Definition: eval.h:1750
_cf_type cf_type
Definition: eval.h:1725
vigra::TinyVector< int, dimension > derivative_spec_type
Definition: eval.h:1845
evaluator(const vspline::bspline< cf_type, dimension > &bspl, const mbf_type &mbf, int shift=0)
This c'tor overload takes a const reference to a multi_bf_type object providing the basis functions.
Definition: eval.h:1924
ET< cf_type > cf_ele_type
Definition: eval.h:1739
void eval(const typename base_type::in_type &_coordinate, typename base_type::out_type &_result) const
unvectorized evaluation function. this is delegated to 'feed' above, which reinterprets the arguments...
Definition: eval.h:1822
double rc_type
Definition: eval.cc:94
@ vsize
Definition: eval.cc:96
const int VSZ
Definition: mandelbrot.cc:68
code to handle out-of-bounds coordinates.
Definition: eval.h:141
make_integer_sequence< std::size_t, N > make_index_sequence
Definition: eval.h:159
Definition: basis.h:79
void odd_split(rc_t v, ic_t &iv, rc_t &fv)
coordinates are split into an integral part and a remainder. this is used for weight generation,...
Definition: basis.h:94
void assign(T &t, const U &u)
Definition: vector.h:473
typename vigra::ExpandElementResult< T > ::type ET
using definition for the 'elementary type' of a type via vigra's ExpandElementResult mechanism....
Definition: common.h:110
vspline::grok_type< typename grokkee_type::in_type, typename grokkee_type::out_type, grokkee_type::vsize > grok(const grokkee_type &grokkee)
grok() is the corresponding factory function, wrapping grokkee in a vspline::grok_type.
vspline::grok_type< bspl_coordinate_type< spline_type, rc_type >, result_type, _vsize > make_evaluator(const spline_type &bspl, vigra::TinyVector< int, spline_type::dimension > dspec=vigra::TinyVector< int, spline_type::dimension >(0), int shift=0)
make_evaluator is a factory function, producing a functor which provides access to an evaluator objec...
Definition: eval.h:2292
typename vigra::NumericTraits< typename vigra::PromoteTraits< typename vigra::ExpandElementResult< coordinate_type > ::type, typename vigra::ExpandElementResult< value_type > ::type > ::Promote > ::RealPromote default_math_type
Definition: eval.h:1469
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
bf_grok_type< _delta_et, _target_et, _vsize > bf_grok(const _grokkee_t &grokkee)
Definition: eval.h:261
vspline::map_functor< nd_rc_type, _vsize, gate_types... > mapper(gate_types ... args)
factory function to create a mapper type functor given a set of gate_type objects....
Definition: map.h:564
typename vector_traits< T, N > ::type simdized_type
this alias is used as a shorthand to pick the vectorized type for a given type T and a size N from 'v...
Definition: vector.h:459
vspline::grok_type< bspl_coordinate_type< spline_type, rc_type >, result_type, _vsize > make_safe_evaluator(const spline_type &bspl, vigra::TinyVector< int, spline_type::dimension > dspec=vigra::TinyVector< int, spline_type::dimension >(0), int shift=0)
make_safe_evaluator is a factory function, producing a functor which provides safe access to an evalu...
Definition: eval.h:2390
void even_split(rc_t v, ic_t &iv, rc_t &fv)
for even splines, the integral part is obtained by rounding. when the result of rounding is subtracte...
Definition: basis.h:131
@ CONSTANT
Definition: common.h:76
@ 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
void bunch(const vigra::TinyVector< ele_type, chn > *const &src, vigra::TinyVector< vspline::vc_simd_type< ele_type, vsz >, chn > &trg, const ic_type &stride)
bunch picks up data from interleaved, strided memory and stores them in a data type representing a pa...
Definition: interleave.h:220
grokkee_type is a vspline::unary_functor returning twice it's input
Definition: grok.cc:73
static constexpr std::size_t size()
Definition: eval.h:146
vspline creates vigra::MultiArrays of vectorized types. As long as the vectorized types are Vc::SimdA...
Definition: common.h:267
basis_functor is an object producing the b-spline basis function value for given arguments,...
Definition: basis.h:402
If there are several differently-typed basis functors to be combined in a multi_bf_type object,...
Definition: eval.h:185
vspline::simdized_type< delta_et, vsize > delta_vt
Definition: eval.h:202
static const std::size_t degree
Definition: eval.h:186
bf_grok_type(const e_eval_t &_e_eval, const v_eval_t &_v_eval)
Definition: eval.h:219
void operator()(target_et *p_trg, const delta_et &delta) const
Definition: eval.h:241
std::function< void(target_et *, const delta_et &) > e_eval_t
Definition: eval.h:197
const v_eval_t v_eval
Definition: eval.h:215
_target_et target_et
Definition: eval.h:189
const e_eval_t e_eval
Definition: eval.h:214
_delta_et delta_et
Definition: eval.h:188
bf_grok_type(const grokkee_type &grokkee)
Definition: eval.h:231
std::function< void(target_vt *, const delta_vt &) > v_eval_t
Definition: eval.h:208
vspline::simdized_type< target_et, vsize > target_vt
Definition: eval.h:203
tag class used to identify all vspline::evaluator instantiations
Definition: eval.h:1473
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
_value_type value_type
Definition: bspline.h:500
const bcv_type bcv
Definition: bspline.h:212
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
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
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
mixin 'callable' is used with CRTP: it serves as additional base to unary functors which are meant to...
helper object to create a type-erased vspline::evaluator for a given bspline object....
Definition: eval.h:2007
vspline::grok_type< bspl_coordinate_type< spline_type, rc_type >, result_type, _vsize > operator()(const spline_type &bspl, vigra::TinyVector< int, spline_type::dimension > dspec=vigra::TinyVector< int, spline_type::dimension >(0), int shift=0)
Definition: eval.h:2011
helper object to create a vspline::mapper object with gate types matching a bspline's boundary condit...
Definition: eval.h:2081
vspline::grok_type< bspl_coordinate_type< spline_type, rc_type >, result_type, _vsize > operator()(const spline_type &bspl, gate_types ... gates, vigra::TinyVector< int, spline_type::dimension > dspec=vigra::TinyVector< int, spline_type::dimension >(0), int shift=0)
Definition: eval.h:2085
'inner_evaluator' implements evaluation of a uniform b-spline, or some other spline-like construct re...
Definition: eval.h:500
vigra::TinyVector< std::ptrdiff_t, dimension > shape_type
Definition: eval.h:536
const int & get_order() const
Definition: eval.h:600
vigra::TinyVector< trg_ele_type, channels > trg_type
Definition: eval.h:534
void obtain_weights(weight_type *p_weight, const int &axis, const rc_type &c) const
obtain weight for a single axis
Definition: eval.h:771
vigra::TinyVector< ic_ele_type, dimension > ic_type
Definition: eval.h:528
const int window_size
size of the window of coefficients contributing to a single evaluation. This equals 'spline_order' to...
Definition: eval.h:581
const int & get_degree() const
Definition: eval.h:605
void eval(const offset_type &select, const vigra::MultiArrayView< 2, math1_type > &weight, result_type &result) const
the 'innermost' eval routine is called with offset(s) and weights. This routine is public because it ...
Definition: eval.h:1127
void eval(const bunch< vector< ic_ele_type, vsize >, dimension > &select, bunch< vector< trg_ele_type, vsize >, channels > &result, std::true_type) const
this overload is taken for discrete coordinates, and dispatches again, depending on the evaluator's '...
Definition: eval.h:1412
vigra::TinyVector< cf_ele_type, channels > cf_type
Definition: eval.h:532
inner_evaluator(const cf_ele_type *const _cf_ebase, const shape_type &_cf_estride, int _spline_degree, const mbf_type &_wgt)
inner_evaluator only has a single constructor, which takes these arguments:
Definition: eval.h:633
_math_ele_type math_ele_type
Definition: eval.h:514
void split(const RT &input, IT &select, RT &tune) const
split function. This function is used to split incoming real coordinates into an integral and a remai...
Definition: eval.h:592
void obtain_weights(weight_type *p_weight, const int &axis) const
Definition: eval.h:779
void eval(const bunch< vector< rc_ele_type, vsize >, dimension > &select, bunch< vector< trg_ele_type, vsize >, channels > &result) const
initial dispatch on whether incoming coordinates are discrete or real
Definition: eval.h:1431
_trg_ele_type trg_ele_type
Definition: eval.h:515
void ieval(const bunch< vector< ic_ele_type, vsize >, dimension > &select, bunch< vector< trg_ele_type, vsize >, channels > &result, std::false_type) const
first ieval overload taking discrete coordinates, implying a 'delta' of zero. this overload is for 'u...
Definition: eval.h:1347
void eval(const bunch< vector< rc_ele_type, vsize >, dimension > &coordinate, bunch< vector< trg_ele_type, vsize >, channels > &result, std::false_type) const
while class evaluator accepts the argument signature of a vspline::unary_functor, class inner_evaluat...
Definition: eval.h:1296
const shape_type & get_estride() const
Definition: eval.h:610
void obtain_weights(vigra::MultiArrayView< 2, weight_type > &weight) const
Definition: eval.h:762
void obtain_weights(vigra::MultiArrayView< 2, weight_type > &weight, const nd_rc_type &c) const
obtain_weights calculates the weights to be applied to a section of the coefficients from the fractio...
Definition: eval.h:753
_ofs_ele_type ofs_ele_type
Definition: eval.h:511
void ieval(const bunch< vector< ic_ele_type, vsize >, dimension > &select, bunch< vector< trg_ele_type, vsize >, channels > &result, std::true_type) const
second ieval overload taking discrete coordinates, implying a 'delta' of zero. this overload is for e...
Definition: eval.h:1384
vigra::TinyVector< rc_ele_type, dimension > rc_type
Definition: eval.h:529
vigra::TinyVector< math_ele_type, channels > math_type
Definition: eval.h:533
vigra::TinyVector< int, dimension > derivative_spec_type
Definition: eval.h:537
class grok_type is a helper class wrapping a vspline::unary_functor so that it's type becomes opaque ...
homogeneous_mbf_type can be used for cases where all basis functors are the same. The evaluation code...
Definition: eval.h:343
const bf_type & operator[](std::size_t i) const
Definition: eval.h:350
homogeneous_mbf_type(const bf_type &_bf)
Definition: eval.h:346
multi_bf_type(int _degree, const vigra::TinyVector< int, ndim > &_dspec)
Definition: eval.h:383
When several basis functors have to be passed to an evaluator, it's okay to pass a container type lik...
Definition: eval.h:307
std::array< bf_type, ndim > base_type
Definition: eval.h:310
multi_bf_type(int _degree, targs ... args)
Definition: eval.h:328
const std::size_t degree
Definition: eval.h:312
exception which is thrown if an opertion is requested which vspline does not support
Definition: common.h:307
class unary_functor provides a functor object which offers a system of types for concrete unary funct...
vector_traits< IN, vsize >::type in_v
vectorized in_type and out_type. vspline::vector_traits supplies these types so that multidimensional...
vector_traits< OUT, vsize >::type out_v
with the definition of 'simd_traits', we can proceed to implement 'vector_traits': struct vector_trai...
Definition: vector.h:344
interface definition for unary functors