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