class evaluator encodes evaluation of a spline-like object. This is a generalization of b-spline evaluation, which is the default if no other basis functions are specified. Technically, a vspline::evaluator is a vspline::unary_functor, which has the specific capability of evaluating a specific spline-like object. This makes it a candidate to be passed to the functions in transform.h, like remap() and transform(), and it also makes it suitable for vspline's functional constructs like chaining, mapping, etc.
More...
template<typename _coordinate_type, typename _trg_type, size_t _vsize = vspline::vector_traits < _trg_type > :: size, int _specialize = -1, typename _math_ele_type = default_math_type < _coordinate_type , _trg_type >, typename _cf_type = _trg_type, typename _mbf_type = multi_bf_type < basis_functor < _math_ele_type > , vigra::ExpandElementResult < _coordinate_type > :: size >>
class vspline::evaluator< _coordinate_type, _trg_type, _vsize, _specialize, _math_ele_type, _cf_type, _mbf_type >
class evaluator encodes evaluation of a spline-like object. This is a generalization of b-spline evaluation, which is the default if no other basis functions are specified. Technically, a vspline::evaluator is a vspline::unary_functor, which has the specific capability of evaluating a specific spline-like object. This makes it a candidate to be passed to the functions in transform.h, like remap() and transform(), and it also makes it suitable for vspline's functional constructs like chaining, mapping, etc.
While creating and using vspline::evaluators is simple enough, especially from vspline::bspline objects, there are also factory functions producing objects capable of evaluating a b-spline. These objects are wrappers around a vspline::evaluator, please see the factory functions make_evaluator() and make_safe_evaluator() at the end of this file.
If you don't want to concern yourself with the details, the easiest way is to have a bspline object handy and use one of the factory functions, assigning the resulting functor to an auto variable:
// given a vspline::bspline object 'bspl' // create an object (a functor) which can evaluate the spline auto ev = vspline::make_safe_evaluator ( bspl ) ; // which can be used like this: auto value = ev ( real_coordinate ) ;
The evaluation relies on 'braced' coefficients, as they are provided by a vspline::bspline object. While the most general constructor will accept a raw pointer to coefficients (enclosed in the necessary 'brace'), this will rarely be used, and an evaluator will be constructed from a bspline object. To create an evaluator directly, the specific type of evaluator has to be established by providing the relevant template arguments. We need at least two types: the 'coordinate type' and the 'value type':
- The coordinate type is encoded as a vigra::TinyVector of some real data type - doing image processing, the typical type would be a vigra::TinyVector < float , 2 >. fundamental real types are also accepted (for 1D splines). There is also specialized code for incoming discrete coordinates, which can be evaluated more quickly, So if the caller only evaluates at discrete locations, it's good to actually pass the discrete coordinates in, rather than converting them to some real type and passing the real equivalent.
- The value type will usually be either a fundamental real data type such as 'float', or a vigra::TinyVector of such an elementary type. Other data types which can be handled by vigra's ExpandElementResult mechanism should also work. When processing colour images, your value type would typically be a vigra::TinyVector < float , 3 >. You can also process integer-valued data, in which case you may suffer from quantization errors, so you should make sure your data cover the dynamic range of the integer type used as best as possible (like by using the 'boost' parameter when prefiltering the b-spline). Processing of integer-valued data is done using floating point arithmetics internally, so the quantization error occurs when the result is ready and assigned to an integer target type; if the target data type is real, the result is precise (within arithmetic precision).
you can choose the data type which is used internally to do computations. per default, this will be a real type 'appropriate' to the operation, but you're free to pick some other type. Note that picking integer types for the purpose is not allowed. The relevant template argument is 'math_ele_type'.
Note that class evaluator operates with 'native' spline coordinates, which run with the coefficient array's core shape, so typically from 0 to M-1 for a 1D spline over M values. Access with different coordinates is most easily done by 'chaining' class evaluator objects to other vspline::unary_functor objects providing coordinate translation, see unary_functor.h, map.h and domain.h.
The 'native' coordinates can be thought of as an extension of the discrete coordinates used to index the spline's knot points. Let's assume you have a 1D spline over knot points in an array 'a'. While you can index 'a' with discrete coordinates like 0, 1, 2... you can evaluate the spline at real coordinates like 0.0, 1.5, 7.8. If a real coordinate has no fractional part, evaluation of the spline at this coordinate will produce the knot point value at the index which is equal to the real coordinate, so the interpolation criterion is fulfilled. vspline does not (currently) provide code for non-uniform splines. For such splines, the control points are not unit-spaced, and possibly also not equally spaced. To evaluate such splines, it's necessary to perform a binary search in the control point vector to locate the interval containing the coordinate in question, subtract the interval's left hand side, then divide by the interval's length. This value constitutes the 'fractional part' or 'delta' used for evaluation, while the interval's ordinal number yields the integral part. Evaluation can then proceed as for uniform splines. A popular subtype of such non-uniform splines are NURBS (non-uniform rational b-splines), which have the constraints that they map a k-D manifold to a k+1-D space, apply weights to the control points and allow coinciding control points. At the core of a NURBS evaluation there is the same evaluation of a uniform spline, but all the operations going on around that are considerable - especially the binary search to locate the correct knot point interval (with it's many conditionals and memory accesses) is very time-consuming.
While the template arguments specify coordinate and value type for unvectorized operation, the types for vectorized operation are inferred from them, using vspline's vector_traits mechanism. The width of SIMD vectors to be used can be chosen explicitly. This is not mandatory - if omitted, a default value is picked.
With the evaluator's type established, an evaluator of this type can be constructed by passing a vspline::bspline object to it's constructor. Usually, the bspline object will contain data of the same value type, and the spline has to have the same number of dimensions as the coordinate type. Alternatively, coefficients can be passed in as a pointer into a field of suitably braced coefficients. It's okay for the spline to hold coefficients of a different type: they will be cast to math_type during the evaluation.
I have already hinted at the evaluation process used, but here it is again in a nutshell: The coordinate at which the spline is to be evaluated is split into it's integral part and a remaining fraction. The integral part defines the location where a window from the coefficient array is taken, and the fractional part defines the weights to use in calculating a weighted sum over this window. This weighted sum represents the result of the evaluation. Coordinate splitting is done with the method split(), which picks the appropriate variant (different code is used for odd and even splines)
The generation of the weights to be applied to the window of coefficients is performed by employing weight functors from basis.h. What's left to do is to bring all the components together, which happens in class inner_evaluator. The workhorse code in the subclass _eval takes care of performing the necessary operations recursively over the dimensions of the spline.
a vspline::evaluator is technically a vspline::unary_functor. This way, it can be directly used by constructs like vspline::chain and has a consistent interface which allows code using evaluators to query it's specifics. Since evaluation uses no conditionals on the data path, the whole process can be formulated as a set of templated member functions using vectorized types or unvectorized types, so the code itself is vector-agnostic. This makes for a nicely compact body of code inside class inner_evaluator, at the cost of having to provide a bit of collateral code to make data access syntactically uniform, which is done with inner_evaluator's 'load' method.
The evaluation strategy is to have all dependencies of the evaluation except for the actual coordinates taken care of by the constructor - and immutable for the evaluator's lifetime. The resulting object has no state which is modified after construction, making it thread-safe. It also constitutes a 'pure' functor in a functional-programming sense, because it has no mutable state and no side-effects, as can be seen by the fact that the 'eval' methods are all marked const.
By providing the evaluation in this way, it becomes easy for calling code to integrate the evaluation into more complex functors. Consider, for example, code which generates coordinates with a functor, then evaluates a b-spline at these coordinates, and finally subjects the resultant values to some postprocessing. All these processing steps can be bound into a single functor, and the calling code can be reduced to polling this functor until it has provided the desired number of output values. vspline has a large body of code to this effect: the family of 'transform' functions. These functions accept functors and 'wield' them over arrays of data. The functors may be of class evaluator, but it's much nicer to have a generalized function which can use any functor with the right signature, because it widens the scope of the transform faimily to deal with any functor producing an m-dimensional output from an n-dimensional input. vspline's companion program, pv, uses this method to create complex pixel pipelines by 'chaining' functors, and then, finally, uses functions from vspline's transform family to 'roll out' the pixel pipeline code over large arrays of data.
An aside: vspline::unary_functor, from which class evaluator inherits, provides convenience code to use unary_functor objects with 'normal' function syntax. So if you have an evaluator e, you can write code like y = e ( x ), which is equivalent to the notation I tend to use: e ( x , y ) - this two-argument form, where the first argumemt is a const reference to the input and the second one a reference to the output has some technical advantages (easier ATD).
While the 'unspecialized' evaluator will try and do 'the right thing' by using general purpose code fit for all eventualities, for time-critical operation there are specializations which can be used to make the code faster:
- template argument 'specialize' can be set to 0 to forcibly use (more efficient) nearest neighbour interpolation, which has the same effect as simply running with degree 0 but avoids code which isn't needed for nearest neighbour interpolation (like the application of weights, which is futile under the circumstances, the weight always being 1.0). specialize can also be set to 1 for explicit n-linear interpolation. There, the weight generation is very simple: the 1D case considers only two coefficients, which have weights w and 1-w, respectively. This is better coded explicitly - the 'general' weight generation code produces just the same weights, but not quite as quickly. Any other value for 'specialize' will result in the general-purpose code being used.
Note how the default number of vector elements is fixed by picking the value which vspline::vector_traits considers appropriate. There should rarely be a need to choose a different number of vector elements: evaluation will often be the most computationally intensive part of a processing chain, and therefore this choice is sensible. But it's not mandatory. Just keep in mind that when building processing pipelines with vspline, all their elements must use the same vectorization width. When you leave it to the defaults to set 'vsize', you may get functors which differ in vsize, and when you try to chain them, the code won't compile. So keep this in mind: When building complex functors, pass an explicit value for vsize to all component functors.
So here are the template arguments to class evaluator again, where the first two are mandatory, while the remainder have defaults:
- _coordinate_type: type of a coordinate, where the spline is to be evaluated. This can be either a fundamental type like float or double, or a vigra::TinyVector of as many elements as the spline has dimensions. discrete coordinates are okay and produce specialized, faster code.
- _trg_type: this is the data type the evaluation will produce as it's result. While internally all arithmetic is done in 'math_type', the internal result is cast to _trg_type when it's ready. _trg_type may be a fundamental type or any type known to vigra's ExpandElementResult mechanism, like vigra::TinyVector. It has to have as many channels as the coefficients of the spline (or the knot point data).
- _vsize: width of SIMD vectors to use for vectorized operation. While class inner_evaluator takes this datum as a template argument to it's eval routine, here it's a template argument to the evaluator class. This is so because class evaluator inherits from vspline::unary_functor, which also requires a specific vector size, because otherwise type erasure using std::functions would not be possible. While you may choose arbitrary _vsize, only small multiples of the hardware vector width of the target machine will produce most efficient code. Passing 1 here will result in unvectorized code.
- specialize can be used to employ more efficient code for degree-0 and degree-1 splines (aka nearest-neighbour and linear interpolation). Pass 0 for degree 0, 1 for degree 1 and -1 for any other degree.
- _math_ele_type: elementary type to use for arithemtics in class inner_evaluator. While in most cases default_math_type will be just right, the default may be overridden. _math_ele_type must be a real data type.
- cf_type: data type of the coefficients of the spline. Normally this will be the same as _trg_type above, but this is not mandatory. The coefficients will be converted to math_type once they have been loaded from memory, and all arithmetic is done in math_type, until finally the result is converted to _trg_type.
- _mbf_type: This defines the basis functors. For b-splines, this is a multi_bf_type of as many vspline::basis_functors as the spline's dimensions. Making this type a template argument opens the code up to the use of arbitrary basis functions, as long as all the basis functors are of the same type. To use differently-typed basis functors, erase their type using bf_grok_type (see above)
Definition at line 1708 of file eval.h.