vspline 1.1.0
Generic C++11 Code for Uniform B-Splines
roundtrip.cc
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/* Permission is hereby granted, free of charge, to any person */
9/* obtaining a copy of this software and associated documentation */
10/* files (the "Software"), to deal in the Software without */
11/* restriction, including without limitation the rights to use, */
12/* copy, modify, merge, publish, distribute, sublicense, and/or */
13/* sell copies of the Software, and to permit persons to whom the */
14/* Software is furnished to do so, subject to the following */
15/* conditions: */
16/* */
17/* The above copyright notice and this permission notice shall be */
18/* included in all copies or substantial portions of the */
19/* Software. */
20/* */
21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND */
22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES */
23/* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND */
24/* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT */
25/* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
26/* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING */
27/* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR */
28/* OTHER DEALINGS IN THE SOFTWARE. */
29/* */
30/************************************************************************/
31
32/// \file roundtrip.cc
33///
34/// \brief benchmarking and testing code for various vspline capabilities
35///
36/// load an image, create a b-spline from it, and restore the original data,
37/// both by normal evaluation and by convolution with the reconstruction kernel.
38/// all of this is done several times each with different boundary conditions,
39/// spline degrees and in float and double arithmetic, the processing times
40/// and differences between input and restored signal are printed to cout.
41///
42/// obviously, this is not a useful program, it's to make sure the engine functions as
43/// intended and all combinations of float and double as values and coordinates compile
44/// and function as intended, also giving an impression of the speed of processing.
45///
46/// compile:
47/// clang++ -std=c++11 -march=native -o roundtrip -O3 -pthread -DUSE_VC roundtrip.cc -lvigraimpex -lVc
48/// or: clang++ -std=c++11 -march=native -o roundtrip -O3 -pthread roundtrip.cc -lvigraimpex
49///
50/// invoke: roundtrip <image file>
51///
52/// there is no image output.
53
54#include <iostream>
55
56#include <vspline/vspline.h>
57
58#include <vigra/stdimage.hxx>
59#include <vigra/imageinfo.hxx>
60#include <vigra/impex.hxx>
61#include <vigra/accumulator.hxx>
62#include <vigra/multi_math.hxx>
63
64#define PRINT_ELAPSED
65
66#ifdef PRINT_ELAPSED
67#include <ctime>
68#include <chrono>
69#endif
70
71using namespace std ;
72
73using namespace vigra ;
74
75/// check for differences between two arrays
76
77template < class view_type >
78long double check_diff ( const view_type& a , const view_type& b )
79{
80 using namespace vigra::multi_math ;
81 using namespace vigra::acc;
82
83 typedef typename view_type::value_type value_type ;
84 typedef typename vigra::ExpandElementResult < value_type > :: type real_type ;
85 typedef MultiArray<view_type::actual_dimension,real_type> error_array ;
86
87 error_array ea ( vigra::multi_math::squaredNorm ( b - a ) ) ;
88 AccumulatorChain<real_type,Select< Mean, Maximum> > ac ;
89 extractFeatures(ea.begin(), ea.end(), ac);
90 std::cout << "warped image diff Mean: " << sqrt(get<Mean>(ac)) << std::endl;
91
92 long double max_error = sqrt(get<Maximum>(ac)) ;
93 std::cout << "warped image diff Maximum: "
94 << max_error << std::endl ;
95
96// if ( max_error > .01 )
97// {
98// for ( int y = 0 ; y < a.shape(1) ; y++ )
99// {
100// for ( int x = 0 ; x < a.shape(0) ; x++ )
101// {
102// auto pa = a ( x , y ) ;
103// auto pb = b ( x , y ) ;
104// auto na = pa[0] * pa[0] + pa[1] * pa[1] + pa[2] * pa[2] ;
105// auto nb = pb[0] * pb[0] + pb[1] * pb[1] + pb[2] * pb[2] ;
106// if ( std::abs ( na - nb ) > .01 )
107// std::cout << "excess at ( " << x << " , "
108// << y << " )" << std::endl ;
109// }
110// }
111// }
112 return max_error ;
113}
114
115template < class view_type ,
116 typename real_type ,
117 typename rc_type ,
118 int specialize >
119long double run_test ( view_type & data ,
121 int DEGREE ,
122 int TIMES = 32 )
123{
124 typedef typename view_type::value_type pixel_type ;
125 typedef typename view_type::difference_type Shape;
126 typedef MultiArray < 2 , pixel_type > array_type ;
127 typedef int int_type ;
128 auto shape = data.shape() ;
129
130 long double max_error = 0.0L ;
131 long double error ;
132
133 // we use simdized types with as many elements as vector_traits
134 // considers appropriate for a given real_type, which is the elementary
135 // type of the (pixel) data we process:
136
137 const int vsize = vspline::vector_traits < real_type > :: size ;
138
140
141 int Nx = data.width() ;
142 int Ny = data.height() ;
143
144 vspline::bspline < pixel_type , 2 > bsp ( data.shape() , DEGREE , bcv ) ;
145
146 size_t max_extent = shape[0] > shape[1] ? shape[0] : shape[1] ;
147
148 vigra::MultiArray < 1 , rc_type > indexes ( max_extent ) ;
149 for ( size_t i = 0 ; i < max_extent ; i++ )
150 indexes[i] = i ;
151
152 vigra::MultiArrayView < 1 , rc_type > ix0
153 ( vigra::Shape1 ( shape[0] ) , indexes.data() ) ;
154
155 vigra::MultiArrayView < 1 , rc_type > ix1
156 ( vigra::Shape1 ( shape[1] ) , indexes.data() ) ;
157
158 vspline::grid_spec < 2 , rc_type > grid { ix0 , ix1 } ;
159
160 bsp.core = data ;
161
162 // first test: time prefilter
163
164#ifdef PRINT_ELAPSED
165 std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
166 std::chrono::system_clock::time_point end ;
167#endif
168
169 for ( int times = 0 ; times < TIMES ; times++ )
170 bsp.prefilter() ;
171
172#ifdef PRINT_ELAPSED
173 end = std::chrono::system_clock::now();
174 cout << "avg " << TIMES << " x prefilter:............................ "
175 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / float(TIMES)
176 << " ms" << endl ;
177#endif
178
179 // to time and test 1D operation, we pretend the data are 1D,
180 // prefilter and restore them.
181
182 start = std::chrono::system_clock::now();
183
184 // cast data to 1D array
185
186 vigra::MultiArrayView < 1 , pixel_type > fake_1d_array
187 ( vigra::Shape1 ( prod ( data.shape() ) ) , data.data() ) ;
188
189 vigra::TinyVector < vspline::bc_code , 1 > bcv1 ( bcv[0] ) ;
190
192 ( fake_1d_array.shape() , DEGREE , bcv1 ) ;
193
194 bsp1.core = fake_1d_array ;
195
196 for ( int times = 0 ; times < TIMES ; times++ )
197 bsp1.prefilter() ;
198
199 #ifdef PRINT_ELAPSED
200 end = std::chrono::system_clock::now();
201 cout << "avg " << TIMES << " x prefilter as fake 1D array:........... "
202 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / float(TIMES)
203 << " ms" << endl ;
204#endif
205
206 // use fresh data, data above are useless after TIMES times filtering
207 bsp1.core = fake_1d_array ;
208 bsp1.prefilter() ;
209
210 start = std::chrono::system_clock::now();
211
212 vigra::MultiArray < 1 , pixel_type > fake_1d_target
213 ( vigra::Shape1 ( prod ( data.shape() ) ) ) ;
214
215 vspline::restore < 1 , pixel_type > ( bsp1 , fake_1d_target ) ;
216
217 for ( int times = 1 ; times < TIMES ; times++ )
218 vspline::restore < 1 , pixel_type > ( bsp1 , fake_1d_target ) ;
219
220#ifdef PRINT_ELAPSED
221 end = std::chrono::system_clock::now();
222 cout << "avg " << TIMES << " x restore original data from 1D:........ "
223 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / float(TIMES)
224 << " ms" << endl ;
225#endif
226
227 cout << "difference original data/restored data:" << endl ;
228 error = check_diff<decltype(fake_1d_array)> ( fake_1d_array , fake_1d_target ) ;
229 if ( error > max_error )
230 max_error = error ;
231
232 // that's all of the 1D testing we do, back to the 2D data.
233 // use fresh data, data above are useless after TIMES times filtering
234 bsp.core = data ;
235 bsp.prefilter() ;
236
237 // get a view to the core coefficients (those which aren't part of the brace)
238 view_type cfview = bsp.core ;
239
240 // set the coordinate type
241 typedef vigra::TinyVector < rc_type , 2 > coordinate_type ;
242
243 // set the evaluator type
245
246 // set the discrete coordinate type
247 typedef vigra::TinyVector < int , 2 > dsc_coordinate_type ;
248
249 // set the discrete evaluator type
250 // currently unused, but is an alternative for unit-spaced
251 // index-based transform.
253
254 // create the evaluator for the b-spline, using plain evaluation (no derivatives)
255
256 eval_type raw_ev ( bsp ) ;
257 dsc_eval_type dsc_ev ( bsp ) ;
258
260
261 auto ev = vspline::make_safe_evaluator < spline_type , rc_type > ( bsp ) ;
262
263 // type for coordinate array
264 typedef vigra::MultiArray<2, coordinate_type> coordinate_array ;
265
266 int Tx = Nx ;
267 int Ty = Ny ;
268
269 // now we create a warp array of coordinates at which the spline will be evaluated.
270 // Also create a target array to contain the result.
271
272 coordinate_array fwarp ( Shape ( Tx , Ty ) ) ;
273 array_type _target ( Shape(Tx,Ty) ) ;
274 view_type target ( _target ) ;
275
276 rc_type dfx = 0.0 , dfy = 0.0 ; // currently evaluating right at knot point locations
277
278 for ( int times = 0 ; times < 1 ; times++ )
279 {
280 for ( int y = 0 ; y < Ty ; y++ )
281 {
282 rc_type fy = (rc_type)(y) + dfy ;
283 for ( int x = 0 ; x < Tx ; x++ )
284 {
285 rc_type fx = (rc_type)(x) + dfx ;
286 // store the coordinate to fwarp[x,y]
287 fwarp [ Shape ( x , y ) ] = coordinate_type ( fx , fy ) ;
288 }
289 }
290 }
291
292 // second test. perform a transform using fwarp as warp array. Since fwarp contains
293 // the discrete coordinates to the knot points, converted to float, the result
294 // should be the same as the input within the given precision
295
296#ifdef PRINT_ELAPSED
297 start = std::chrono::system_clock::now();
298#endif
299
300 for ( int times = 0 ; times < TIMES ; times++ )
301 vspline::transform ( ev , fwarp , target ) ;
302
303
304#ifdef PRINT_ELAPSED
305 end = std::chrono::system_clock::now();
306 cout << "avg " << TIMES << " x transform with ready-made bspline:.... "
307 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / float(TIMES)
308 << " ms" << endl ;
309#endif
310
311 error = check_diff<view_type> ( target , data ) ;
312 if ( error > max_error )
313 max_error = error ;
314
315 // third test: do the same with the 'classic' remap routine which internally creates
316 // a b-spline
317
318#ifdef PRINT_ELAPSED
319 start = std::chrono::system_clock::now();
320#endif
321
322 for ( int times = 0 ; times < TIMES ; times++ )
323 vspline::remap ( data , fwarp , target , bcv , DEGREE ) ;
324
325#ifdef PRINT_ELAPSED
326 end = std::chrono::system_clock::now();
327 cout << "avg " << TIMES << " x classic remap:........................ "
328 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / float(TIMES)
329 << " ms" << endl ;
330#endif
331
332 error = check_diff<view_type> ( target , data ) ;
333 if ( error > max_error )
334 max_error = error ;
335
336 // fourth test: perform an transform() directly using the b-spline evaluator
337 // as the transform's functor. This is, yet again, the same, because
338 // it evaluates at all discrete positions, but now without the warp array:
339 // the index-based transform feeds the evaluator with the discrete coordinates.
340
341#ifdef PRINT_ELAPSED
342 start = std::chrono::system_clock::now();
343#endif
344
345 for ( int times = 0 ; times < TIMES ; times++ )
346 vspline::transform ( ev , target ) ;
347
348#ifdef PRINT_ELAPSED
349 end = std::chrono::system_clock::now();
350 cout << "avg " << TIMES << " x index-based transform................. "
351 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / float(TIMES)
352 << " ms" << endl ;
353#endif
354
355 cout << "difference original data/restored data:" << endl ;
356 error = check_diff<view_type> ( target , data ) ;
357 if ( error > max_error )
358 max_error = error ;
359
360 // fifth test: use 'restore' which internally uses convolution. This is
361 // usually slightly faster than the previous way to restore the original
362 // data, but otherwise makes no difference.
363
364#ifdef PRINT_ELAPSED
365 start = std::chrono::system_clock::now();
366#endif
367
368 for ( int times = 0 ; times < TIMES ; times++ )
369 vspline::restore < 2 , pixel_type > ( bsp , target ) ;
370
371#ifdef PRINT_ELAPSED
372 end = std::chrono::system_clock::now();
373 cout << "avg " << TIMES << " x restore original data: .............. "
374 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / float(TIMES)
375 << " ms" << endl ;
376#endif
377
378 cout << "difference original data/restored data:" << endl ;
379 error = check_diff<view_type> ( data , target ) ;
380 if ( error > max_error )
381 max_error = error ;
382 cout << endl ;
383
384#ifdef PRINT_ELAPSED
385 start = std::chrono::system_clock::now();
386#endif
387
388 for ( int times = 0 ; times < TIMES ; times++ )
389 vspline::transform ( grid , raw_ev , target ) ;
390
391#ifdef PRINT_ELAPSED
392 end = std::chrono::system_clock::now();
393 cout << "avg " << TIMES << " x grid eval over canonical indexes: ... "
394 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / float(TIMES)
395 << " ms" << endl ;
396#endif
397
398 cout << "difference original data/restored data:" << endl ;
399 error = check_diff<view_type> ( data , target ) ;
400 if ( error > max_error )
401 max_error = error ;
402 cout << endl ;
403
404#ifdef PRINT_ELAPSED
405 start = std::chrono::system_clock::now();
406#endif
407
408 for ( int times = 0 ; times < TIMES ; times++ )
409 vspline::transform ( grid , ev , target ) ;
410
411#ifdef PRINT_ELAPSED
412 end = std::chrono::system_clock::now();
413 cout << "avg " << TIMES << " x gen. grid eval over canonical indexes: "
414 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / float(TIMES)
415 << " ms" << endl ;
416#endif
417
418 cout << "difference original data/restored data:" << endl ;
419 error = check_diff<view_type> ( data , target ) ;
420 if ( error > max_error )
421 max_error = error ;
422 cout << endl ;
423
424 return max_error ;
425}
426
427template < class real_type , class rc_type >
428long double process_image ( char * name )
429{
430 long double max_error = 0.0L ;
431 long double error ;
432
433 cout << fixed << showpoint << setprecision(16) ;
434
435 // the import and info-displaying code is taken from vigra:
436
437 vigra::ImageImportInfo imageInfo(name);
438 // print some information
439 std::cout << "Image information:\n";
440 std::cout << " file format: " << imageInfo.getFileType() << std::endl;
441 std::cout << " width: " << imageInfo.width() << std::endl;
442 std::cout << " height: " << imageInfo.height() << std::endl;
443 std::cout << " pixel type: " << imageInfo.getPixelType() << std::endl;
444 std::cout << " color image: ";
445 if (imageInfo.isColor()) std::cout << "yes (";
446 else std::cout << "no (";
447 std::cout << "number of channels: " << imageInfo.numBands() << ")\n";
448
449 typedef vigra::RGBValue<real_type,0,1,2> pixel_type;
450 typedef vigra::MultiArray<2, pixel_type> array_type ;
451 typedef vigra::MultiArrayView<2, pixel_type> view_type ;
452
453 // to test that strided data are processed correctly, we load the image
454 // to an inner subarray of containArray
455
456// array_type containArray(imageInfo.shape()+vigra::Shape2(3,5));
457// view_type imageArray = containArray.subarray(vigra::Shape2(1,2),vigra::Shape2(-2,-3)) ;
458
459 // alternatively, just use the same for both
460
461 array_type containArray ( imageInfo.shape() );
462 view_type imageArray ( containArray ) ;
463
464 vigra::importImage(imageInfo, imageArray);
465
466 // test these boundary conditions:
467
468 vspline::bc_code bcs[] =
469 {
474 } ;
475
476 for ( int b = 0 ; b < 4 ; b++ )
477 {
478 vspline::bc_code bc = bcs[b] ;
479 for ( int spline_degree = 1 ; spline_degree < 8 ; spline_degree++ )
480 {
481#if defined USE_VC
482
483 cout << "testing bc code " << vspline::bc_name[bc]
484 << " spline degree " << spline_degree
485 << " using Vc" << endl ;
486
487#elif defined USE_HWY
488
489 cout << "testing bc code " << vspline::bc_name[bc]
490 << " spline degree " << spline_degree
491 << " using highway" << endl ;
492
493#elif defined USE_STDSIMD
494
495 cout << "testing bc code " << vspline::bc_name[bc]
496 << " spline degree " << spline_degree
497 << " using std::simd" << endl ;
498
499#else
500
501 cout << "testing bc code " << vspline::bc_name[bc]
502 << " spline degree " << spline_degree
503 << " using SIMD emulation" << endl ;
504
505#endif
506
507 if ( spline_degree == 0 )
508 {
509 std::cout << "using specialized evaluator" << std::endl ;
510 error = run_test < view_type , real_type , rc_type , 0 >
511 ( imageArray , bc , spline_degree ) ;
512 if ( error > max_error )
513 max_error = error ;
514 std::cout << "using unspecialized evaluator" << std::endl ;
515 error = run_test < view_type , real_type , rc_type , -1 >
516 ( imageArray , bc , spline_degree ) ;
517 if ( error > max_error )
518 max_error = error ;
519 }
520 else if ( spline_degree == 1 )
521 {
522 std::cout << "using specialized evaluator" << std::endl ;
523 error = run_test < view_type , real_type , rc_type , 1 >
524 ( imageArray , bc , spline_degree ) ;
525 if ( error > max_error )
526 max_error = error ;
527 std::cout << "using unspecialized evaluator" << std::endl ;
528 error = run_test < view_type , real_type , rc_type , -1 >
529 ( imageArray , bc , spline_degree ) ;
530 if ( error > max_error )
531 max_error = error ;
532 }
533 else
534 {
535 error = run_test < view_type , real_type , rc_type , -1 >
536 ( imageArray , bc , spline_degree ) ;
537 if ( error > max_error )
538 max_error = error ;
539 }
540 }
541 }
542 return max_error ;
543}
544
545int main ( int argc , char * argv[] )
546{
547 long double max_error = 0.0L ;
548 long double error ;
549
550 cout << "testing float data, float coordinates" << endl ;
551 error = process_image<float,float> ( argv[1] ) ;
552 if ( error > max_error )
553 max_error = error ;
554 cout << "max error of float/float test: " << error << std::endl << std::endl ;
555
556 cout << endl << "testing double data, double coordinates" << endl ;
557 error = process_image<double,double> ( argv[1] ) ;
558 if ( error > max_error )
559 max_error = error ;
560 cout << "max error of double/double test: " << error << std::endl << std::endl ;
561
562 cout << endl << "testing long double data, float coordinates" << endl ;
563 error = process_image<long double,float> ( argv[1] ) ;
564 if ( error > max_error )
565 max_error = error ;
566 cout << "max error of ldouble/float test: " << error << std::endl << std::endl ;
567
568 cout << endl << "testing long double data, double coordinates" << endl ;
569 error = process_image<long double,double> ( argv[1] ) ;
570 if ( error > max_error )
571 max_error = error ;
572 cout << "max error of ldouble/double test: " << error << std::endl << std::endl ;
573
574 cout << "testing float data, double coordinates" << endl ;
575 error = process_image<float,double> ( argv[1] ) ;
576 if ( error > max_error )
577 max_error = error ;
578 cout << "max error of float/double test: " << error << std::endl << std::endl ;
579
580 cout << endl << "testing double data, float coordinates" << endl ;
581 error = process_image<double,float> ( argv[1] ) ;
582 if ( error > max_error )
583 max_error = error ;
584 cout << "max error of double/float test: " << error << std::endl << std::endl ;
585
586 cout << "reached end. max error of all tests: " << max_error << std::endl ;
587}
vigra::RGBValue< float, 0, 1, 2 > pixel_type
Definition: ca_correct.cc:103
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
double rc_type
Definition: eval.cc:94
@ vsize
Definition: eval.cc:96
void transform(const unary_functor_type &functor, const vigra::MultiArrayView< dimension, typename unary_functor_type::in_type > &input, vigra::MultiArrayView< dimension, typename unary_functor_type::out_type > &output, int njobs=vspline::default_njobs, vspline::atomic< bool > *p_cancel=0)
implementation of two-array transform using wielding::coupled_wield.
Definition: transform.h:211
vigra::TinyVector< vspline::bc_code, dimension > bcv_type
a type for a set of boundary condition codes, one per axis
Definition: transform.h:554
const std::string bc_name[]
bc_name is for diagnostic output of bc codes
Definition: common.h:84
void remap(const vigra::MultiArrayView< cf_dimension, original_type > &input, const vigra::MultiArrayView< trg_dimension, coordinate_type > &coordinates, vigra::MultiArrayView< trg_dimension, result_type > &output, bcv_type< bcv_dimension > bcv=bcv_type< bcv_dimension >(MIRROR), int degree=3, int njobs=vspline::default_njobs, vspline::atomic< bool > *p_cancel=0)
Implementation of 'classic' remap, which directly takes an array of values and remaps it,...
Definition: transform.h:600
bc_code
This enumeration is used for codes connected to boundary conditions. There are two aspects to boundar...
Definition: common.h:71
@ NATURAL
Definition: common.h:75
@ REFLECT
Definition: common.h:74
@ PERIODIC
Definition: common.h:73
@ MIRROR
Definition: common.h:72
vigra::TinyVector< vigra::MultiArrayView< 1, rc_ele_type >, dimension > grid_spec
Definition: transform.h:701
int main(int argc, char *argv[])
Definition: roundtrip.cc:545
long double run_test(view_type &data, vspline::bc_code bc, int DEGREE, int TIMES=32)
Definition: roundtrip.cc:119
long double check_diff(const view_type &a, const view_type &b)
check for differences between two arrays
Definition: roundtrip.cc:78
long double process_image(char *name)
Definition: roundtrip.cc:428
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
void prefilter(vspline::xlf_type boost=vspline::xlf_type(1), int njobs=vspline::default_njobs)
prefilter converts the knot point data in the 'core' area into b-spline coefficients....
Definition: bspline.h:815
with the definition of 'simd_traits', we can proceed to implement 'vector_traits': struct vector_trai...
Definition: vector.h:344
includes all headers from vspline (most of them indirectly)