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