Merge pull request #390 from taka-no-me:fix_relative_error_check
[profile/ivi/opencv.git] / modules / ts / src / ts_perf.cpp
1 #include "precomp.hpp"
2
3 #ifdef HAVE_CUDA
4 #include "opencv2/core/gpumat.hpp"
5 #endif
6
7 #ifdef ANDROID
8 # include <sys/time.h>
9 #endif
10
11 using namespace perf;
12
13 int64 TestBase::timeLimitDefault = 0;
14 unsigned int TestBase::iterationsLimitDefault = (unsigned int)(-1);
15 int64 TestBase::_timeadjustment = 0;
16
17 const std::string command_line_keys =
18     "{   |perf_max_outliers   |8        |percent of allowed outliers}"
19     "{   |perf_min_samples    |10       |minimal required numer of samples}"
20     "{   |perf_force_samples  |100      |force set maximum number of samples for all tests}"
21     "{   |perf_seed           |809564   |seed for random numbers generator}"
22     "{   |perf_threads        |-1       |the number of worker threads, if parallel execution is enabled}"
23     "{   |perf_write_sanity   |false    |create new records for sanity checks}"
24     "{   |perf_verify_sanity  |false    |fail tests having no regression data for sanity checks}"
25 #ifdef ANDROID
26     "{   |perf_time_limit     |6.0      |default time limit for a single test (in seconds)}"
27     "{   |perf_affinity_mask  |0        |set affinity mask for the main thread}"
28     "{   |perf_log_power_checkpoints  | |additional xml logging for power measurement}"
29 #else
30     "{   |perf_time_limit     |3.0      |default time limit for a single test (in seconds)}"
31 #endif
32     "{   |perf_max_deviation  |1.0      |}"
33     "{h  |help                |false    |print help info}"
34 #ifdef HAVE_CUDA
35     "{   |perf_run_cpu        |false    |run GPU performance tests for analogical CPU functions}"
36     "{   |perf_cuda_device    |0        |run GPU test suite onto specific CUDA capable device}"
37     "{   |perf_cuda_info_only |false    |print an information about system and an available CUDA devices and then exit.}"
38 #endif
39 ;
40
41 static double       param_max_outliers;
42 static double       param_max_deviation;
43 static unsigned int param_min_samples;
44 static unsigned int param_force_samples;
45 static uint64       param_seed;
46 static double       param_time_limit;
47 static int          param_threads;
48 static bool         param_write_sanity;
49 static bool         param_verify_sanity;
50 #ifdef HAVE_CUDA
51 static bool         param_run_cpu;
52 static int          param_cuda_device;
53 #endif
54
55
56 #ifdef ANDROID
57 static int          param_affinity_mask;
58 static bool         log_power_checkpoints;
59
60 #include <sys/syscall.h>
61 #include <pthread.h>
62 static void setCurrentThreadAffinityMask(int mask)
63 {
64     pid_t pid=gettid();
65     int syscallres=syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);
66     if (syscallres)
67     {
68         int err=errno;
69         err=err;//to avoid warnings about unused variables
70         LOGE("Error in the syscall setaffinity: mask=%d=0x%x err=%d=0x%x", mask, mask, err, err);
71     }
72 }
73 #endif
74
75 #ifdef HAVE_CUDA
76 # include <opencv2/core/gpumat.hpp>
77 #endif
78
79 namespace {
80
81 class PerfEnvironment: public ::testing::Environment
82 {
83 public:
84     void TearDown()
85     {
86         cv::setNumThreads(-1);
87     }
88 };
89
90 } // namespace
91
92 static void randu(cv::Mat& m)
93 {
94     const int bigValue = 0x00000FFF;
95     if (m.depth() < CV_32F)
96     {
97         int minmax[] = {0, 256};
98         cv::Mat mr = cv::Mat(m.rows, (int)(m.cols * m.elemSize()), CV_8U, m.ptr(), m.step[0]);
99         cv::randu(mr, cv::Mat(1, 1, CV_32S, minmax), cv::Mat(1, 1, CV_32S, minmax + 1));
100     }
101     else if (m.depth() == CV_32F)
102     {
103         //float minmax[] = {-FLT_MAX, FLT_MAX};
104         float minmax[] = {-bigValue, bigValue};
105         cv::Mat mr = m.reshape(1);
106         cv::randu(mr, cv::Mat(1, 1, CV_32F, minmax), cv::Mat(1, 1, CV_32F, minmax + 1));
107     }
108     else
109     {
110         //double minmax[] = {-DBL_MAX, DBL_MAX};
111         double minmax[] = {-bigValue, bigValue};
112         cv::Mat mr = m.reshape(1);
113         cv::randu(mr, cv::Mat(1, 1, CV_64F, minmax), cv::Mat(1, 1, CV_64F, minmax + 1));
114     }
115 }
116
117 /*****************************************************************************************\
118 *                       inner exception class for early termination
119 \*****************************************************************************************/
120
121 class PerfEarlyExitException: public cv::Exception {};
122
123 /*****************************************************************************************\
124 *                                   ::perf::Regression
125 \*****************************************************************************************/
126
127 Regression& Regression::instance()
128 {
129     static Regression single;
130     return single;
131 }
132
133 Regression& Regression::add(TestBase* test, const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
134 {
135     if(test) test->verified = true;
136     return instance()(name, array, eps, err);
137 }
138
139 Regression& Regression::addKeypoints(TestBase* test, const std::string& name, const std::vector<cv::KeyPoint>& array, double eps, ERROR_TYPE err)
140 {
141     int len = (int)array.size();
142     cv::Mat pt      (len, 1, CV_32FC2, (void*)&array[0].pt,       sizeof(cv::KeyPoint));
143     cv::Mat size    (len, 1, CV_32FC1, (void*)&array[0].size,     sizeof(cv::KeyPoint));
144     cv::Mat angle   (len, 1, CV_32FC1, (void*)&array[0].angle,    sizeof(cv::KeyPoint));
145     cv::Mat response(len, 1, CV_32FC1, (void*)&array[0].response, sizeof(cv::KeyPoint));
146     cv::Mat octave  (len, 1, CV_32SC1, (void*)&array[0].octave,   sizeof(cv::KeyPoint));
147     cv::Mat class_id(len, 1, CV_32SC1, (void*)&array[0].class_id, sizeof(cv::KeyPoint));
148
149     return Regression::add(test, name + "-pt",       pt,       eps, ERROR_ABSOLUTE)
150                                 (name + "-size",     size,     eps, ERROR_ABSOLUTE)
151                                 (name + "-angle",    angle,    eps, ERROR_ABSOLUTE)
152                                 (name + "-response", response, eps, err)
153                                 (name + "-octave",   octave,   eps, ERROR_ABSOLUTE)
154                                 (name + "-class_id", class_id, eps, ERROR_ABSOLUTE);
155 }
156
157 Regression& Regression::addMatches(TestBase* test, const std::string& name, const std::vector<cv::DMatch>& array, double eps, ERROR_TYPE err)
158 {
159     int len = (int)array.size();
160     cv::Mat queryIdx(len, 1, CV_32SC1, (void*)&array[0].queryIdx, sizeof(cv::DMatch));
161     cv::Mat trainIdx(len, 1, CV_32SC1, (void*)&array[0].trainIdx, sizeof(cv::DMatch));
162     cv::Mat imgIdx  (len, 1, CV_32SC1, (void*)&array[0].imgIdx,   sizeof(cv::DMatch));
163     cv::Mat distance(len, 1, CV_32FC1, (void*)&array[0].distance, sizeof(cv::DMatch));
164
165     return Regression::add(test, name + "-queryIdx", queryIdx, DBL_EPSILON, ERROR_ABSOLUTE)
166                                 (name + "-trainIdx", trainIdx, DBL_EPSILON, ERROR_ABSOLUTE)
167                                 (name + "-imgIdx",   imgIdx,   DBL_EPSILON, ERROR_ABSOLUTE)
168                                 (name + "-distance", distance, eps, err);
169 }
170
171 void Regression::Init(const std::string& testSuitName, const std::string& ext)
172 {
173     instance().init(testSuitName, ext);
174 }
175
176 void Regression::init(const std::string& testSuitName, const std::string& ext)
177 {
178     if (!storageInPath.empty())
179     {
180         LOGE("Subsequent initialisation of Regression utility is not allowed.");
181         return;
182     }
183
184     const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
185     const char *path_separator = "/";
186
187     if (data_path_dir)
188     {
189         int len = (int)strlen(data_path_dir)-1;
190         if (len < 0) len = 0;
191         std::string path_base = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
192                 + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator)
193                 + "perf"
194                 + path_separator;
195
196         storageInPath = path_base + testSuitName + ext;
197         storageOutPath = path_base + testSuitName;
198     }
199     else
200     {
201         storageInPath = testSuitName + ext;
202         storageOutPath = testSuitName;
203     }
204
205     suiteName = testSuitName;
206
207     try
208     {
209         if (storageIn.open(storageInPath, cv::FileStorage::READ))
210         {
211             rootIn = storageIn.root();
212             if (storageInPath.length() > 3 && storageInPath.substr(storageInPath.length()-3) == ".gz")
213                 storageOutPath += "_new";
214             storageOutPath += ext;
215         }
216     }
217     catch(cv::Exception&)
218     {
219         LOGE("Failed to open sanity data for reading: %s", storageInPath.c_str());
220     }
221
222     if(!storageIn.isOpened())
223         storageOutPath = storageInPath;
224 }
225
226 Regression::Regression() : regRNG(cv::getTickCount())//this rng should be really random
227 {
228 }
229
230 Regression::~Regression()
231 {
232     if (storageIn.isOpened())
233         storageIn.release();
234     if (storageOut.isOpened())
235     {
236         if (!currentTestNodeName.empty())
237             storageOut << "}";
238         storageOut.release();
239     }
240 }
241
242 cv::FileStorage& Regression::write()
243 {
244     if (!storageOut.isOpened() && !storageOutPath.empty())
245     {
246         int mode = (storageIn.isOpened() && storageInPath == storageOutPath)
247                 ? cv::FileStorage::APPEND : cv::FileStorage::WRITE;
248         storageOut.open(storageOutPath, mode);
249         if (!storageOut.isOpened())
250         {
251             LOGE("Could not open \"%s\" file for writing", storageOutPath.c_str());
252             storageOutPath.clear();
253         }
254         else if (mode == cv::FileStorage::WRITE && !rootIn.empty())
255         {
256             //TODO: write content of rootIn node into the storageOut
257         }
258     }
259     return storageOut;
260 }
261
262 std::string Regression::getCurrentTestNodeName()
263 {
264     const ::testing::TestInfo* const test_info =
265       ::testing::UnitTest::GetInstance()->current_test_info();
266
267     if (test_info == 0)
268         return "undefined";
269
270     std::string nodename = std::string(test_info->test_case_name()) + "--" + test_info->name();
271     size_t idx = nodename.find_first_of('/');
272     if (idx != std::string::npos)
273         nodename.erase(idx);
274
275     const char* type_param = test_info->type_param();
276     if (type_param != 0)
277         (nodename += "--") += type_param;
278
279     const char* value_param = test_info->value_param();
280     if (value_param != 0)
281         (nodename += "--") += value_param;
282
283     for(size_t i = 0; i < nodename.length(); ++i)
284         if (!isalnum(nodename[i]) && '_' != nodename[i])
285             nodename[i] = '-';
286
287     return nodename;
288 }
289
290 bool Regression::isVector(cv::InputArray a)
291 {
292     return a.kind() == cv::_InputArray::STD_VECTOR_MAT || a.kind() == cv::_InputArray::STD_VECTOR_VECTOR;
293 }
294
295 double Regression::getElem(cv::Mat& m, int y, int x, int cn)
296 {
297     switch (m.depth())
298     {
299     case CV_8U: return *(m.ptr<unsigned char>(y, x) + cn);
300     case CV_8S: return *(m.ptr<signed char>(y, x) + cn);
301     case CV_16U: return *(m.ptr<unsigned short>(y, x) + cn);
302     case CV_16S: return *(m.ptr<signed short>(y, x) + cn);
303     case CV_32S: return *(m.ptr<signed int>(y, x) + cn);
304     case CV_32F: return *(m.ptr<float>(y, x) + cn);
305     case CV_64F: return *(m.ptr<double>(y, x) + cn);
306     default: return 0;
307     }
308 }
309
310 void Regression::write(cv::Mat m)
311 {
312     if (!m.empty() && m.dims < 2) return;
313
314     double min, max;
315     cv::minMaxIdx(m, &min, &max);
316     write() << "min" << min << "max" << max;
317
318     write() << "last" << "{" << "x" << m.size.p[1] - 1 << "y" << m.size.p[0] - 1
319         << "val" << getElem(m, m.size.p[0] - 1, m.size.p[1] - 1, m.channels() - 1) << "}";
320
321     int x, y, cn;
322     x = regRNG.uniform(0, m.size.p[1]);
323     y = regRNG.uniform(0, m.size.p[0]);
324     cn = regRNG.uniform(0, m.channels());
325     write() << "rng1" << "{" << "x" << x << "y" << y;
326     if(cn > 0) write() << "cn" << cn;
327     write() << "val" << getElem(m, y, x, cn) << "}";
328
329     x = regRNG.uniform(0, m.size.p[1]);
330     y = regRNG.uniform(0, m.size.p[0]);
331     cn = regRNG.uniform(0, m.channels());
332     write() << "rng2" << "{" << "x" << x << "y" << y;
333     if (cn > 0) write() << "cn" << cn;
334     write() << "val" << getElem(m, y, x, cn) << "}";
335 }
336
337 void Regression::verify(cv::FileNode node, cv::Mat actual, double eps, std::string argname, ERROR_TYPE err)
338 {
339     if (!actual.empty() && actual.dims < 2) return;
340
341     double expect_min = (double)node["min"];
342     double expect_max = (double)node["max"];
343
344     if (err == ERROR_RELATIVE)
345         eps *= std::max(std::abs(expect_min), std::abs(expect_max));
346
347     double actual_min, actual_max;
348     cv::minMaxIdx(actual, &actual_min, &actual_max);
349
350     ASSERT_NEAR(expect_min, actual_min, eps)
351             << argname << " has unexpected minimal value" << std::endl;
352     ASSERT_NEAR(expect_max, actual_max, eps)
353             << argname << " has unexpected maximal value" << std::endl;
354
355     cv::FileNode last = node["last"];
356     double actual_last = getElem(actual, actual.size.p[0] - 1, actual.size.p[1] - 1, actual.channels() - 1);
357     int expect_cols = (int)last["x"] + 1;
358     int expect_rows = (int)last["y"] + 1;
359     ASSERT_EQ(expect_cols, actual.size.p[1])
360             << argname << " has unexpected number of columns" << std::endl;
361     ASSERT_EQ(expect_rows, actual.size.p[0])
362             << argname << " has unexpected number of rows" << std::endl;
363
364     double expect_last = (double)last["val"];
365     ASSERT_NEAR(expect_last, actual_last, eps)
366             << argname << " has unexpected value of the last element" << std::endl;
367
368     cv::FileNode rng1 = node["rng1"];
369     int x1 = rng1["x"];
370     int y1 = rng1["y"];
371     int cn1 = rng1["cn"];
372
373     double expect_rng1 = (double)rng1["val"];
374     // it is safe to use x1 and y1 without checks here because we have already
375     // verified that mat size is the same as recorded
376     double actual_rng1 = getElem(actual, y1, x1, cn1);
377
378     ASSERT_NEAR(expect_rng1, actual_rng1, eps)
379             << argname << " has unexpected value of the ["<< x1 << ":" << y1 << ":" << cn1 <<"] element" << std::endl;
380
381     cv::FileNode rng2 = node["rng2"];
382     int x2 = rng2["x"];
383     int y2 = rng2["y"];
384     int cn2 = rng2["cn"];
385
386     double expect_rng2 = (double)rng2["val"];
387     double actual_rng2 = getElem(actual, y2, x2, cn2);
388
389     ASSERT_NEAR(expect_rng2, actual_rng2, eps)
390             << argname << " has unexpected value of the ["<< x2 << ":" << y2 << ":" << cn2 <<"] element" << std::endl;
391 }
392
393 void Regression::write(cv::InputArray array)
394 {
395     write() << "kind" << array.kind();
396     write() << "type" << array.type();
397     if (isVector(array))
398     {
399         int total = (int)array.total();
400         int idx = regRNG.uniform(0, total);
401         write() << "len" << total;
402         write() << "idx" << idx;
403
404         cv::Mat m = array.getMat(idx);
405
406         if (m.total() * m.channels() < 26) //5x5 or smaller
407             write() << "val" << m;
408         else
409             write(m);
410     }
411     else
412     {
413         if (array.total() * array.channels() < 26) //5x5 or smaller
414             write() << "val" << array.getMat();
415         else
416             write(array.getMat());
417     }
418 }
419
420 static int countViolations(const cv::Mat& expected, const cv::Mat& actual, const cv::Mat& diff, double eps, double* max_violation = 0, double* max_allowed = 0)
421 {
422     cv::Mat diff64f;
423     diff.reshape(1).convertTo(diff64f, CV_64F);
424
425     cv::Mat expected_abs = cv::abs(expected.reshape(1));
426     cv::Mat actual_abs = cv::abs(actual.reshape(1));
427     cv::Mat maximum, mask;
428     cv::max(expected_abs, actual_abs, maximum);
429     cv::multiply(maximum, cv::Vec<double, 1>(eps), maximum, CV_64F);
430     cv::compare(diff64f, maximum, mask, cv::CMP_GT);
431
432     int v = cv::countNonZero(mask);
433
434     if (v > 0 && max_violation != 0 && max_allowed != 0)
435     {
436         int loc[10];
437         cv::minMaxIdx(maximum, 0, max_allowed, 0, loc, mask);
438         *max_violation = diff64f.at<double>(loc[1], loc[0]);
439     }
440
441     return v;
442 }
443
444 void Regression::verify(cv::FileNode node, cv::InputArray array, double eps, ERROR_TYPE err)
445 {
446     int expected_kind = (int)node["kind"];
447     int expected_type = (int)node["type"];
448     ASSERT_EQ(expected_kind, array.kind()) << "  Argument \"" << node.name() << "\" has unexpected kind";
449     ASSERT_EQ(expected_type, array.type()) << "  Argument \"" << node.name() << "\" has unexpected type";
450
451     cv::FileNode valnode = node["val"];
452     if (isVector(array))
453     {
454         int expected_length = (int)node["len"];
455         ASSERT_EQ(expected_length, (int)array.total()) << "  Vector \"" << node.name() << "\" has unexpected length";
456         int idx = node["idx"];
457
458         cv::Mat actual = array.getMat(idx);
459
460         if (valnode.isNone())
461         {
462             ASSERT_LE((size_t)26, actual.total() * (size_t)actual.channels())
463                     << "  \"" << node.name() << "[" <<  idx << "]\" has unexpected number of elements";
464             verify(node, actual, eps, cv::format("%s[%d]", node.name().c_str(), idx), err);
465         }
466         else
467         {
468             cv::Mat expected;
469             valnode >> expected;
470
471             if(expected.empty())
472             {
473                 ASSERT_TRUE(actual.empty())
474                     << "  expected empty " << node.name() << "[" <<  idx<< "]";
475             }
476             else
477             {
478                 ASSERT_EQ(expected.size(), actual.size())
479                         << "  " << node.name() << "[" <<  idx<< "] has unexpected size";
480
481                 cv::Mat diff;
482                 cv::absdiff(expected, actual, diff);
483
484                 if (err == ERROR_ABSOLUTE)
485                 {
486                     if (!cv::checkRange(diff, true, 0, 0, eps))
487                     {
488                         if(expected.total() * expected.channels() < 12)
489                             std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
490
491                         double max;
492                         cv::minMaxIdx(diff.reshape(1), 0, &max);
493
494                         FAIL() << "  Absolute difference (=" << max << ") between argument \""
495                                << node.name() << "[" <<  idx << "]\" and expected value is greater than " << eps;
496                     }
497                 }
498                 else if (err == ERROR_RELATIVE)
499                 {
500                     double maxv, maxa;
501                     int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
502                     if (violations > 0)
503                     {
504                         FAIL() << "  Relative difference (" << maxv << " of " << maxa << " allowed) between argument \""
505                                << node.name() << "[" <<  idx << "]\" and expected value is greater than " << eps << " in " << violations << " points";
506                     }
507                 }
508             }
509         }
510     }
511     else
512     {
513         if (valnode.isNone())
514         {
515             ASSERT_LE((size_t)26, array.total() * (size_t)array.channels())
516                     << "  Argument \"" << node.name() << "\" has unexpected number of elements";
517             verify(node, array.getMat(), eps, "Argument \"" + node.name() + "\"", err);
518         }
519         else
520         {
521             cv::Mat expected;
522             valnode >> expected;
523             cv::Mat actual = array.getMat();
524
525             if(expected.empty())
526             {
527                 ASSERT_TRUE(actual.empty())
528                     << "  expected empty " << node.name();
529             }
530             else
531             {
532                 ASSERT_EQ(expected.size(), actual.size())
533                         << "  Argument \"" << node.name() << "\" has unexpected size";
534
535                 cv::Mat diff;
536                 cv::absdiff(expected, actual, diff);
537
538                 if (err == ERROR_ABSOLUTE)
539                 {
540                     if (!cv::checkRange(diff, true, 0, 0, eps))
541                     {
542                         if(expected.total() * expected.channels() < 12)
543                             std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
544
545                         double max;
546                         cv::minMaxIdx(diff.reshape(1), 0, &max);
547
548                         FAIL() << "  Difference (=" << max << ") between argument1 \"" << node.name()
549                                << "\" and expected value is greater than " << eps;
550                     }
551                 }
552                 else if (err == ERROR_RELATIVE)
553                 {
554                     double maxv, maxa;
555                     int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
556                     if (violations > 0)
557                     {
558                         FAIL() << "  Relative difference (" << maxv << " of " << maxa << " allowed) between argument \"" << node.name()
559                                << "\" and expected value is greater than " << eps << " in " << violations << " points";
560                     }
561                 }
562             }
563         }
564     }
565 }
566
567 Regression& Regression::operator() (const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
568 {
569     // exit if current test is already failed
570     if(::testing::UnitTest::GetInstance()->current_test_info()->result()->Failed()) return *this;
571
572     if(!array.empty() && array.depth() == CV_USRTYPE1)
573     {
574         ADD_FAILURE() << "  Can not check regression for CV_USRTYPE1 data type for " << name;
575         return *this;
576     }
577
578     std::string nodename = getCurrentTestNodeName();
579
580 #ifdef HAVE_CUDA
581     static const std::string prefix = (param_run_cpu)? "CPU_" : "GPU_";
582     if(suiteName == "gpu")
583         nodename = prefix + nodename;
584 #endif
585
586     cv::FileNode n = rootIn[nodename];
587     if(n.isNone())
588     {
589         if(param_write_sanity)
590         {
591             if (nodename != currentTestNodeName)
592             {
593                 if (!currentTestNodeName.empty())
594                     write() << "}";
595                 currentTestNodeName = nodename;
596
597                 write() << nodename << "{";
598             }
599             // TODO: verify that name is alphanumeric, current error message is useless
600             write() << name << "{";
601             write(array);
602             write() << "}";
603         }
604         else if(param_verify_sanity)
605         {
606             ADD_FAILURE() << "  No regression data for " << name << " argument";
607         }
608     }
609     else
610     {
611         cv::FileNode this_arg = n[name];
612         if (!this_arg.isMap())
613             ADD_FAILURE() << "  No regression data for " << name << " argument";
614         else
615             verify(this_arg, array, eps, err);
616     }
617
618     return *this;
619 }
620
621
622 /*****************************************************************************************\
623 *                                ::perf::performance_metrics
624 \*****************************************************************************************/
625 performance_metrics::performance_metrics()
626 {
627     bytesIn = 0;
628     bytesOut = 0;
629     samples = 0;
630     outliers = 0;
631     gmean = 0;
632     gstddev = 0;
633     mean = 0;
634     stddev = 0;
635     median = 0;
636     min = 0;
637     frequency = 0;
638     terminationReason = TERM_UNKNOWN;
639 }
640
641
642 /*****************************************************************************************\
643 *                                   ::perf::TestBase
644 \*****************************************************************************************/
645
646
647 void TestBase::Init(int argc, const char* const argv[])
648 {
649     cv::CommandLineParser args(argc, argv, command_line_keys.c_str());
650     if (args.get<bool>("help"))
651     {
652         args.printParams();
653         printf("\n\n");
654         return;
655     }
656
657     ::testing::AddGlobalTestEnvironment(new PerfEnvironment);
658
659     param_max_outliers  = std::min(100., std::max(0., args.get<double>("perf_max_outliers")));
660     param_min_samples   = std::max(1u, args.get<unsigned int>("perf_min_samples"));
661     param_max_deviation = std::max(0., args.get<double>("perf_max_deviation"));
662     param_seed          = args.get<uint64>("perf_seed");
663     param_time_limit    = std::max(0., args.get<double>("perf_time_limit"));
664     param_force_samples = args.get<unsigned int>("perf_force_samples");
665     param_write_sanity  = args.get<bool>("perf_write_sanity");
666     param_verify_sanity = args.get<bool>("perf_verify_sanity");
667     param_threads  = args.get<int>("perf_threads");
668 #ifdef ANDROID
669     param_affinity_mask   = args.get<int>("perf_affinity_mask");
670     log_power_checkpoints = args.get<bool>("perf_log_power_checkpoints");
671 #endif
672
673 #ifdef HAVE_CUDA
674
675     bool printOnly        = args.get<bool>("perf_cuda_info_only");
676
677     if (printOnly)
678         exit(0);
679
680     param_run_cpu         = args.get<bool>("perf_run_cpu");
681     param_cuda_device      = std::max(0, std::min(cv::gpu::getCudaEnabledDeviceCount(), args.get<int>("perf_cuda_device")));
682
683     if (param_run_cpu)
684         printf("[----------]\n[ GPU INFO ] \tRun test suite on CPU.\n[----------]\n"), fflush(stdout);
685     else
686     {
687         cv::gpu::DeviceInfo info(param_cuda_device);
688         if (!info.isCompatible())
689         {
690             printf("[----------]\n[ FAILURE  ] \tDevice %s is NOT compatible with current GPU module build.\n[----------]\n", info.name().c_str()), fflush(stdout);
691             exit(-1);
692         }
693
694         cv::gpu::setDevice(param_cuda_device);
695
696         printf("[----------]\n[ GPU INFO ] \tRun test suite on %s GPU.\n[----------]\n", info.name().c_str()), fflush(stdout);
697     }
698 #endif
699
700 //    if (!args.check())
701 //    {
702 //        args.printErrors();
703 //        return;
704 //    }
705
706     timeLimitDefault = param_time_limit == 0.0 ? 1 : (int64)(param_time_limit * cv::getTickFrequency());
707     iterationsLimitDefault = param_force_samples == 0 ? (unsigned)(-1) : param_force_samples;
708     _timeadjustment = _calibrate();
709 }
710
711 int64 TestBase::_calibrate()
712 {
713     class _helper : public ::perf::TestBase
714     {
715         public:
716         performance_metrics& getMetrics() { return calcMetrics(); }
717         virtual void TestBody() {}
718         virtual void PerfTestBody()
719         {
720             //the whole system warmup
721             SetUp();
722             cv::Mat a(2048, 2048, CV_32S, cv::Scalar(1));
723             cv::Mat b(2048, 2048, CV_32S, cv::Scalar(2));
724             declare.time(30);
725             double s = 0;
726             for(declare.iterations(20); startTimer(), next(); stopTimer())
727                 s+=a.dot(b);
728             declare.time(s);
729
730             //self calibration
731             SetUp();
732             for(declare.iterations(1000); startTimer(), next(); stopTimer()){}
733         }
734     };
735
736     _timeadjustment = 0;
737     _helper h;
738     h.PerfTestBody();
739     double compensation = h.getMetrics().min;
740     LOGD("Time compensation is %.0f", compensation);
741     return (int64)compensation;
742 }
743
744 #ifdef _MSC_VER
745 # pragma warning(push)
746 # pragma warning(disable:4355)  // 'this' : used in base member initializer list
747 #endif
748 TestBase::TestBase(): declare(this)
749 {
750 }
751 #ifdef _MSC_VER
752 # pragma warning(pop)
753 #endif
754
755
756 void TestBase::declareArray(SizeVector& sizes, cv::InputOutputArray a, int wtype)
757 {
758     if (!a.empty())
759     {
760         sizes.push_back(std::pair<int, cv::Size>(getSizeInBytes(a), getSize(a)));
761         warmup(a, wtype);
762     }
763     else if (a.kind() != cv::_InputArray::NONE)
764         ADD_FAILURE() << "  Uninitialized input/output parameters are not allowed for performance tests";
765 }
766
767 void TestBase::warmup(cv::InputOutputArray a, int wtype)
768 {
769     if (a.empty()) return;
770     if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
771         warmup_impl(a.getMat(), wtype);
772     else
773     {
774         size_t total = a.total();
775         for (size_t i = 0; i < total; ++i)
776             warmup_impl(a.getMat((int)i), wtype);
777     }
778 }
779
780 int TestBase::getSizeInBytes(cv::InputArray a)
781 {
782     if (a.empty()) return 0;
783     int total = (int)a.total();
784     if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
785         return total * CV_ELEM_SIZE(a.type());
786
787     int size = 0;
788     for (int i = 0; i < total; ++i)
789         size += (int)a.total(i) * CV_ELEM_SIZE(a.type(i));
790
791     return size;
792 }
793
794 cv::Size TestBase::getSize(cv::InputArray a)
795 {
796     if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
797         return a.size();
798     return cv::Size();
799 }
800
801 bool TestBase::next()
802 {
803     bool has_next = ++currentIter < nIters && totalTime < timeLimit;
804     cv::theRNG().state = param_seed; //this rng should generate same numbers for each run
805
806 #ifdef ANDROID
807     if (log_power_checkpoints)
808     {
809         timeval tim;
810         gettimeofday(&tim, NULL);
811         unsigned long long t1 = tim.tv_sec * 1000LLU + (unsigned long long)(tim.tv_usec / 1000.f);
812
813         if (currentIter == 1) RecordProperty("test_start", cv::format("%llu",t1).c_str());
814         if (!has_next) RecordProperty("test_complete", cv::format("%llu",t1).c_str());
815     }
816 #endif
817     return has_next;
818 }
819
820 void TestBase::warmup_impl(cv::Mat m, int wtype)
821 {
822     switch(wtype)
823     {
824     case WARMUP_READ:
825         cv::sum(m.reshape(1));
826         return;
827     case WARMUP_WRITE:
828         m.reshape(1).setTo(cv::Scalar::all(0));
829         return;
830     case WARMUP_RNG:
831         randu(m);
832         return;
833     default:
834         return;
835     }
836 }
837
838 unsigned int TestBase::getTotalInputSize() const
839 {
840     unsigned int res = 0;
841     for (SizeVector::const_iterator i = inputData.begin(); i != inputData.end(); ++i)
842         res += i->first;
843     return res;
844 }
845
846 unsigned int TestBase::getTotalOutputSize() const
847 {
848     unsigned int res = 0;
849     for (SizeVector::const_iterator i = outputData.begin(); i != outputData.end(); ++i)
850         res += i->first;
851     return res;
852 }
853
854 void TestBase::startTimer()
855 {
856     lastTime = cv::getTickCount();
857 }
858
859 void TestBase::stopTimer()
860 {
861     int64 time = cv::getTickCount();
862     if (lastTime == 0)
863         ADD_FAILURE() << "  stopTimer() is called before startTimer()";
864     lastTime = time - lastTime;
865     totalTime += lastTime;
866     lastTime -= _timeadjustment;
867     if (lastTime < 0) lastTime = 0;
868     times.push_back(lastTime);
869     lastTime = 0;
870 }
871
872 performance_metrics& TestBase::calcMetrics()
873 {
874     if ((metrics.samples == (unsigned int)currentIter) || times.size() == 0)
875         return metrics;
876
877     metrics.bytesIn = getTotalInputSize();
878     metrics.bytesOut = getTotalOutputSize();
879     metrics.frequency = cv::getTickFrequency();
880     metrics.samples = (unsigned int)times.size();
881     metrics.outliers = 0;
882
883     if (metrics.terminationReason != performance_metrics::TERM_INTERRUPT && metrics.terminationReason != performance_metrics::TERM_EXCEPTION)
884     {
885         if (currentIter == nIters)
886             metrics.terminationReason = performance_metrics::TERM_ITERATIONS;
887         else if (totalTime >= timeLimit)
888             metrics.terminationReason = performance_metrics::TERM_TIME;
889         else
890             metrics.terminationReason = performance_metrics::TERM_UNKNOWN;
891     }
892
893     std::sort(times.begin(), times.end());
894
895     //estimate mean and stddev for log(time)
896     double gmean = 0;
897     double gstddev = 0;
898     int n = 0;
899     for(TimeVector::const_iterator i = times.begin(); i != times.end(); ++i)
900     {
901         double x = static_cast<double>(*i)/runsPerIteration;
902         if (x < DBL_EPSILON) continue;
903         double lx = log(x);
904
905         ++n;
906         double delta = lx - gmean;
907         gmean += delta / n;
908         gstddev += delta * (lx - gmean);
909     }
910
911     gstddev = n > 1 ? sqrt(gstddev / (n - 1)) : 0;
912
913     TimeVector::const_iterator start = times.begin();
914     TimeVector::const_iterator end = times.end();
915
916     //filter outliers assuming log-normal distribution
917     //http://stackoverflow.com/questions/1867426/modeling-distribution-of-performance-measurements
918     int offset = 0;
919     if (gstddev > DBL_EPSILON)
920     {
921         double minout = exp(gmean - 3 * gstddev) * runsPerIteration;
922         double maxout = exp(gmean + 3 * gstddev) * runsPerIteration;
923         while(*start < minout) ++start, ++metrics.outliers, ++offset;
924         do --end, ++metrics.outliers; while(*end > maxout);
925         ++end, --metrics.outliers;
926     }
927
928     metrics.min = static_cast<double>(*start)/runsPerIteration;
929     //calc final metrics
930     n = 0;
931     gmean = 0;
932     gstddev = 0;
933     double mean = 0;
934     double stddev = 0;
935     int m = 0;
936     for(; start != end; ++start)
937     {
938         double x = static_cast<double>(*start)/runsPerIteration;
939         if (x > DBL_EPSILON)
940         {
941             double lx = log(x);
942             ++m;
943             double gdelta = lx - gmean;
944             gmean += gdelta / m;
945             gstddev += gdelta * (lx - gmean);
946         }
947         ++n;
948         double delta = x - mean;
949         mean += delta / n;
950         stddev += delta * (x - mean);
951     }
952
953     metrics.mean = mean;
954     metrics.gmean = exp(gmean);
955     metrics.gstddev = m > 1 ? sqrt(gstddev / (m - 1)) : 0;
956     metrics.stddev = n > 1 ? sqrt(stddev / (n - 1)) : 0;
957     metrics.median = n % 2
958             ? (double)times[offset + n / 2]
959             : 0.5 * (times[offset + n / 2] + times[offset + n / 2 - 1]);
960
961     metrics.median /= runsPerIteration;
962
963     return metrics;
964 }
965
966 void TestBase::validateMetrics()
967 {
968     performance_metrics& m = calcMetrics();
969
970     if (HasFailure()) return;
971
972     ASSERT_GE(m.samples, 1u)
973       << "  No time measurements was performed.\nstartTimer() and stopTimer() commands are required for performance tests.";
974
975     EXPECT_GE(m.samples, param_min_samples)
976       << "  Only a few samples are collected.\nPlease increase number of iterations or/and time limit to get reliable performance measurements.";
977
978     if (m.gstddev > DBL_EPSILON)
979     {
980         EXPECT_GT(/*m.gmean * */1., /*m.gmean * */ 2 * sinh(m.gstddev * param_max_deviation))
981           << "  Test results are not reliable ((mean-sigma,mean+sigma) deviation interval is greater than measured time interval).";
982     }
983
984     EXPECT_LE(m.outliers, std::max((unsigned int)cvCeil(m.samples * param_max_outliers / 100.), 1u))
985       << "  Test results are not reliable (too many outliers).";
986 }
987
988 void TestBase::reportMetrics(bool toJUnitXML)
989 {
990     performance_metrics& m = calcMetrics();
991
992     if (toJUnitXML)
993     {
994         RecordProperty("bytesIn", (int)m.bytesIn);
995         RecordProperty("bytesOut", (int)m.bytesOut);
996         RecordProperty("term", m.terminationReason);
997         RecordProperty("samples", (int)m.samples);
998         RecordProperty("outliers", (int)m.outliers);
999         RecordProperty("frequency", cv::format("%.0f", m.frequency).c_str());
1000         RecordProperty("min", cv::format("%.0f", m.min).c_str());
1001         RecordProperty("median", cv::format("%.0f", m.median).c_str());
1002         RecordProperty("gmean", cv::format("%.0f", m.gmean).c_str());
1003         RecordProperty("gstddev", cv::format("%.6f", m.gstddev).c_str());
1004         RecordProperty("mean", cv::format("%.0f", m.mean).c_str());
1005         RecordProperty("stddev", cv::format("%.0f", m.stddev).c_str());
1006     }
1007     else
1008     {
1009         const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
1010         const char* type_param = test_info->type_param();
1011         const char* value_param = test_info->value_param();
1012
1013 #if defined(ANDROID) && defined(USE_ANDROID_LOGGING)
1014         LOGD("[ FAILED   ] %s.%s", test_info->test_case_name(), test_info->name());
1015 #endif
1016
1017         if (type_param)  LOGD("type      = %11s", type_param);
1018         if (value_param) LOGD("params    = %11s", value_param);
1019
1020         switch (m.terminationReason)
1021         {
1022         case performance_metrics::TERM_ITERATIONS:
1023             LOGD("termination reason:  reached maximum number of iterations");
1024             break;
1025         case performance_metrics::TERM_TIME:
1026             LOGD("termination reason:  reached time limit");
1027             break;
1028         case performance_metrics::TERM_INTERRUPT:
1029             LOGD("termination reason:  aborted by the performance testing framework");
1030             break;
1031         case performance_metrics::TERM_EXCEPTION:
1032             LOGD("termination reason:  unhandled exception");
1033             break;
1034         case performance_metrics::TERM_UNKNOWN:
1035         default:
1036             LOGD("termination reason:  unknown");
1037             break;
1038         };
1039
1040         LOGD("bytesIn   =%11lu", (unsigned long)m.bytesIn);
1041         LOGD("bytesOut  =%11lu", (unsigned long)m.bytesOut);
1042         if (nIters == (unsigned int)-1 || m.terminationReason == performance_metrics::TERM_ITERATIONS)
1043             LOGD("samples   =%11u",  m.samples);
1044         else
1045             LOGD("samples   =%11u of %u", m.samples, nIters);
1046         LOGD("outliers  =%11u", m.outliers);
1047         LOGD("frequency =%11.0f", m.frequency);
1048         if (m.samples > 0)
1049         {
1050             LOGD("min       =%11.0f = %.2fms", m.min, m.min * 1e3 / m.frequency);
1051             LOGD("median    =%11.0f = %.2fms", m.median, m.median * 1e3 / m.frequency);
1052             LOGD("gmean     =%11.0f = %.2fms", m.gmean, m.gmean * 1e3 / m.frequency);
1053             LOGD("gstddev   =%11.8f = %.2fms for 97%% dispersion interval", m.gstddev, m.gmean * 2 * sinh(m.gstddev * 3) * 1e3 / m.frequency);
1054             LOGD("mean      =%11.0f = %.2fms", m.mean, m.mean * 1e3 / m.frequency);
1055             LOGD("stddev    =%11.0f = %.2fms", m.stddev, m.stddev * 1e3 / m.frequency);
1056         }
1057     }
1058 }
1059
1060 void TestBase::SetUp()
1061 {
1062     cv::theRNG().state = param_seed; // this rng should generate same numbers for each run
1063
1064     if (param_threads >= 0)
1065         cv::setNumThreads(param_threads);
1066
1067 #ifdef ANDROID
1068     if (param_affinity_mask)
1069         setCurrentThreadAffinityMask(param_affinity_mask);
1070 #endif
1071
1072     verified = false;
1073     lastTime = 0;
1074     totalTime = 0;
1075     runsPerIteration = 1;
1076     nIters = iterationsLimitDefault;
1077     currentIter = (unsigned int)-1;
1078     timeLimit = timeLimitDefault;
1079     times.clear();
1080 }
1081
1082 void TestBase::TearDown()
1083 {
1084     if (!HasFailure() && !verified)
1085         ADD_FAILURE() << "The test has no sanity checks. There should be at least one check at the end of performance test.";
1086
1087     validateMetrics();
1088     if (HasFailure())
1089         reportMetrics(false);
1090     else
1091     {
1092         const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
1093         const char* type_param = test_info->type_param();
1094         const char* value_param = test_info->value_param();
1095         if (value_param) printf("[ VALUE    ] \t%s\n", value_param), fflush(stdout);
1096         if (type_param)  printf("[ TYPE     ] \t%s\n", type_param), fflush(stdout);
1097         reportMetrics(true);
1098     }
1099 }
1100
1101 std::string TestBase::getDataPath(const std::string& relativePath)
1102 {
1103     if (relativePath.empty())
1104     {
1105         ADD_FAILURE() << "  Bad path to test resource";
1106         throw PerfEarlyExitException();
1107     }
1108
1109     const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
1110     const char *path_separator = "/";
1111
1112     std::string path;
1113     if (data_path_dir)
1114     {
1115         int len = (int)strlen(data_path_dir) - 1;
1116         if (len < 0) len = 0;
1117         path = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
1118                 + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator);
1119     }
1120     else
1121     {
1122         path = ".";
1123         path += path_separator;
1124     }
1125
1126     if (relativePath[0] == '/' || relativePath[0] == '\\')
1127         path += relativePath.substr(1);
1128     else
1129         path += relativePath;
1130
1131     FILE* fp = fopen(path.c_str(), "r");
1132     if (fp)
1133         fclose(fp);
1134     else
1135     {
1136         ADD_FAILURE() << "  Requested file \"" << path << "\" does not exist.";
1137         throw PerfEarlyExitException();
1138     }
1139     return path;
1140 }
1141
1142 void TestBase::RunPerfTestBody()
1143 {
1144     try
1145     {
1146         this->PerfTestBody();
1147     }
1148     catch(PerfEarlyExitException)
1149     {
1150         metrics.terminationReason = performance_metrics::TERM_INTERRUPT;
1151         return;//no additional failure logging
1152     }
1153     catch(cv::Exception e)
1154     {
1155         metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
1156         #ifdef HAVE_CUDA
1157             if (e.code == CV_GpuApiCallError)
1158                 cv::gpu::resetDevice();
1159         #endif
1160         FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n  Actual: it throws cv::Exception:\n  " << e.what();
1161     }
1162     catch(std::exception e)
1163     {
1164         metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
1165         FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n  Actual: it throws std::exception:\n  " << e.what();
1166     }
1167     catch(...)
1168     {
1169         metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
1170         FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n  Actual: it throws...";
1171     }
1172 }
1173
1174 /*****************************************************************************************\
1175 *                          ::perf::TestBase::_declareHelper
1176 \*****************************************************************************************/
1177 TestBase::_declareHelper& TestBase::_declareHelper::iterations(unsigned int n)
1178 {
1179     test->times.clear();
1180     test->times.reserve(n);
1181     test->nIters = std::min(n, TestBase::iterationsLimitDefault);
1182     test->currentIter = (unsigned int)-1;
1183     return *this;
1184 }
1185
1186 TestBase::_declareHelper& TestBase::_declareHelper::time(double timeLimitSecs)
1187 {
1188     test->times.clear();
1189     test->currentIter = (unsigned int)-1;
1190     test->timeLimit = (int64)(timeLimitSecs * cv::getTickFrequency());
1191     return *this;
1192 }
1193
1194 TestBase::_declareHelper& TestBase::_declareHelper::tbb_threads(int n)
1195 {
1196     cv::setNumThreads(n);
1197     return *this;
1198 }
1199
1200 TestBase::_declareHelper& TestBase::_declareHelper::runs(unsigned int runsNumber)
1201 {
1202     test->runsPerIteration = runsNumber;
1203     return *this;
1204 }
1205
1206 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, int wtype)
1207 {
1208     if (!test->times.empty()) return *this;
1209     TestBase::declareArray(test->inputData, a1, wtype);
1210     return *this;
1211 }
1212
1213 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)
1214 {
1215     if (!test->times.empty()) return *this;
1216     TestBase::declareArray(test->inputData, a1, wtype);
1217     TestBase::declareArray(test->inputData, a2, wtype);
1218     return *this;
1219 }
1220
1221 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)
1222 {
1223     if (!test->times.empty()) return *this;
1224     TestBase::declareArray(test->inputData, a1, wtype);
1225     TestBase::declareArray(test->inputData, a2, wtype);
1226     TestBase::declareArray(test->inputData, a3, wtype);
1227     return *this;
1228 }
1229
1230 TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)
1231 {
1232     if (!test->times.empty()) return *this;
1233     TestBase::declareArray(test->inputData, a1, wtype);
1234     TestBase::declareArray(test->inputData, a2, wtype);
1235     TestBase::declareArray(test->inputData, a3, wtype);
1236     TestBase::declareArray(test->inputData, a4, wtype);
1237     return *this;
1238 }
1239
1240 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, int wtype)
1241 {
1242     if (!test->times.empty()) return *this;
1243     TestBase::declareArray(test->outputData, a1, wtype);
1244     return *this;
1245 }
1246
1247 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)
1248 {
1249     if (!test->times.empty()) return *this;
1250     TestBase::declareArray(test->outputData, a1, wtype);
1251     TestBase::declareArray(test->outputData, a2, wtype);
1252     return *this;
1253 }
1254
1255 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)
1256 {
1257     if (!test->times.empty()) return *this;
1258     TestBase::declareArray(test->outputData, a1, wtype);
1259     TestBase::declareArray(test->outputData, a2, wtype);
1260     TestBase::declareArray(test->outputData, a3, wtype);
1261     return *this;
1262 }
1263
1264 TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)
1265 {
1266     if (!test->times.empty()) return *this;
1267     TestBase::declareArray(test->outputData, a1, wtype);
1268     TestBase::declareArray(test->outputData, a2, wtype);
1269     TestBase::declareArray(test->outputData, a3, wtype);
1270     TestBase::declareArray(test->outputData, a4, wtype);
1271     return *this;
1272 }
1273
1274 TestBase::_declareHelper::_declareHelper(TestBase* t) : test(t)
1275 {
1276 }
1277
1278 /*****************************************************************************************\
1279 *                                  miscellaneous
1280 \*****************************************************************************************/
1281
1282 namespace {
1283 struct KeypointComparator
1284 {
1285     std::vector<cv::KeyPoint>& pts_;
1286     comparators::KeypointGreater cmp;
1287
1288     KeypointComparator(std::vector<cv::KeyPoint>& pts) : pts_(pts), cmp() {}
1289
1290     bool operator()(int idx1, int idx2) const
1291     {
1292         return cmp(pts_[idx1], pts_[idx2]);
1293     }
1294 private:
1295     const KeypointComparator& operator=(const KeypointComparator&); // quiet MSVC
1296 };
1297 }//namespace
1298
1299 void perf::sort(std::vector<cv::KeyPoint>& pts, cv::InputOutputArray descriptors)
1300 {
1301     cv::Mat desc = descriptors.getMat();
1302
1303     CV_Assert(pts.size() == (size_t)desc.rows);
1304     cv::AutoBuffer<int> idxs(desc.rows);
1305
1306     for (int i = 0; i < desc.rows; ++i)
1307         idxs[i] = i;
1308
1309     std::sort((int*)idxs, (int*)idxs + desc.rows, KeypointComparator(pts));
1310
1311     std::vector<cv::KeyPoint> spts(pts.size());
1312     cv::Mat sdesc(desc.size(), desc.type());
1313
1314     for(int j = 0; j < desc.rows; ++j)
1315     {
1316         spts[j] = pts[idxs[j]];
1317         cv::Mat row = sdesc.row(j);
1318         desc.row(idxs[j]).copyTo(row);
1319     }
1320
1321     spts.swap(pts);
1322     sdesc.copyTo(desc);
1323 }
1324
1325 /*****************************************************************************************\
1326 *                                  ::perf::GpuPerf
1327 \*****************************************************************************************/
1328 #ifdef HAVE_CUDA
1329 bool perf::GpuPerf::targetDevice()
1330 {
1331     return !param_run_cpu;
1332 }
1333 #endif
1334
1335 /*****************************************************************************************\
1336 *                                  ::perf::PrintTo
1337 \*****************************************************************************************/
1338 namespace perf
1339 {
1340
1341 void PrintTo(const MatType& t, ::std::ostream* os)
1342 {
1343     switch( CV_MAT_DEPTH((int)t) )
1344     {
1345         case CV_8U:  *os << "8U";  break;
1346         case CV_8S:  *os << "8S";  break;
1347         case CV_16U: *os << "16U"; break;
1348         case CV_16S: *os << "16S"; break;
1349         case CV_32S: *os << "32S"; break;
1350         case CV_32F: *os << "32F"; break;
1351         case CV_64F: *os << "64F"; break;
1352         case CV_USRTYPE1: *os << "USRTYPE1"; break;
1353         default: *os << "INVALID_TYPE"; break;
1354     }
1355     *os << 'C' << CV_MAT_CN((int)t);
1356 }
1357
1358 } //namespace perf
1359
1360 /*****************************************************************************************\
1361 *                                  ::cv::PrintTo
1362 \*****************************************************************************************/
1363 namespace cv {
1364
1365 void PrintTo(const Size& sz, ::std::ostream* os)
1366 {
1367     *os << /*"Size:" << */sz.width << "x" << sz.height;
1368 }
1369
1370 }  // namespace cv
1371