vspline 1.1.0
Generic C++11 Code for Uniform B-Splines
unary_functor.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 unary_functor.h
41
42 \brief interface definition for unary functors
43
44 vspline's evaluation and remapping code relies on a unary functor template
45 which is used as the base for vspline::evaluator and also constitutes the
46 type of object accepted by most of the functions in transform.h.
47
48 This template produces functors which are meant to yield a single output
49 for a single input, where both the input and output types may be single
50 types or vigra::TinyVectors, and their elementary types may be vectorized.
51 The functors are expected to provide methods named eval() which are capable
52 of performing the required functionality. These eval routines take both
53 their input and output by reference - the input is taken by const &, and the
54 output as plain &. The result type of the eval routines is void. While
55 such unary functors can be hand-coded, the class template 'unary_functor'
56 provides services to create such functors in a uniform way, with a specifc
57 system of associated types and some convenience code. Using unary_functor
58 is meant to facilitate the creation of the unary functors used in vspline.
59
60 Using unary_functor generates objects which can be easily combined into
61 more complex unary functors, a typical use would be to 'chain' two
62 unary_functors, see class template 'chain_type' below, which also provides
63 an example for the use of unary_functor.
64
65 class unary_functor takes three template arguments:
66
67 - the argument type, IN
68
69 - the result type, OUT
70
71 - the number of fundamentals (float, int etc.) in a vector, _vsize
72
73 The vectorized argument and result type are deduced from IN, OUT and
74 _vsize by querying vspline::vector_traits. When using Vc (-DUSE_VC),
75 these types will be Vc::SimdArrays if the elementary type can be used
76 to form a SimdArray. Otherwise vspline provides a fallback type emulating
77 vectorization: vspline::simd_type. This fallback type emulates just enough
78 of SimdArray's capabilities to function as a replacement inside vspline's
79 body of code.
80
81 So where is eval() or operator()? Not in class unary_functor. The actual
82 functionality is provided by the derived class. There is deliberately no
83 code concerning evaluation in class unary_functor. My initial implementation
84 had pure virtual functions to define the interface for evaluation, but this
85 required explicitly providing the overloads in the derived class. Simply
86 omitting any reference to evaluation allows the derived class to accomplish
87 evaluation with a template if the code is syntactically the same for vectorized
88 and unvectorized operation. To users of concrete functors inheriting from
89 unary_functor this makes no difference. The only drawback is that it's not
90 possible to perform evaluation via a base class pointer or reference. But
91 this is best avoided anyway because it degrades performance. If the need arises
92 to have several unary_functors with the same template signature share a common
93 type, there's a mechanism to make the internals opaque by 'grokking'.
94 grokking provides a wrapper around a unary_functor which hides it's type,
95 vspline::grok_type directly inherits from unary_functor and the only template
96 arguments are IN, OUT and _vsize. This hurts performance a little - just as
97 calling via a base class pointer/reference would, but the code is outside
98 class unary_functor and therefore only activated when needed.
99
100 Class vspline::evaluator is itself coded as a vspline::unary_functor and can
101 serve as another example for the use of the code in this file.
102
103 Before the introduction of vspline::simd_type, vectorization was done with
104 Vc or not at all. Now vspline::vector_traits will produce Vc types if
105 possible and vspline::simd_type otherwise. This breaks code relying on
106 the fallback to scalar without Vc, and it also breaks code that assumes that
107 Vc is the sole method of vectorization.
108
109 Extant code written for use with Vc should function as before as long as
110 USE_VC is defined. It may be possible now to use such code even without Vc.
111 This depends on how much of Vc::SimdArray's functionality is used. If such
112 code runs without Vc, it may still not perform well and possibly even worse
113 than scalar code.
114*/
115
116#ifndef VSPLINE_UNARY_FUNCTOR_H
117#define VSPLINE_UNARY_FUNCTOR_H
118
119#include <functional>
120#include "common.h"
121
122namespace vspline {
123
124/// we derive all vspline::unary_functors from this empty class, to have
125/// a common base type for all of them. This enables us to easily check if
126/// a type is a vspline::unary_functor without having to wrangle with
127/// unary_functor's template arguments.
128
129template < size_t _vsize >
131
132/// class unary_functor provides a functor object which offers a system
133/// of types for concrete unary functors derived from it. If vectorization
134/// isn't used, this is trivial, but with vectorization in use, we get
135/// vectorized types derived from plain IN and OUT via query of
136/// vspline::vector_traits.
137///
138/// class unary_functor itself does not provide operator(), this is left to
139/// the concrete functors inheriting from unary_functor. It is expected
140/// that the derived classes provide evaluation capability, either as a
141/// method template or as (overloaded) method(s) 'eval'. eval is to be coded
142/// as taking it's first argument as a const&, and writing it's result to
143/// it's second argument, which it receives by reference. eval's return type
144/// is void. Inside vspline, classes derived from unary_functor do provide
145/// operator(), so instances of these objects can be called with function
146/// call syntax as well.
147///
148/// Why not lay down an interface with a pure virtual function eval()
149/// which derived classes would need to override? Suppose you had, in
150/// unary_functor,
151///
152/// virtual void eval ( const in_type & in , out_type & out ) = 0 ;
153///
154/// Then, in a derived class, you'd have to provide an override with this
155/// signature. Initially, this seems reasonable enough, but if you want to
156/// implement eval() as a member function template in the derived class, you
157/// still would have to provide the override (calling an instantiated version
158/// of your template), because your template won't be recognized as a viable
159/// way to override the pure virtual base class member function. Since
160/// providing eval as a template is common (oftentimes vectorized and
161/// unvectorized code are the same) I've decided against having virtual eval
162/// routines, to avoid the need for explicitly overriding them in derived
163/// classes which provide eval() as a template.
164///
165/// How about providing operator() in unary_functor? We might add the derived
166/// class to the template argument list and use unary_functor with CRP. I've
167/// decideded against this and instead provide callability as a mixin to be
168/// used as needed. This keeps the complexity of unary_functor-derived objects
169/// low, adding the extra capability only where it's deemed appropriate. For
170/// the mixin, see class 'callable' further down.
171///
172/// With no virtual member functions, class unary_functor becomes very simple,
173/// which is desirable from a design standpoint, and also makes unary_functors
174/// smaller, avoiding the creation of the virtual function table.
175///
176/// The type system used in unary_functor is taken from vspline::vector_traits,
177/// additionally prefixing the types with in_ and out_, for input and output
178/// types. The other elements of the type names are the same as in
179/// vector_traits.
180
181// KFJ 2020-11-15 it does no harm defining that the default for
182// OUT should be IN, and this is a very common situation.
183
184template < typename IN , // argument or input type
185 typename OUT = IN , // result type
186 size_t _vsize = vspline::vector_traits < IN > :: size
187 >
189: public unary_functor_tag < _vsize >
190{
191 static const bool has_capped_eval = false ;
192
193 // number of fundamentals in simdized data. If vsize is 1, the vectorized
194 // types will 'collapse' to the unvectorized types.
195
196 enum { vsize = _vsize } ;
197
198 // number of dimensions. This may well be different for IN and OUT.
199
200 enum { dim_in = vspline::vector_traits < IN > :: dimension } ;
201 enum { dim_out = vspline::vector_traits < OUT > :: dimension } ;
202
203 // typedefs for incoming (argument) and outgoing (result) type. These two types
204 // are non-vectorized types, like vigra::TinyVector < float , 2 >. Since such types
205 // consist of elements of the same type, the corresponding vectorized type can be
206 // easily automatically determined.
207
208 typedef IN in_type ;
209 typedef OUT out_type ;
210
211 // elementary types of same. we rely on vspline::vector_traits to provide
212 // these types.
213
214 typedef typename vspline::vector_traits < IN > :: ele_type in_ele_type ;
215 typedef typename vspline::vector_traits < OUT > :: ele_type out_ele_type ;
216
217 // 'synthetic' types for input and output. These are always TinyVectors,
218 // possibly of only one element, of the elementary type of in_type/out_type.
219 // On top of providing a uniform container type (the TinyVector) the
220 // synthetic type is also 'unaware' of any specific meaning the 'true'
221 // input/output type may have, and arithmetic operations on the synthetic
222 // types won't clash with arithmetic defined for the 'true' types.
223
224 typedef vigra::TinyVector < in_ele_type , dim_in > in_nd_ele_type ;
225 typedef vigra::TinyVector < out_ele_type , dim_out > out_nd_ele_type ;
226
227 // for vectorized operation, we need a few extra typedefs. I use a _v
228 // suffix instead of the _type suffix above to indicate vectorized types.
229 // If vsize is 1, the _v types simply collapse to the unvectorized
230 // types, having them does no harm, but it's not safe to assume that,
231 // for example, in_v and in_type are in fact different types.
232
233 /// a simdized type of the elementary type of result_type,
234 /// which is used for coefficients and results. this is fixed via
235 /// the traits class vector_traits (in vector.h). Note how we derive
236 /// this type using vsize from the template argument, not what
237 /// vspline::vector_traits deems appropriate for ele_type - though
238 /// both numbers will be the same in most cases.
239
240 typedef typename vector_traits < IN , vsize > :: ele_v in_ele_v ;
241 typedef typename vector_traits < OUT , vsize > :: ele_v out_ele_v ;
242
243 // 'synthetic' types for simdized input and output. These are always
244 // TinyVectors, possibly of only one element, of the simdized input
245 // and output type.
246
247 typedef typename vector_traits < IN , vsize > :: nd_ele_v in_nd_ele_v ;
248 typedef typename vector_traits < OUT , vsize > :: nd_ele_v out_nd_ele_v ;
249
250 /// vectorized in_type and out_type. vspline::vector_traits supplies these
251 /// types so that multidimensional/multichannel data come as vigra::TinyVectors,
252 /// while 'singular' data won't be made into TinyVectors of one element.
253
254 typedef typename vector_traits < IN , vsize > :: type in_v ;
255 typedef typename vector_traits < OUT , vsize > :: type out_v ;
256
257 /// vsize wide vector of ints, used for gather/scatter indexes
258
259 typedef typename vector_traits < int , vsize > :: ele_v ic_v ;
260
261} ;
262
263// KFJ 2018-07-14
264// To deal with an issue with cppyy, which has trouble processing
265// templated operator() into an overloaded callable, I introduce this
266// mixin, which specifically provides two distinct operator() overloads.
267// This is also a better way to introduce the callable quality, since on
268// the side of the derived class it requires only inheriting from the
269// mixin, rather than the verbose templated operator() I used previously.
270// This is still experimental.
271
272/// mixin 'callable' is used with CRTP: it serves as additional base to
273/// unary functors which are meant to provide operator() and takes the
274/// derived class as it's first template argument, followed be the
275/// argument types and vectorization width, so that the parameter and
276/// return type for operator() and - if vsize is greater than 1 - it's
277/// vectorized overload can be produced.
278/// This formulation has the advantage of not having to rely on the
279/// 'out_type_of' mechanism I was using before and provides precisely
280/// the operator() overload(s) which are appropriate.
281
282template < class derived_type ,
283 typename IN ,
284 typename OUT = IN ,
285 size_t vsize = vspline::vector_traits < IN > :: size
286 >
288{
289 // using a cl_ prefix here for the vectorized types to avoid a name
290 // clash with class unary_functor, which also defines in_v, out_v
291
292 typedef typename vector_traits < IN , vsize > :: type cl_in_v ;
293 typedef typename vector_traits < OUT , vsize > :: type cl_out_v ;
294
295 OUT operator() ( const IN & in ) const
296 {
297 auto self = static_cast < const derived_type * const > ( this ) ;
298 OUT out ;
299 self->eval ( in , out ) ;
300 return out ;
301 }
302
303 OUT operator() ( const IN & in )
304 {
305 auto self = static_cast < derived_type * > ( this ) ;
306 OUT out ;
307 self->eval ( in , out ) ;
308 return out ;
309 }
310
311 template < typename = std::enable_if < ( vsize > 1 ) > >
312 cl_out_v operator() ( const cl_in_v & in ) const
313 {
314 auto self = static_cast < const derived_type * const > ( this ) ;
315 cl_out_v out ;
316 self->eval ( in , out ) ;
317 return out ;
318 }
319
320 template < typename = std::enable_if < ( vsize > 1 ) > >
322 {
323 auto self = static_cast < derived_type * > ( this ) ;
324 cl_out_v out ;
325 self->eval ( in , out ) ;
326 return out ;
327 }
328} ;
329
330/// eval_wrap is a helper function template, wrapping an 'ordinary'
331/// function which returns some value given some input in a void function
332/// taking input as const reference and writing output to a reference,
333/// which is the signature used for evaluation in vspline::unary_functors.
334
335template < class IN , class OUT >
336std::function < void ( const IN& , OUT& ) >
337eval_wrap ( std::function < OUT ( const IN& ) > f )
338{
339 return [f] ( const IN& in , OUT& out ) { out = f ( in ) ; } ;
340}
341
342/// struct broadcast is a mixin providing an 'eval' method to a functor which
343/// can process vectorized arguments. This mixin is inherited by the functor
344/// missing that capability, using CRTP. Because here, in the providing class,
345/// nothing is known (or, even, knowable) about the functor, we need to pass
346/// additional template arguments to establish the usual vspline unary functor
347/// frame of reference, namely in_type, out_type, vsize etc.
348/// The resulting 'vectorized' eval may not be efficient: it has to build
349/// individual 'in_type' values from the vectorized input, process them with
350/// the derived functor's eval routine, then insert the resulting out_type in
351/// the vectorized output. But it's a quick way of getting vectorized evaluation
352/// capability without writing the vector code. This is particularly useful when
353/// the functor's unvectorized eval() is complex (like, calling into legacy code
354/// or even into opaque binary) and 'proper' vectorization is hard to do.
355/// And with a bit of luck, the optimizer 'recognizes' what's going on and
356/// produces SIMD code anyway.
357/// Note that the derived class needs a using declaration for the vectorized
358/// eval overload inherited from this base class - see broadcast_type (below)
359/// for an example of using this mixin.
360
361template < class derived_type ,
362 typename IN ,
363 typename OUT = IN ,
364 size_t _vsize = vspline::vector_traits < IN > :: size
365 >
367: public vspline::unary_functor < IN , OUT , _vsize >
368{
370
374
375 using typename base_type::in_type ;
376 using typename base_type::out_type ;
377 using typename base_type::in_ele_type ;
378 using typename base_type::out_ele_type ;
381
382 using typename base_type::in_v ;
383 using typename base_type::out_v ;
384 using typename base_type::in_ele_v ;
385 using typename base_type::out_ele_v ;
386 using typename base_type::in_nd_ele_v ;
387 using typename base_type::out_nd_ele_v ;
388
389 // provide the vectorized eval which broadcasts the unvectorized one.
390 // note that, in the derived functor, you need a using statement, like
391 // using broadcast < derived_functor , float , float , 8 > :: eval ;
392 // to be able to invoke the vectorized eval, if this is missing, only
393 // the unvectorized eval in derived_functor is seen.
394
395 template < typename = std::enable_if < ( vsize > 1 ) > >
396 void eval ( const in_v & inv ,
397 out_v & outv ) const
398 {
399 // to use the derived functor's eval routine, we need:
400
401 auto fp = static_cast < const derived_type * > ( this ) ;
402
403 // we reinterpret input and output as nD types. in_v/out_v are
404 // plain SIMD types if in_type/out_type is fundamental; here we
405 // want a TinyVector of one element in this case.
406
407 const in_nd_ele_v & iv ( reinterpret_cast < const in_nd_ele_v & > ( inv ) ) ;
408 out_nd_ele_v & ov ( reinterpret_cast < out_nd_ele_v & > ( outv ) ) ;
409
410 // we also need a view to an in_type and an out_type object as a nD
411 // entity, even if they are single-channel, so that we can us a loop
412 // to access them, while we need the plain in_type/out_type objects
413 // as arguments to the unvectorized eval routine
414
415 in_nd_ele_type nd_in ;
416 out_nd_ele_type nd_out ;
417
418 in_type & in ( reinterpret_cast < in_type & > ( nd_in ) ) ;
419 out_type & out ( reinterpret_cast < out_type & > ( nd_out ) ) ;
420
421 // now comes the rollout. If either dim_in or dim_out are 1,
422 // the compiler should optimize away the loop.
423
424 for ( int e = 0 ; e < vsize ; e++ )
425 {
426 // extract the eth input value from the simdized input
427
428 for ( int d = 0 ; d < dim_in ; d++ )
429 nd_in [ d ] = iv [ d ] [ e ] ;
430
431 // process it with eval, passing the eval-compatible references
432
433 fp->eval ( in , out ) ;
434
435 // now distribute eval's result to the simdized output
436
437 for ( int d = 0 ; d < dim_out ; d++ )
438 ov [ d ] [ e ] = nd_out [ d ] ;
439 }
440 }
441} ;
442
443// broadcast_type's constructor takes an unvectorized function and
444// 'rolls it out' into a vspline::unary_functor with both unvectorized
445// and vectorized eval methods. The 'vectorized' version produces
446// unvectorized data from the vector(s), processes them with the
447// unvectorized function and writes them to the result vector(s).
448// All of this is not very efficient, unless the compiler 'gets it'
449// And produces 'real' vector code - not too likely, but maybe more
450// so in the future. The code is more for 'quick shots' where vector
451// code is not (yet) available or the effort to write it is not
452// worth while. Another use scenario is calls to opaque code like
453// foreign functions. When using broadcast_type with python code, keep
454// in mind that you shouldn't have multithreading enabled, so
455// define VSPLINE_SINGLETHREAD.
456// The c'tor args allow passing in lambdas, which is extra convenient:
457// vspline::broadcast_type < T > bf ( [] ( T x ) { return x + x ; } ) ;
458
459template < typename I ,
460 typename O = I ,
461 std::size_t S = vspline::vector_traits < I > :: size >
463: public broadcast < broadcast_type < I , O , S > , I , O , S >
464{
465 typedef std::function < void ( const I & , O & ) > eval_type ;
466 typedef std::function < O ( const I & ) > call_type ;
467
468private:
469
470 const eval_type _eval ;
471
472 typedef broadcast < broadcast_type < I , O , S > , I , O , S >
473 base_type ;
474
475public:
476
477 // two c'tor variants, taking the unvectorized function in different
478 // guise
479
481 : _eval ( eval_wrap ( _f ) )
482 { }
483
484 broadcast_type ( const eval_type & _ev )
485 : _eval ( _ev )
486 { }
487
488 // unvectorized eval delegates to _eval
489
490 void eval ( const I & in , O & out ) const
491 {
492 _eval ( in , out ) ;
493 }
494
495 // we need a using declaration to make the 'vectorized' form of 'eval'
496 // from the mixin 'broadcast' available in this (derived) class
497
498 using base_type::eval ;
499} ;
500
501/// class chain_type is a helper class to pass one unary functor's result
502/// as argument to another one. We rely on T1 and T2 to provide a few of the
503/// standard types used in unary functors. Typically, T1 and T2 will both be
504/// vspline::unary_functors, but the type requirements could also be fulfilled
505/// 'manually'.
506///
507/// Note how callability is introduced via the mixin 'vspline::callable'.
508/// The inheritance definition looks confusing, the template arg list reads as:
509/// 'the derived class, followed by the arguments needed to determine the
510/// call signature(s)'. See vspline::callable above.
511
512template < typename T1 ,
513 typename T2 >
515: public vspline::unary_functor < typename T1::in_type ,
516 typename T2::out_type ,
517 T1::vsize > ,
518 public vspline::callable
519 < chain_type < T1 , T2 > ,
520 typename T1::in_type ,
521 typename T2::out_type ,
522 T1::vsize
523 >
524{
525 // definition base_type
526
527 enum { vsize = T1::vsize } ;
528
529 typedef vspline::unary_functor < typename T1::in_type ,
530 typename T2::out_type ,
532
533 using typename base_type::in_type ;
534 using typename base_type::out_type ;
535 using typename base_type::in_v ;
536 using typename base_type::out_v ;
537
538 // we require both functors to share the same vectorization width
539
540 static_assert ( T1::vsize == T2::vsize ,
541 "can only chain unary_functors with the same vector width" ) ;
542
543 static_assert ( std::is_same < typename T1::out_type , typename T2::in_type > :: value ,
544 "chain: output of first functor must match input of second functor" ) ;
545
546 typedef typename T1::out_type intermediate_type ;
547 typedef typename T1::out_v intermediate_v ;
548
549 // hold the two functors by value
550
551 const T1 t1 ;
552 const T2 t2 ;
553
554 // the constructor initializes them
555
556 chain_type ( const T1 & _t1 , const T2 & _t2 )
557 : t1 ( _t1 ) ,
558 t2 ( _t2 )
559 { } ;
560
561 // the actual eval needs a bit of trickery to determine the type of
562 // the intermediate type from the type of the first argument.
563
564 void eval ( const in_type & argument ,
565 out_type & result ) const
566 {
567 intermediate_type intermediate ;
568 t1.eval ( argument , intermediate ) ; // evaluate first functor into intermediate
569 t2.eval ( intermediate , result ) ; // feed it as input to second functor
570 }
571
572 template < typename = std::enable_if < ( vsize > 1 ) > >
573 void eval ( const in_v & argument ,
574 out_v & result ) const
575 {
576 intermediate_v intermediate ;
577 t1.eval ( argument , intermediate ) ; // evaluate first functor into intermediate
578 t2.eval ( intermediate , result ) ; // feed it as input to second functor
579 }
580
581} ;
582
583/// chain is a factory function yielding the result of chaining
584/// two unary_functors.
585
586template < class T1 , class T2 >
588chain ( const T1 & t1 , const T2 & t2 )
589{
590 return vspline::chain_type < T1 , T2 > ( t1 , t2 ) ;
591}
592
593/// using operator overloading, we can exploit operator+'s semantics
594/// to chain several unary functors. We need to specifically enable
595/// this for types derived from unary_functor_tag to avoid a catch-all
596/// situation.
597
598template < typename T1 ,
599 typename T2 ,
600 typename enable = typename
601 std::enable_if
602 < std::is_base_of
604 T1
605 > :: value
606 && std::is_base_of
608 T2
609 > :: value
610 > :: type
611 >
613operator+ ( const T1 & t1 , const T2 & t2 )
614{
615 return vspline::chain ( t1 , t2 ) ;
616}
617
618/// class grok_type is a helper class wrapping a vspline::unary_functor
619/// so that it's type becomes opaque - a technique called 'type erasure',
620/// here applied to vspline::unary_functors with their specific
621/// capability of providing both vectorized and unvectorized operation
622/// in one common object.
623///
624/// While 'grokking' a unary_functor may degrade performance slightly,
625/// the resulting type is less complex, and when working on complex
626/// constructs involving several unary_functors, it can be helpful to
627/// wrap the whole bunch into a grok_type for some time to make compiler
628/// messages more palatable. I even suspect that the resulting functor,
629/// which simply delegates to two std::functions, may optimize better at
630/// times than a more complex functor in the 'grokkee'.
631///
632/// Performance aside, 'grokking' a vspline::unary_functor produces a
633/// simple, consistent type that can hold *any* unary_functor with the
634/// given input and output type(s), so it allows to hold and use a
635/// variety of (intrinsically differently typed) functors at runtime
636/// via a common handle which is a vspline::unary_functor itself and
637/// can be passed to the transform-type routines. With unary_functors
638/// being first-class, copyable objects, this also makes it possible
639/// to pass around unary_functors between different TUs where user
640/// code can provide new functors at will which can simply be used
641/// without having to recompile to make their type known, at the cost
642/// of a call through a std::function.
643///
644/// grok_type also provides a convenient way to introduce functors into
645/// vspline. Since the functionality is implemented with std::functions,
646/// we allow direct initialization of these std::functions on top of
647/// 'grokking' the capabilities of another unary_functor via lambda
648/// expressions.
649///
650/// Another aspect to grokking is keeping the binary compact. Suppose you
651/// have an operation which is polymorphic with several template args.
652/// When you instantiate all possible combinations, you end up with
653/// a large number of types, each with it's own binary representation.
654/// Type erasure can significantly reduce this effect by bundling
655/// a whole set of types into a single type. This is especially welcome
656/// when working with functor composition: You can build up a variety of
657/// functors addressing a specific aspect of your functionality and
658/// 'grok' the result, so you only have one type left, which is much
659/// easier to handle: you can pass it around and introduce it's type
660/// into other parts of your code without having to deal with the
661/// complexity of the originla grokked type family.
662///
663/// For grok_type objects where _vsize is greater 1, there are
664/// constructor overloads taking only a single function. These
665/// constructors broadcast the unvectorized function to process
666/// vectorized data, providing a quick way to produce code which
667/// runs with vector data, albeit less efficiently than true vector
668/// code.
669///
670/// finally, for convenience, grok_type also provides operator(),
671/// to use the grok_type object with function call syntax, and it
672/// also provides the common 'eval' routine(s), just like any other
673/// unary_functor.
674
675template < typename IN , // argument or input type
676 typename OUT = IN , // result type
677 size_t _vsize = vspline::vector_traits < IN > :: size
678 >
680: public vspline::unary_functor < IN , OUT , _vsize > ,
681 public vspline::callable < grok_type < IN , OUT , _vsize > ,
682 IN , OUT , _vsize >
683{
685
686 using base_type::vsize ;
687 using base_type::dim_in ;
688 using base_type::dim_out ;
689
690 using typename base_type::in_type ;
691 using typename base_type::out_type ;
692 using typename base_type::in_v ;
693 using typename base_type::out_v ;
694
695 typedef std::function < void ( const in_type & , out_type & ) > eval_type ;
696 typedef std::function < out_type ( const in_type & ) > call_type ;
697
699
700 operator bool()
701 {
702 return _ev ;
703 }
704
705 // given these types, we can define the types for the std::function
706 // we will use to wrap the grokkee's evaluation code in.
707
708 typedef std::function < void ( const in_v & , out_v & ) > v_eval_type ;
709
710 // this is the class member holding the std::function:
711
713
714 // we also define a std::function type using 'normal' call/return syntax
715
716 typedef std::function < out_v ( const in_v & ) > v_call_type ;
717
718 /// we provide a default constructor so we can create an empty
719 /// grok_type and assign to it later. Calling the empty grok_type's
720 /// eval will result in an exception.
721
722 grok_type() { } ;
723
724 /// direct initialization of the internal evaluation functions
725 /// this overload, with two arguments, specifies the unvectorized
726 /// and the vectorized evaluation function explicitly.
727
728 grok_type ( const eval_type & fev ,
729 const v_eval_type & vfev )
730 : _ev ( fev ) ,
731 _v_ev ( vfev )
732 { } ;
733
734 /// constructor taking a call_type and a v_call_type,
735 /// initializing the two std::functions _ev and _v_ev
736 /// with wrappers around these arguments which provide
737 /// the 'standard' vspline evaluation functor signature
738
740 : _ev ( eval_wrap ( f ) )
741 , _v_ev ( eval_wrap ( vf ) )
742 { } ;
743
744 /// constructor from 'grokkee' using lambda expressions
745 /// to initialize the std::functions _ev and _v_ev.
746 /// we enable this if grokkee_type is a vspline::unary_functor
747
748 template < class grokkee_type ,
749 typename std::enable_if
750 < std::is_base_of
753 > :: value ,
754 int
755 > :: type = 0
756 >
758 : _ev ( [ grokkee ] ( const IN & in , OUT & out )
759 { grokkee.eval ( in , out ) ; } )
760 , _v_ev ( [ grokkee ] ( const in_v & in , out_v & out )
761 { grokkee.eval ( in , out ) ; } )
762 { } ;
763
764 /// constructor taking only an unvectorized evaluation function.
765 /// this function is broadcast, providing evaluation of SIMD types
766 /// with non-vector code, which is less efficient.
767
768 grok_type ( const eval_type & fev )
769 : grok_type ( vspline::broadcast_type < IN , OUT , vsize >
770 ( [fev] ( const IN & in )
771 {
772 OUT out ;
773 fev ( in , out ) ;
774 return out ;
775 }
776 )
777 )
778 { } ;
779
780 /// constructor taking only one call_type, which is also broadcast,
781 /// since the call_type std::function is wrapped to provide a
782 /// std::function with vspline's standard evaluation functor signature
783 /// and the result is fed to the single-argument functor above.
784
785 grok_type ( const call_type & f )
786 : grok_type ( vspline::broadcast_type < IN , OUT , vsize > ( f ) )
787 { } ;
788
789 /// unvectorized evaluation. This is delegated to _ev.
790
791 void eval ( const IN & i , OUT & o ) const
792 {
793 _ev ( i , o ) ;
794 }
795
796 /// vectorized evaluation function template
797 /// the eval overload above will catch calls with (in_type, out_type)
798 /// while this overload will catch vectorized evaluations.
799
800 template < typename = std::enable_if < ( vsize > 1 ) > >
801 void eval ( const in_v & i , out_v & o ) const
802 {
803 _v_ev ( i , o ) ;
804 }
805
806} ;
807
808/// specialization of grok_type for _vsize == 1
809/// this is the only possible specialization if vectorization is not used.
810/// here we don't use _v_ev but only the unvectorized evaluation.
811
812template < typename IN , // argument or input type
813 typename OUT // result type
814 >
815struct grok_type < IN , OUT , 1 >
816: public vspline::unary_functor < IN , OUT , 1 > ,
817 public vspline::callable < grok_type < IN , OUT , 1 > ,
818 IN , OUT , 1 >
819{
821
822 enum { vsize = 1 } ;
823 using typename base_type::in_type ;
824 using typename base_type::out_type ;
825 using typename base_type::in_v ;
826 using typename base_type::out_v ;
827
828 typedef std::function < void ( const in_type & , out_type & ) > eval_type ;
829 typedef std::function < out_type ( const in_type & ) > call_type ;
830
832
833 grok_type() { } ;
834
835 template < class grokkee_type ,
836 typename std::enable_if
837 < std::is_base_of
840 > :: value ,
841 int
842 > :: type = 0
843 >
845 : _ev ( [ grokkee ] ( const IN & in , OUT & out )
846 { grokkee.eval ( in , out ) ; } )
847 { } ;
848
849 grok_type ( const eval_type & fev )
850 : _ev ( fev )
851 { } ;
852
854 : _ev ( eval_wrap ( f ) )
855 { } ;
856
857 void eval ( const IN & i , OUT & o ) const
858 {
859 _ev ( i , o ) ;
860 }
861
862} ;
863
864/// grok() is the corresponding factory function, wrapping grokkee
865/// in a vspline::grok_type.
866
867template < class grokkee_type >
869 typename grokkee_type::out_type ,
871grok ( const grokkee_type & grokkee )
872{
873 return vspline::grok_type < typename grokkee_type::in_type ,
874 typename grokkee_type::out_type ,
876 ( grokkee ) ;
877}
878
879/// amplify_type amplifies it's input with a factor. If the data are
880/// multi-channel, the factor is multi-channel as well and the channels
881/// are amplified by the corresponding elements of the factor.
882/// I added this class to make work with integer-valued splines more
883/// comfortable - if these splines are prefiltered with 'boost', the
884/// effect of the boost has to be reversed at some point, and amplify_type
885/// does just that when you use 1/boost as the 'factor'.
886
887template < class _in_type ,
888 class _out_type = _in_type ,
889 class _math_type = _in_type ,
892: public vspline::unary_functor < _in_type , _out_type , _vsize > ,
893 public vspline::callable
894 < amplify_type < _in_type , _out_type , _math_type , _vsize > ,
895 _in_type ,
896 _out_type ,
897 _vsize
898 >
899{
900 typedef typename
902
903 enum { vsize = _vsize } ;
905
906 // TODO: might assert common dimensionality
907
908 using typename base_type::in_type ;
909 using typename base_type::out_type ;
910 using typename base_type::in_v ;
911 using typename base_type::out_v ;
912 using typename base_type::in_nd_ele_v ;
913 using typename base_type::out_nd_ele_v ;
914
915 typedef _math_type math_type ;
916
917 typedef typename vigra::ExpandElementResult < math_type > :: type
919
920 typedef vigra::TinyVector < math_ele_type , dimension > math_nd_ele_type ;
921
924
926
927 // constructors initialize factor. If dimension is greater than 1,
928 // we have two constructors, one taking a TinyVector, one taking
929 // a single value for all dimensions.
930
931 template < typename = std::enable_if < ( dimension > 1 ) > >
932 amplify_type ( const math_type & _factor )
933 : factor ( _factor )
934 { } ;
935
936 amplify_type ( const math_ele_type & _factor )
937 : factor ( _factor )
938 { } ;
939
940 void eval ( const in_type & in , out_type & out ) const
941 {
942 out = out_type ( math_type ( in ) * factor ) ;
943 }
944
945 template < typename = std::enable_if < ( vsize > 1 ) > >
946 void eval ( const in_v & in , out_v & out ) const
947 {
948 // we take a view to the arguments as TinyVectors, even if
949 // the data are 'singular'
950
951 const in_nd_ele_v & _in
952 = reinterpret_cast < in_nd_ele_v const & > ( in ) ;
953
954 const math_nd_ele_type & _factor
955 = reinterpret_cast < math_nd_ele_type const & > ( factor ) ;
956
957 out_nd_ele_v & _out
958 = reinterpret_cast < out_nd_ele_v & > ( out ) ;
959
960 // and perform the application of the factor element-wise
961
962 for ( int i = 0 ; i < dimension ; i++ )
963 vspline::assign ( _out[i] , math_ele_v ( _in[i] ) * _factor[i] ) ;
964 }
965
966} ;
967
968/// flip functor produces it's input with component order reversed.
969/// This can be used to deal with situations where coordinates in
970/// the 'wrong' order have to be fed to a functor expecting the opposite
971/// order and should be a fast way of doing so, since the compiler can
972/// likely optimize it well.
973/// I added this class to provide simple handling of incoming NumPy
974/// coordinates, which are normally in reverse order of vigra coordinates
975
976template < typename _in_type ,
978struct flip
979: public vspline::unary_functor < _in_type , _in_type , _vsize > ,
980 public vspline::callable
981 < flip < _in_type , _vsize > ,
982 _in_type ,
983 _in_type ,
984 _vsize
985 >
986{
987 typedef typename vspline::unary_functor
988 < _in_type , _in_type , _vsize > base_type ;
989
990 enum { vsize = _vsize } ;
992
993 using typename base_type::in_type ;
994 using typename base_type::out_type ;
995 using typename base_type::in_v ;
996 using typename base_type::out_v ;
997 using typename base_type::in_nd_ele_type ;
998 using typename base_type::out_nd_ele_type ;
999 using typename base_type::in_nd_ele_v ;
1000 using typename base_type::out_nd_ele_v ;
1001
1002 void eval ( const in_type & in_ , out_type & out ) const
1003 {
1004 // we need a copy of 'in' in case _in == out
1005
1006 in_type in ( in_ ) ;
1007
1008 // we take a view to the arguments as TinyVectors, even if
1009 // the data are 'singular'
1010
1011 const in_nd_ele_type & _in
1012 = reinterpret_cast < in_nd_ele_type const & > ( in ) ;
1013
1014 out_nd_ele_type & _out
1015 = reinterpret_cast < out_nd_ele_type & > ( out ) ;
1016
1017 for ( int e = 0 ; e < dimension ; e++ )
1018 _out [ e ] = _in [ dimension - e - 1 ] ;
1019 }
1020
1021 template < typename = std::enable_if < ( vsize > 1 ) > >
1022 void eval ( const in_v & in_ , out_v & out ) const
1023 {
1024 // we need a copy of 'in' in case _in == out
1025
1026 in_v in ( in_ ) ;
1027
1028 // we take a view to the arguments as TinyVectors, even if
1029 // the data are 'singular'
1030
1031 const in_nd_ele_v & _in
1032 = reinterpret_cast < in_nd_ele_v const & > ( in ) ;
1033
1034 out_nd_ele_v & _out
1035 = reinterpret_cast < out_nd_ele_v & > ( out ) ;
1036
1037 for ( int e = 0 ; e < dimension ; e++ )
1038 vspline::assign ( _out [ e ] , _in [ dimension - e - 1 ] ) ;
1039 }
1040
1041} ;
1042
1043/// at times we require reading access to an nD array at given coordinates,
1044/// as a functor which, receiving the coordinates, produces the values
1045/// from the array. In the scalar case, this is trivial: if the coordinate
1046/// is integral, we have a simple indexed access, and if it is real, we
1047/// can use std::round to produce a nearby discrete coordinate.
1048/// But for the vectorized case we need a bit more effort: We need to
1049/// translate the access with a vector of coordinates into a gather
1050/// operation. We start out with a generalized template class 'yield-type':
1051
1052template < typename crd_t ,
1053 typename data_t ,
1054 size_t _vsize = vspline::vector_traits < data_t > :: size ,
1055 class enable = void
1056 >
1058{ } ;
1059
1060/// First, we specialize for integral coordinates
1061
1062template < typename T >
1064 typename std::conditional
1065 < std::is_fundamental<T>::value ,
1066 std::is_integral<T> ,
1067 std::is_integral<typename T::value_type>
1068 > :: type ;
1069
1070template < typename crd_t ,
1071 typename data_t ,
1072 size_t _vsize >
1074 < crd_t , data_t , _vsize ,
1075 typename std::enable_if
1076 < crd_integral < crd_t > :: value > :: type >
1077: public unary_functor < crd_t , data_t , _vsize > ,
1078 public vspline::callable
1079 < yield_type < crd_t , data_t , _vsize > ,
1080 crd_t , data_t , _vsize
1081 >
1082{
1083 typedef unary_functor < crd_t ,
1084 data_t ,
1085 _vsize > base_type ;
1086
1087 enum { vsize = _vsize } ;
1088 enum { dimension = base_type::dim_in } ;
1089 enum { channels = base_type::dim_out } ;
1090 using typename base_type::in_type ;
1091 using typename base_type::out_type ;
1092 using typename base_type::in_ele_type ;
1093 using typename base_type::out_ele_type ;
1094 using typename base_type::in_v ;
1095 using typename base_type::out_v ;
1096
1097 typedef typename std::integral_constant
1098 < bool , ( dimension > 1 ) > :: type is_nd_t ;
1099
1100 typedef typename std::integral_constant
1101 < bool , ( channels > 1 ) > :: type is_mc_t ;
1102
1103 typedef vigra::MultiArrayView < dimension , out_type > array_type ;
1105
1106 yield_type ( const array_type & _data )
1107 : data ( _data )
1108 { }
1109
1110private:
1111
1112 // scalar operation: simple indexed access. This is dispatched to via
1113 // the third argument, and for the scalar case, the number of dimensions
1114 // and channels is irrelevant: the array's operator[] knows what to do.
1115
1116 template < typename d_t , typename c_t >
1117 void eval ( const in_type & crd , out_type & v ,
1118 std::true_type ,
1119 d_t ,
1120 c_t
1121 ) const
1122 {
1123 v = data [ crd ] ;
1124 }
1125
1126 // the next four overloads deal with vectorized access.
1127 // first variant: coordinate is 1D, value is single-channel
1128
1129 void eval ( const in_v & crd , out_v & v ,
1130 std::false_type ,
1131 std::false_type ,
1132 std::false_type ) const
1133 {
1134 auto ofs = crd * int(data.stride(0)) ;
1135
1136 data_t * p_src = data.data() ;
1137 v.gather ( p_src , ofs ) ;
1138 }
1139
1140 // second variant: coordinate is 1D, value is multi-channel
1141
1142 void eval ( const in_v & crd , out_v & v ,
1143 std::false_type ,
1144 std::false_type ,
1145 std::true_type ) const
1146 {
1147 auto ofs = crd * int(data.stride(0)) ;
1148 ofs *= channels ;
1149
1150 out_ele_type * p_src = (out_ele_type*) ( data.data() ) ;
1151 for ( int ch = 0 ; ch < channels ; ch++ )
1152 {
1153 v[ch].gather ( p_src + ch , ofs ) ;
1154 }
1155 }
1156
1157 // third variant: coordinate is nD, value is single-channel
1158
1159 void eval ( const in_v & crd , out_v & v ,
1160 std::false_type ,
1161 std::true_type ,
1162 std::false_type ) const
1163 {
1164 auto ofs = crd[0] * int(data.stride(0)) ;
1165 for ( int d = 1 ; d < dimension ; d++ )
1166 {
1167 ofs += crd[d] * int(data.stride(d)) ;
1168 }
1169
1170 out_ele_type * p_src = (out_ele_type*) ( data.data() ) ;
1171 v.gather ( p_src , ofs ) ;
1172 }
1173
1174 // fourth variant: coordinate is nD, value is multi-channel
1175
1176 void eval ( const in_v & crd , out_v & v ,
1177 std::false_type ,
1178 std::true_type ,
1179 std::true_type ) const
1180 {
1181 auto ofs = crd[0] * int(data.stride(0)) ;
1182 for ( int d = 1 ; d < dimension ; d++ )
1183 {
1184 ofs += crd[d] * int(data.stride(d)) ;
1185 }
1186 ofs *= channels ;
1187
1188 out_ele_type * p_src = (out_ele_type*) ( data.data() ) ;
1189 for ( int ch = 0 ; ch < channels ; ch++ )
1190 {
1191 v[ch].gather ( p_src + ch , ofs ) ;
1192 }
1193 }
1194
1195public:
1196
1197 // this is the dispatching function:
1198
1199 template < typename in_t , typename out_t >
1200 void eval ( const in_t & crd , out_t & v ) const
1201 {
1202 typedef typename std::is_same < out_t , out_type > :: type is_scalar_t ;
1203 eval ( crd , v , is_scalar_t() , is_nd_t() , is_mc_t() ) ;
1204 }
1205
1206} ;
1207
1208/// Next, we specialize for real coordinates
1209
1210template < typename crd_t ,
1211 typename data_t ,
1212 size_t _vsize >
1214 < crd_t , data_t , _vsize ,
1215 typename std::enable_if
1216 < ! crd_integral < crd_t > :: value > :: type >
1217: public unary_functor < crd_t , data_t , _vsize > ,
1218 public vspline::callable
1219 < yield_type < crd_t , data_t , _vsize > ,
1220 crd_t , data_t , _vsize
1221 >
1222{
1223 typedef unary_functor < crd_t ,
1224 data_t ,
1225 _vsize > base_type ;
1226
1227 enum { vsize = _vsize } ;
1228 enum { dimension = base_type::dim_in } ;
1229 enum { channels = base_type::dim_out } ;
1230 using typename base_type::in_type ;
1231 using typename base_type::out_type ;
1232 using typename base_type::in_ele_type ;
1233 using typename base_type::out_ele_type ;
1234 using typename base_type::in_v ;
1235 using typename base_type::out_v ;
1236
1237 typedef typename std::integral_constant
1238 < bool , ( dimension > 1 ) > :: type is_nd_t ;
1239
1240 typedef vigra::MultiArrayView < dimension , out_type > array_type ;
1241
1242 typedef typename std::conditional
1243 < dimension == 1 ,
1244 int ,
1245 vigra::TinyVector < int , dimension >
1246 > :: type ic_t ;
1247
1249
1250 const iy_t iy ;
1251
1252 yield_type ( const array_type & _data )
1253 : iy ( _data )
1254 { }
1255
1256private:
1257
1258 // The implementation is simple: we round the coordinate to int and
1259 // delegate to the int version
1260
1261 // scalar operation: simple indexed access, for 1D and nD cases
1262
1263 void eval ( const in_type & crd , out_type & v ,
1264 std::true_type ,
1265 std::false_type ) const
1266 {
1267 iy.eval ( std::round ( crd ) , v ) ;
1268 }
1269
1270 void eval ( const in_type & crd , out_type & v ,
1271 std::true_type ,
1272 std::true_type ) const
1273 {
1274 vigra::TinyVector < int , dimension > icrd ;
1275 for ( int d = 0 ; d < dimension ; d++ )
1276 icrd [ d ] = std::round ( crd [ d ] ) ;
1277 iy.eval ( icrd , v ) ;
1278 }
1279
1280 // vectorized operation, for 1D and nD cases
1281
1282 void eval ( const in_v & crd , out_v & v ,
1283 std::false_type ,
1284 std::false_type ) const
1285 {
1286 typename iy_t::in_v icrd ( round ( crd ) ) ;
1287 iy.eval ( icrd , v ) ;
1288 }
1289
1290 void eval ( const in_v & crd , out_v & v ,
1291 std::false_type ,
1292 std::true_type ) const
1293 {
1294 typename iy_t::in_v icrd ;
1295 for ( int d = 0 ; d < dimension ; d++ )
1296 icrd [ d ] = round ( crd [ d ] ) ;
1297 iy.eval ( icrd , v ) ;
1298 }
1299
1300public:
1301
1302 template < typename in_t , typename out_t >
1303 void eval ( const in_t & crd , out_t & v ) const
1304 {
1305 typedef typename std::is_same < out_t , out_type > :: type is_scalar_t ;
1306 eval ( crd , v , is_scalar_t() , is_nd_t() ) ;
1307 }
1308
1309} ;
1310
1311/// while 'normal' unary_functors are all derived from unary_functor_tag,
1312/// sink functors will be derived from sink_functor_tag.
1313
1314template < size_t _vsize >
1316
1317/// sink_functor is used for functors without an output - e.g. reductors
1318/// which are used for analytic purposes on data sets. They use the same
1319/// system of input types, but omit the output types.
1320
1321template < typename IN , // argument or input type
1322 size_t _vsize = vspline::vector_traits < IN > :: size
1323 >
1325: public sink_functor_tag < _vsize >
1326{
1327 // number of fundamentals in simdized data. If vsize is 1, the vectorized
1328 // types will 'collapse' to the unvectorized types.
1329
1330 enum { vsize = _vsize } ;
1331
1332 // number of dimensions.
1333
1334 enum { dim_in = vspline::vector_traits < IN > :: dimension } ;
1335
1336 // typedefs for incoming (argument) type. This type is an
1337 // unvectorized type, like vigra::TinyVector < float , 2 >.
1338 // Since such types consist of elements of the same type,
1339 // the corresponding vectorized type can be easily determined.
1340
1341 typedef IN in_type ;
1342
1343 // elementary types of same. we rely on vspline::vector_traits to provide
1344 // these types.
1345
1346 typedef typename vspline::vector_traits < IN > :: ele_type in_ele_type ;
1347
1348 // 'synthetic' type for input. This is always a TinyVector, possibly
1349 // of only one element, of the elementary type of in_type.
1350 // On top of providing a uniform container type (the TinyVector) the
1351 // synthetic type is also 'unaware' of any specific meaning the 'true'
1352 // input type may have, and arithmetic operations on the synthetic
1353 // types won't clash with arithmetic defined for the 'true' types.
1354
1355 typedef vigra::TinyVector < in_ele_type , dim_in > in_nd_ele_type ;
1356
1357 // for vectorized operation, we need a few extra typedefs. I use a _v
1358 // suffix instead of the _type suffix above to indicate vectorized types.
1359 // If vsize is 1, the _v types simply collapse to the unvectorized
1360 // types, having them does no harm, but it's not safe to assume that,
1361 // for example, in_v and in_type are in fact different types.
1362
1363 /// a simdized type of the elementary type of result_type,
1364 /// which is used for coefficients and results. this is fixed via
1365 /// the traits class vector_traits (in vector.h). Note how we derive
1366 /// this type using vsize from the template argument, not what
1367 /// vspline::vector_traits deems appropriate for ele_type - though
1368 /// both numbers will be the same in most cases.
1369
1370 typedef typename vector_traits < IN , vsize > :: ele_v in_ele_v ;
1371
1372 // 'synthetic' types for simdized input. This is always a
1373 // TinyVector, possibly of only one element, of the simdized input
1374 // and output type.
1375
1376 typedef typename vector_traits < IN , vsize > :: nd_ele_v in_nd_ele_v ;
1377
1378 /// vectorized in_type. vspline::vector_traits supplies this
1379 /// type so that multidimensional/multichannel data come as
1380 /// vigra::TinyVectors, while 'singular' data won't be made
1381 /// into TinyVectors of one element.
1382
1383 typedef typename vector_traits < IN , vsize > :: type in_v ;
1384
1385 /// vsize wide vector of ints, used for gather/scatter indexes
1386
1387 typedef typename vector_traits < int , vsize > :: ele_v ic_v ;
1388
1389} ;
1390
1391} ; // end of namespace vspline
1392
1393#endif // VSPLINE_UNARY_FUNCTOR_H
1394
definitions common to all files in this project, utility code
@ vsize
Definition: eval.cc:96
Definition: basis.h:79
vspline::chain_type< T1, T2 > operator+(const T1 &t1, const T2 &t2)
using operator overloading, we can exploit operator+'s semantics to chain several unary functors....
void assign(T &t, const U &u)
Definition: vector.h:473
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::chain_type< T1, T2 > chain(const T1 &t1, const T2 &t2)
chain is a factory function yielding the result of chaining two unary_functors.
typename std::conditional< std::is_fundamental< T >::value, std::is_integral< T >, std::is_integral< typename T::value_type > > ::type crd_integral
First, we specialize for integral coordinates.
std::function< void(const IN &, OUT &) > eval_wrap(std::function< OUT(const IN &) > f)
eval_wrap is a helper function template, wrapping an 'ordinary' function which returns some value giv...
grokkee_type is a vspline::unary_functor returning twice it's input
Definition: grok.cc:73
void eval(const IN &in, OUT &out) const
Definition: grok.cc:75
amplify_type amplifies it's input with a factor. If the data are multi-channel, the factor is multi-c...
vspline::vector_traits< math_ele_type, vsize >::type math_ele_v
vector_traits< IN, vsize >::nd_ele_v in_nd_ele_v
vigra::ExpandElementResult< math_type >::type math_ele_type
vector_traits< IN, vsize >::type in_v
vectorized in_type and out_type. vspline::vector_traits supplies these types so that multidimensional...
amplify_type(const math_type &_factor)
vector_traits< OUT, vsize >::nd_ele_v out_nd_ele_v
const math_type factor
vector_traits< OUT, vsize >::type out_v
vspline::unary_functor< _in_type, _out_type, _vsize > base_type
vigra::TinyVector< math_ele_type, dimension > math_nd_ele_type
amplify_type(const math_ele_type &_factor)
void eval(const in_v &in, out_v &out) const
void eval(const in_type &in, out_type &out) const
broadcast_type(const eval_type &_ev)
std::function< void(const I &, O &) > eval_type
std::function< O(const I &) > call_type
void eval(const I &in, O &out) const
broadcast_type(const call_type &_f)
struct broadcast is a mixin providing an 'eval' method to a functor which can process vectorized argu...
vigra::TinyVector< in_ele_type, dim_in > in_nd_ele_type
vector_traits< IN, vsize >::nd_ele_v in_nd_ele_v
vigra::TinyVector< out_ele_type, dim_out > out_nd_ele_type
vector_traits< IN, vsize >::type in_v
vectorized in_type and out_type. vspline::vector_traits supplies these types so that multidimensional...
vspline::unary_functor< IN, OUT, _vsize > base_type
vector_traits< OUT, vsize >::nd_ele_v out_nd_ele_v
vector_traits< OUT, vsize >::type out_v
void eval(const in_v &inv, out_v &outv) const
mixin 'callable' is used with CRTP: it serves as additional base to unary functors which are meant to...
vector_traits< OUT, vsize >::type cl_out_v
OUT operator()(const IN &in) const
vector_traits< IN, vsize >::type cl_in_v
class chain_type is a helper class to pass one unary functor's result as argument to another one....
vspline::unary_functor< typename T1::in_type, typename T2::out_type, vsize > base_type
chain_type(const T1 &_t1, const T2 &_t2)
vector_traits< IN, vsize >::type in_v
vectorized in_type and out_type. vspline::vector_traits supplies these types so that multidimensional...
void eval(const in_type &argument, out_type &result) const
vector_traits< OUT, vsize >::type out_v
void eval(const in_v &argument, out_v &result) const
T1::out_v intermediate_v
T1::out_type intermediate_type
flip functor produces it's input with component order reversed. This can be used to deal with situati...
void eval(const in_type &in_, out_type &out) const
vigra::TinyVector< in_ele_type, dim_in > in_nd_ele_type
vspline::unary_functor< _in_type, _in_type, _vsize > base_type
vector_traits< IN, vsize >::nd_ele_v in_nd_ele_v
vigra::TinyVector< out_ele_type, dim_out > out_nd_ele_type
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 >::nd_ele_v out_nd_ele_v
vector_traits< OUT, vsize >::type out_v
void eval(const in_v &in_, out_v &out) const
void eval(const IN &i, OUT &o) const
grok_type(const eval_type &fev)
std::function< void(const in_type &, out_type &) > eval_type
std::function< out_type(const in_type &) > call_type
vspline::unary_functor< IN, OUT, 1 > base_type
grok_type(grokkee_type grokkee)
class grok_type is a helper class wrapping a vspline::unary_functor so that it's type becomes opaque ...
grok_type(call_type f, v_call_type vf)
constructor taking a call_type and a v_call_type, initializing the two std::functions _ev and _v_ev w...
void eval(const in_v &i, out_v &o) const
vectorized evaluation function template the eval overload above will catch calls with (in_type,...
grok_type()
we provide a default constructor so we can create an empty grok_type and assign to it later....
void eval(const IN &i, OUT &o) const
unvectorized evaluation. This is delegated to _ev.
std::function< out_v(const in_v &) > v_call_type
std::function< out_type(const in_type &) > call_type
vector_traits< IN, vsize >::type in_v
vectorized in_type and out_type. vspline::vector_traits supplies these types so that multidimensional...
grok_type(grokkee_type grokkee)
constructor from 'grokkee' using lambda expressions to initialize the std::functions _ev and _v_ev....
vector_traits< OUT, vsize >::type out_v
std::function< void(const in_type &, out_type &) > eval_type
grok_type(const eval_type &fev, const v_eval_type &vfev)
direct initialization of the internal evaluation functions this overload, with two arguments,...
grok_type(const call_type &f)
constructor taking only one call_type, which is also broadcast, since the call_type std::function is ...
std::function< void(const in_v &, out_v &) > v_eval_type
vspline::unary_functor< IN, OUT, _vsize > base_type
grok_type(const eval_type &fev)
constructor taking only an unvectorized evaluation function. this function is broadcast,...
while 'normal' unary_functors are all derived from unary_functor_tag, sink functors will be derived f...
sink_functor is used for functors without an output - e.g. reductors which are used for analytic purp...
vector_traits< IN, vsize >::nd_ele_v in_nd_ele_v
vspline::vector_traits< IN >::ele_type in_ele_type
vector_traits< IN, vsize >::type in_v
vectorized in_type. vspline::vector_traits supplies this type so that multidimensional/multichannel d...
vector_traits< IN, vsize >::ele_v in_ele_v
a simdized type of the elementary type of result_type, which is used for coefficients and results....
vector_traits< int, vsize >::ele_v ic_v
vsize wide vector of ints, used for gather/scatter indexes
vigra::TinyVector< in_ele_type, dim_in > in_nd_ele_type
we derive all vspline::unary_functors from this empty class, to have a common base type for all of th...
class unary_functor provides a functor object which offers a system of types for concrete unary funct...
vigra::TinyVector< in_ele_type, dim_in > in_nd_ele_type
vspline::vector_traits< OUT >::ele_type out_ele_type
vector_traits< IN, vsize >::nd_ele_v in_nd_ele_v
vigra::TinyVector< out_ele_type, dim_out > out_nd_ele_type
vector_traits< IN, vsize >::type in_v
vectorized in_type and out_type. vspline::vector_traits supplies these types so that multidimensional...
vspline::vector_traits< IN >::ele_type in_ele_type
vector_traits< OUT, vsize >::nd_ele_v out_nd_ele_v
vector_traits< OUT, vsize >::type out_v
vector_traits< int, vsize >::ele_v ic_v
vsize wide vector of ints, used for gather/scatter indexes
vector_traits< IN, vsize >::ele_v in_ele_v
a simdized type of the elementary type of result_type, which is used for coefficients and results....
static const bool has_capped_eval
vector_traits< OUT, vsize >::ele_v out_ele_v
with the definition of 'simd_traits', we can proceed to implement 'vector_traits': struct vector_trai...
Definition: vector.h:344
std::conditional< dimension==1, int, vigra::TinyVector< int, dimension > >::type ic_t
at times we require reading access to an nD array at given coordinates, as a functor which,...