-#include "precomp.hpp"\r
-\r
-#ifdef ANDROID\r
-# include <sys/time.h>\r
-#endif\r
-\r
-using namespace perf;\r
-\r
-int64 TestBase::timeLimitDefault = 0;\r
-unsigned int TestBase::iterationsLimitDefault = (unsigned int)(-1);\r
-int64 TestBase::_timeadjustment = 0;\r
-\r
-const char *command_line_keys =\r
-{\r
- "{ |perf_max_outliers |8 |percent of allowed outliers}"\r
- "{ |perf_min_samples |10 |minimal required numer of samples}"\r
- "{ |perf_force_samples |100 |force set maximum number of samples for all tests}"\r
- "{ |perf_seed |809564 |seed for random numbers generator}"\r
- "{ |perf_tbb_nthreads |-1 |if TBB is enabled, the number of TBB threads}"\r
- "{ |perf_write_sanity |false |allow to create new records for sanity checks}"\r
- #ifdef ANDROID\r
- "{ |perf_time_limit |6.0 |default time limit for a single test (in seconds)}"\r
- "{ |perf_affinity_mask |0 |set affinity mask for the main thread}"\r
- "{ |perf_log_power_checkpoints |false |additional xml logging for power measurement}"\r
- #else\r
- "{ |perf_time_limit |3.0 |default time limit for a single test (in seconds)}"\r
- #endif\r
- "{ |perf_max_deviation |1.0 |}"\r
- "{h |help |false |}"\r
-};\r
-\r
-static double param_max_outliers;\r
-static double param_max_deviation;\r
-static unsigned int param_min_samples;\r
-static unsigned int param_force_samples;\r
-static uint64 param_seed;\r
-static double param_time_limit;\r
-static int param_tbb_nthreads;\r
-static bool param_write_sanity;\r
-#ifdef ANDROID\r
-static int param_affinity_mask;\r
-static bool log_power_checkpoints;\r
-\r
-#include <sys/syscall.h>\r
-#include <pthread.h>\r
-static void setCurrentThreadAffinityMask(int mask)\r
-{\r
- pid_t pid=gettid();\r
- int syscallres=syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);\r
- if (syscallres)\r
- {\r
- int err=errno;\r
- err=err;//to avoid warnings about unused variables\r
- LOGE("Error in the syscall setaffinity: mask=%d=0x%x err=%d=0x%x", mask, mask, err, err);\r
- }\r
-}\r
-\r
-#endif\r
-\r
-static void randu(cv::Mat& m)\r
-{\r
- const int bigValue = 0x00000FFF;\r
- if (m.depth() < CV_32F)\r
- {\r
- int minmax[] = {0, 256};\r
- cv::Mat mr = cv::Mat(m.rows, (int)(m.cols * m.elemSize()), CV_8U, m.ptr(), m.step[0]);\r
- cv::randu(mr, cv::Mat(1, 1, CV_32S, minmax), cv::Mat(1, 1, CV_32S, minmax + 1));\r
- }\r
- else if (m.depth() == CV_32F)\r
- {\r
- //float minmax[] = {-FLT_MAX, FLT_MAX};\r
- float minmax[] = {-bigValue, bigValue};\r
- cv::Mat mr = m.reshape(1);\r
- cv::randu(mr, cv::Mat(1, 1, CV_32F, minmax), cv::Mat(1, 1, CV_32F, minmax + 1));\r
- }\r
- else\r
- {\r
- //double minmax[] = {-DBL_MAX, DBL_MAX};\r
- double minmax[] = {-bigValue, bigValue};\r
- cv::Mat mr = m.reshape(1);\r
- cv::randu(mr, cv::Mat(1, 1, CV_64F, minmax), cv::Mat(1, 1, CV_64F, minmax + 1));\r
- }\r
-}\r
-\r
-/*****************************************************************************************\\r
-* inner exception class for early termination\r
-\*****************************************************************************************/\r
-\r
-class PerfEarlyExitException: public cv::Exception {};\r
-\r
-/*****************************************************************************************\\r
-* ::perf::Regression\r
-\*****************************************************************************************/\r
-\r
-Regression& Regression::instance()\r
-{\r
- static Regression single;\r
- return single;\r
-}\r
-\r
-Regression& Regression::add(const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)\r
-{\r
- return instance()(name, array, eps, err);\r
-}\r
-\r
-void Regression::Init(const std::string& testSuitName, const std::string& ext)\r
-{\r
- instance().init(testSuitName, ext);\r
-}\r
-\r
-void Regression::init(const std::string& testSuitName, const std::string& ext)\r
-{\r
- if (!storageInPath.empty())\r
- {\r
- LOGE("Subsequent initialisation of Regression utility is not allowed.");\r
- return;\r
- }\r
-\r
- const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");\r
- const char *path_separator = "/";\r
-\r
- if (data_path_dir)\r
- {\r
- int len = (int)strlen(data_path_dir)-1;\r
- if (len < 0) len = 0;\r
- std::string path_base = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))\r
- + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator)\r
- + "perf"\r
- + path_separator;\r
-\r
- storageInPath = path_base + testSuitName + ext;\r
- storageOutPath = path_base + testSuitName;\r
- }\r
- else\r
- {\r
- storageInPath = testSuitName + ext;\r
- storageOutPath = testSuitName;\r
- }\r
-\r
- try\r
- {\r
- if (storageIn.open(storageInPath, cv::FileStorage::READ))\r
- {\r
- rootIn = storageIn.root();\r
- if (storageInPath.length() > 3 && storageInPath.substr(storageInPath.length()-3) == ".gz")\r
- storageOutPath += "_new";\r
- storageOutPath += ext;\r
- }\r
- }\r
- catch(cv::Exception&)\r
- {\r
- LOGE("Failed to open sanity data for reading: %s", storageInPath.c_str());\r
- }\r
-\r
- if(!storageIn.isOpened())\r
- storageOutPath = storageInPath;\r
-}\r
-\r
-Regression::Regression() : regRNG(cv::getTickCount())//this rng should be really random\r
-{\r
-}\r
-\r
-Regression::~Regression()\r
-{\r
- if (storageIn.isOpened())\r
- storageIn.release();\r
- if (storageOut.isOpened())\r
- {\r
- if (!currentTestNodeName.empty())\r
- storageOut << "}";\r
- storageOut.release();\r
- }\r
-}\r
-\r
-cv::FileStorage& Regression::write()\r
-{\r
- if (!storageOut.isOpened() && !storageOutPath.empty())\r
- {\r
- int mode = (storageIn.isOpened() && storageInPath == storageOutPath)\r
- ? cv::FileStorage::APPEND : cv::FileStorage::WRITE;\r
- storageOut.open(storageOutPath, mode);\r
- if (!storageOut.isOpened())\r
- {\r
- LOGE("Could not open \"%s\" file for writing", storageOutPath.c_str());\r
- storageOutPath.clear();\r
- }\r
- else if (mode == cv::FileStorage::WRITE && !rootIn.empty())\r
- {\r
- //TODO: write content of rootIn node into the storageOut\r
- }\r
- }\r
- return storageOut;\r
-}\r
-\r
-std::string Regression::getCurrentTestNodeName()\r
-{\r
- const ::testing::TestInfo* const test_info =\r
- ::testing::UnitTest::GetInstance()->current_test_info();\r
-\r
- if (test_info == 0)\r
- return "undefined";\r
-\r
- std::string nodename = std::string(test_info->test_case_name()) + "--" + test_info->name();\r
- size_t idx = nodename.find_first_of('/');\r
- if (idx != std::string::npos)\r
- nodename.erase(idx);\r
-\r
- const char* type_param = test_info->type_param();\r
- if (type_param != 0)\r
- (nodename += "--") += type_param;\r
-\r
- const char* value_param = test_info->value_param();\r
- if (value_param != 0)\r
- (nodename += "--") += value_param;\r
-\r
- for(size_t i = 0; i < nodename.length(); ++i)\r
- if (!isalnum(nodename[i]) && '_' != nodename[i])\r
- nodename[i] = '-';\r
-\r
- return nodename;\r
-}\r
-\r
-bool Regression::isVector(cv::InputArray a)\r
-{\r
- return a.kind() == cv::_InputArray::STD_VECTOR_MAT || a.kind() == cv::_InputArray::STD_VECTOR_VECTOR;\r
-}\r
-\r
-double Regression::getElem(cv::Mat& m, int y, int x, int cn)\r
-{\r
- switch (m.depth())\r
- {\r
- case CV_8U: return *(m.ptr<unsigned char>(y, x) + cn);\r
- case CV_8S: return *(m.ptr<signed char>(y, x) + cn);\r
- case CV_16U: return *(m.ptr<unsigned short>(y, x) + cn);\r
- case CV_16S: return *(m.ptr<signed short>(y, x) + cn);\r
- case CV_32S: return *(m.ptr<signed int>(y, x) + cn);\r
- case CV_32F: return *(m.ptr<float>(y, x) + cn);\r
- case CV_64F: return *(m.ptr<double>(y, x) + cn);\r
- default: return 0;\r
- }\r
-}\r
-\r
-void Regression::write(cv::Mat m)\r
-{\r
- double min, max;\r
- cv::minMaxLoc(m, &min, &max);\r
- write() << "min" << min << "max" << max;\r
-\r
- write() << "last" << "{" << "x" << m.cols-1 << "y" << m.rows-1\r
- << "val" << getElem(m, m.rows-1, m.cols-1, m.channels()-1) << "}";\r
-\r
- int x, y, cn;\r
- x = regRNG.uniform(0, m.cols);\r
- y = regRNG.uniform(0, m.rows);\r
- cn = regRNG.uniform(0, m.channels());\r
- write() << "rng1" << "{" << "x" << x << "y" << y;\r
- if(cn > 0) write() << "cn" << cn;\r
- write() << "val" << getElem(m, y, x, cn) << "}";\r
-\r
- x = regRNG.uniform(0, m.cols);\r
- y = regRNG.uniform(0, m.rows);\r
- cn = regRNG.uniform(0, m.channels());\r
- write() << "rng2" << "{" << "x" << x << "y" << y;\r
- if (cn > 0) write() << "cn" << cn;\r
- write() << "val" << getElem(m, y, x, cn) << "}";\r
-}\r
-\r
-static double evalEps(double expected, double actual, double _eps, ERROR_TYPE err)\r
-{\r
- if (err == ERROR_ABSOLUTE)\r
- return _eps;\r
- else if (err == ERROR_RELATIVE)\r
- return std::max(std::abs(expected), std::abs(actual)) * err;\r
- return 0;\r
-}\r
-\r
-void Regression::verify(cv::FileNode node, cv::Mat actual, double _eps, std::string argname, ERROR_TYPE err)\r
-{\r
- double actual_min, actual_max;\r
- cv::minMaxLoc(actual, &actual_min, &actual_max);\r
-\r
- double eps = evalEps((double)node["min"], actual_min, _eps, err);\r
- ASSERT_NEAR((double)node["min"], actual_min, eps)\r
- << " " << argname << " has unexpected minimal value";\r
-\r
- eps = evalEps((double)node["max"], actual_max, _eps, err);\r
- ASSERT_NEAR((double)node["max"], actual_max, eps)\r
- << " " << argname << " has unexpected maximal value";\r
-\r
- cv::FileNode last = node["last"];\r
- double actualLast = getElem(actual, actual.rows - 1, actual.cols - 1, actual.channels() - 1);\r
- ASSERT_EQ((int)last["x"], actual.cols - 1)\r
- << " " << argname << " has unexpected number of columns";\r
- ASSERT_EQ((int)last["y"], actual.rows - 1)\r
- << " " << argname << " has unexpected number of rows";\r
-\r
- eps = evalEps((double)last["val"], actualLast, _eps, err);\r
- ASSERT_NEAR((double)last["val"], actualLast, eps)\r
- << " " << argname << " has unexpected value of last element";\r
-\r
- cv::FileNode rng1 = node["rng1"];\r
- int x1 = rng1["x"];\r
- int y1 = rng1["y"];\r
- int cn1 = rng1["cn"];\r
-\r
- eps = evalEps((double)rng1["val"], getElem(actual, y1, x1, cn1), _eps, err);\r
- ASSERT_NEAR((double)rng1["val"], getElem(actual, y1, x1, cn1), eps)\r
- << " " << argname << " has unexpected value of ["<< x1 << ":" << y1 << ":" << cn1 <<"] element";\r
-\r
- cv::FileNode rng2 = node["rng2"];\r
- int x2 = rng2["x"];\r
- int y2 = rng2["y"];\r
- int cn2 = rng2["cn"];\r
-\r
- eps = evalEps((double)rng2["val"], getElem(actual, y2, x2, cn2), _eps, err);\r
- ASSERT_NEAR((double)rng2["val"], getElem(actual, y2, x2, cn2), eps)\r
- << " " << argname << " has unexpected value of ["<< x2 << ":" << y2 << ":" << cn2 <<"] element";\r
-}\r
-\r
-void Regression::write(cv::InputArray array)\r
-{\r
- write() << "kind" << array.kind();\r
- write() << "type" << array.type();\r
- if (isVector(array))\r
- {\r
- int total = (int)array.total();\r
- int idx = regRNG.uniform(0, total);\r
- write() << "len" << total;\r
- write() << "idx" << idx;\r
-\r
- cv::Mat m = array.getMat(idx);\r
-\r
- if (m.total() * m.channels() < 26) //5x5 or smaller\r
- write() << "val" << m;\r
- else\r
- write(m);\r
- }\r
- else\r
- {\r
- if (array.total() * array.channels() < 26) //5x5 or smaller\r
- write() << "val" << array.getMat();\r
- else\r
- write(array.getMat());\r
- }\r
-}\r
-\r
-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)\r
-{\r
- cv::Mat diff64f;\r
- diff.reshape(1).convertTo(diff64f, CV_64F);\r
-\r
- cv::Mat expected_abs = cv::abs(expected.reshape(1));\r
- cv::Mat actual_abs = cv::abs(actual.reshape(1));\r
- cv::Mat maximum, mask;\r
- cv::max(expected_abs, actual_abs, maximum);\r
- cv::multiply(maximum, cv::Vec<double, 1>(eps), maximum, CV_64F);\r
- cv::compare(diff64f, maximum, mask, cv::CMP_GT);\r
-\r
- int v = cv::countNonZero(mask);\r
-\r
- if (v > 0 && max_violation != 0 && max_allowed != 0)\r
- {\r
- int loc[10];\r
- cv::minMaxIdx(maximum, 0, max_allowed, 0, loc, mask);\r
- *max_violation = diff64f.at<double>(loc[1], loc[0]);\r
- }\r
-\r
- return v;\r
-}\r
-\r
-void Regression::verify(cv::FileNode node, cv::InputArray array, double eps, ERROR_TYPE err)\r
-{\r
- ASSERT_EQ((int)node["kind"], array.kind()) << " Argument \"" << node.name() << "\" has unexpected kind";\r
- ASSERT_EQ((int)node["type"], array.type()) << " Argument \"" << node.name() << "\" has unexpected type";\r
-\r
- cv::FileNode valnode = node["val"];\r
- if (isVector(array))\r
- {\r
- ASSERT_EQ((int)node["len"], (int)array.total()) << " Vector \"" << node.name() << "\" has unexpected length";\r
- int idx = node["idx"];\r
-\r
- cv::Mat actual = array.getMat(idx);\r
-\r
- if (valnode.isNone())\r
- {\r
- ASSERT_LE((size_t)26, actual.total() * (size_t)actual.channels())\r
- << " \"" << node.name() << "[" << idx << "]\" has unexpected number of elements";\r
- verify(node, actual, eps, cv::format("%s[%d]", node.name().c_str(), idx), err);\r
- }\r
- else\r
- {\r
- cv::Mat expected;\r
- valnode >> expected;\r
-\r
- ASSERT_EQ(expected.size(), actual.size())\r
- << " " << node.name() << "[" << idx<< "] has unexpected size";\r
-\r
- cv::Mat diff;\r
- cv::absdiff(expected, actual, diff);\r
-\r
- if (err == ERROR_ABSOLUTE)\r
- {\r
- if (!cv::checkRange(diff, true, 0, 0, eps))\r
- {\r
- double max;\r
- cv::minMaxLoc(diff.reshape(1), 0, &max);\r
- FAIL() << " Absolute difference (=" << max << ") between argument \""\r
- << node.name() << "[" << idx << "]\" and expected value is bugger than " << eps;\r
- }\r
- }\r
- else if (err == ERROR_RELATIVE)\r
- {\r
- double maxv, maxa;\r
- int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);\r
- if (violations > 0)\r
- {\r
- FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \""\r
- << node.name() << "[" << idx << "]\" and expected value is bugger than " << eps << " in " << violations << " points";\r
- }\r
- }\r
- }\r
- }\r
- else\r
- {\r
- if (valnode.isNone())\r
- {\r
- ASSERT_LE((size_t)26, array.total() * (size_t)array.channels())\r
- << " Argument \"" << node.name() << "\" has unexpected number of elements";\r
- verify(node, array.getMat(), eps, "Argument " + node.name(), err);\r
- }\r
- else\r
- {\r
- cv::Mat expected;\r
- valnode >> expected;\r
- cv::Mat actual = array.getMat();\r
-\r
- ASSERT_EQ(expected.size(), actual.size())\r
- << " Argument \"" << node.name() << "\" has unexpected size";\r
-\r
- cv::Mat diff;\r
- cv::absdiff(expected, actual, diff);\r
-\r
- if (err == ERROR_ABSOLUTE)\r
- {\r
- if (!cv::checkRange(diff, true, 0, 0, eps))\r
- {\r
- double max;\r
- cv::minMaxLoc(diff.reshape(1), 0, &max);\r
- FAIL() << " Difference (=" << max << ") between argument \"" << node.name()\r
- << "\" and expected value is bugger than " << eps;\r
- }\r
- }\r
- else if (err == ERROR_RELATIVE)\r
- {\r
- double maxv, maxa;\r
- int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);\r
- if (violations > 0)\r
- {\r
- FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \"" << node.name()\r
- << "\" and expected value is bugger than " << eps << " in " << violations << " points";\r
- }\r
- }\r
- }\r
- }\r
-}\r
-\r
-Regression& Regression::operator() (const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)\r
-{\r
- std::string nodename = getCurrentTestNodeName();\r
-\r
- cv::FileNode n = rootIn[nodename];\r
- if(n.isNone())\r
- {\r
- if(param_write_sanity)\r
- {\r
- if (nodename != currentTestNodeName)\r
- {\r
- if (!currentTestNodeName.empty())\r
- write() << "}";\r
- currentTestNodeName = nodename;\r
-\r
- write() << nodename << "{";\r
- }\r
- write() << name << "{";\r
- write(array);\r
- write() << "}";\r
- }\r
- }\r
- else\r
- {\r
- cv::FileNode this_arg = n[name];\r
- if (!this_arg.isMap())\r
- ADD_FAILURE() << " No regression data for " << name << " argument";\r
- else\r
- verify(this_arg, array, eps, err);\r
- }\r
- return *this;\r
-}\r
-\r
-\r
-/*****************************************************************************************\\r
-* ::perf::performance_metrics\r
-\*****************************************************************************************/\r
-performance_metrics::performance_metrics()\r
-{\r
- bytesIn = 0;\r
- bytesOut = 0;\r
- samples = 0;\r
- outliers = 0;\r
- gmean = 0;\r
- gstddev = 0;\r
- mean = 0;\r
- stddev = 0;\r
- median = 0;\r
- min = 0;\r
- frequency = 0;\r
- terminationReason = TERM_UNKNOWN;\r
-}\r
-\r
-\r
-/*****************************************************************************************\\r
-* ::perf::TestBase\r
-\*****************************************************************************************/\r
-\r
-\r
-void TestBase::Init(int argc, const char* const argv[])\r
-{\r
- cv::CommandLineParser args(argc, argv, command_line_keys);\r
- param_max_outliers = std::min(100., std::max(0., args.get<double>("perf_max_outliers")));\r
- param_min_samples = std::max(1u, args.get<unsigned int>("perf_min_samples"));\r
- param_max_deviation = std::max(0., args.get<double>("perf_max_deviation"));\r
- param_seed = args.get<uint64>("perf_seed");\r
- param_time_limit = std::max(0., args.get<double>("perf_time_limit"));\r
- param_force_samples = args.get<unsigned int>("perf_force_samples");\r
- param_write_sanity = args.get<bool>("perf_write_sanity");\r
- param_tbb_nthreads = args.get<int>("perf_tbb_nthreads");\r
-#ifdef ANDROID\r
- param_affinity_mask = args.get<int>("perf_affinity_mask");\r
- log_power_checkpoints = args.get<bool>("perf_log_power_checkpoints");\r
-#endif\r
-\r
- if (args.get<bool>("help"))\r
- {\r
- args.printParams();\r
- printf("\n\n");\r
- return;\r
- }\r
-\r
- timeLimitDefault = param_time_limit == 0.0 ? 1 : (int64)(param_time_limit * cv::getTickFrequency());\r
- iterationsLimitDefault = param_force_samples == 0 ? (unsigned)(-1) : param_force_samples;\r
- _timeadjustment = _calibrate();\r
-}\r
-\r
-int64 TestBase::_calibrate()\r
-{\r
- class _helper : public ::perf::TestBase\r
- {\r
- public:\r
- performance_metrics& getMetrics() { return calcMetrics(); }\r
- virtual void TestBody() {}\r
- virtual void PerfTestBody()\r
- {\r
- //the whole system warmup\r
- SetUp();\r
- cv::Mat a(2048, 2048, CV_32S, cv::Scalar(1));\r
- cv::Mat b(2048, 2048, CV_32S, cv::Scalar(2));\r
- declare.time(30);\r
- double s = 0;\r
- for(declare.iterations(20); startTimer(), next(); stopTimer())\r
- s+=a.dot(b);\r
- declare.time(s);\r
-\r
- //self calibration\r
- SetUp();\r
- for(declare.iterations(1000); startTimer(), next(); stopTimer()){}\r
- }\r
- };\r
-\r
- _timeadjustment = 0;\r
- _helper h;\r
- h.PerfTestBody();\r
- double compensation = h.getMetrics().min;\r
- LOGD("Time compensation is %.0f", compensation);\r
- return (int64)compensation;\r
-}\r
-\r
-#ifdef _MSC_VER\r
-# pragma warning(push)\r
-# pragma warning(disable:4355) // 'this' : used in base member initializer list\r
-#endif\r
-TestBase::TestBase(): declare(this)\r
-{\r
-}\r
-#ifdef _MSC_VER\r
-# pragma warning(pop)\r
-#endif\r
-\r
-\r
-void TestBase::declareArray(SizeVector& sizes, cv::InputOutputArray a, int wtype)\r
-{\r
- if (!a.empty())\r
- {\r
- sizes.push_back(std::pair<int, cv::Size>(getSizeInBytes(a), getSize(a)));\r
- warmup(a, wtype);\r
- }\r
- else if (a.kind() != cv::_InputArray::NONE)\r
- ADD_FAILURE() << " Uninitialized input/output parameters are not allowed for performance tests";\r
-}\r
-\r
-void TestBase::warmup(cv::InputOutputArray a, int wtype)\r
-{\r
- if (a.empty()) return;\r
- if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)\r
- warmup_impl(a.getMat(), wtype);\r
- else\r
- {\r
- size_t total = a.total();\r
- for (size_t i = 0; i < total; ++i)\r
- warmup_impl(a.getMat((int)i), wtype);\r
- }\r
-}\r
-\r
-int TestBase::getSizeInBytes(cv::InputArray a)\r
-{\r
- if (a.empty()) return 0;\r
- int total = (int)a.total();\r
- if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)\r
- return total * CV_ELEM_SIZE(a.type());\r
-\r
- int size = 0;\r
- for (int i = 0; i < total; ++i)\r
- size += (int)a.total(i) * CV_ELEM_SIZE(a.type(i));\r
-\r
- return size;\r
-}\r
-\r
-cv::Size TestBase::getSize(cv::InputArray a)\r
-{\r
- if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)\r
- return a.size();\r
- return cv::Size();\r
-}\r
-\r
-bool TestBase::next()\r
-{\r
- bool has_next = ++currentIter < nIters && totalTime < timeLimit;\r
-#ifdef ANDROID\r
- if (log_power_checkpoints)\r
- {\r
- timeval tim;\r
- gettimeofday(&tim, NULL);\r
- unsigned long long t1 = tim.tv_sec * 1000LLU + (unsigned long long)(tim.tv_usec / 1000.f);\r
-\r
- if (currentIter == 1) RecordProperty("test_start", cv::format("%llu",t1).c_str());\r
- if (!has_next) RecordProperty("test_complete", cv::format("%llu",t1).c_str());\r
- }\r
-#endif\r
- return has_next;\r
-}\r
-\r
-void TestBase::warmup_impl(cv::Mat m, int wtype)\r
-{\r
- switch(wtype)\r
- {\r
- case WARMUP_READ:\r
- cv::sum(m.reshape(1));\r
- return;\r
- case WARMUP_WRITE:\r
- m.reshape(1).setTo(cv::Scalar::all(0));\r
- return;\r
- case WARMUP_RNG:\r
- randu(m);\r
- return;\r
- default:\r
- return;\r
- }\r
-}\r
-\r
-unsigned int TestBase::getTotalInputSize() const\r
-{\r
- unsigned int res = 0;\r
- for (SizeVector::const_iterator i = inputData.begin(); i != inputData.end(); ++i)\r
- res += i->first;\r
- return res;\r
-}\r
-\r
-unsigned int TestBase::getTotalOutputSize() const\r
-{\r
- unsigned int res = 0;\r
- for (SizeVector::const_iterator i = outputData.begin(); i != outputData.end(); ++i)\r
- res += i->first;\r
- return res;\r
-}\r
-\r
-void TestBase::startTimer()\r
-{\r
- lastTime = cv::getTickCount();\r
-}\r
-\r
-void TestBase::stopTimer()\r
-{\r
- int64 time = cv::getTickCount();\r
- if (lastTime == 0)\r
- ADD_FAILURE() << " stopTimer() is called before startTimer()";\r
- lastTime = time - lastTime;\r
- totalTime += lastTime;\r
- lastTime -= _timeadjustment;\r
- if (lastTime < 0) lastTime = 0;\r
- times.push_back(lastTime);\r
- lastTime = 0;\r
-}\r
-\r
-performance_metrics& TestBase::calcMetrics()\r
-{\r
- if ((metrics.samples == (unsigned int)currentIter) || times.size() == 0)\r
- return metrics;\r
-\r
- metrics.bytesIn = getTotalInputSize();\r
- metrics.bytesOut = getTotalOutputSize();\r
- metrics.frequency = cv::getTickFrequency();\r
- metrics.samples = (unsigned int)times.size();\r
- metrics.outliers = 0;\r
-\r
- if (metrics.terminationReason != performance_metrics::TERM_INTERRUPT && metrics.terminationReason != performance_metrics::TERM_EXCEPTION)\r
- {\r
- if (currentIter == nIters)\r
- metrics.terminationReason = performance_metrics::TERM_ITERATIONS;\r
- else if (totalTime >= timeLimit)\r
- metrics.terminationReason = performance_metrics::TERM_TIME;\r
- else\r
- metrics.terminationReason = performance_metrics::TERM_UNKNOWN;\r
- }\r
-\r
- std::sort(times.begin(), times.end());\r
-\r
- //estimate mean and stddev for log(time)\r
- double gmean = 0;\r
- double gstddev = 0;\r
- int n = 0;\r
- for(TimeVector::const_iterator i = times.begin(); i != times.end(); ++i)\r
- {\r
- double x = static_cast<double>(*i)/runsPerIteration;\r
- if (x < DBL_EPSILON) continue;\r
- double lx = log(x);\r
-\r
- ++n;\r
- double delta = lx - gmean;\r
- gmean += delta / n;\r
- gstddev += delta * (lx - gmean);\r
- }\r
-\r
- gstddev = n > 1 ? sqrt(gstddev / (n - 1)) : 0;\r
-\r
- TimeVector::const_iterator start = times.begin();\r
- TimeVector::const_iterator end = times.end();\r
-\r
- //filter outliers assuming log-normal distribution\r
- //http://stackoverflow.com/questions/1867426/modeling-distribution-of-performance-measurements\r
- int offset = 0;\r
- if (gstddev > DBL_EPSILON)\r
- {\r
- double minout = exp(gmean - 3 * gstddev) * runsPerIteration;\r
- double maxout = exp(gmean + 3 * gstddev) * runsPerIteration;\r
- while(*start < minout) ++start, ++metrics.outliers, ++offset;\r
- do --end, ++metrics.outliers; while(*end > maxout);\r
- ++end, --metrics.outliers;\r
- }\r
-\r
- metrics.min = static_cast<double>(*start)/runsPerIteration;\r
- //calc final metrics\r
- n = 0;\r
- gmean = 0;\r
- gstddev = 0;\r
- double mean = 0;\r
- double stddev = 0;\r
- int m = 0;\r
- for(; start != end; ++start)\r
- {\r
- double x = static_cast<double>(*start)/runsPerIteration;\r
- if (x > DBL_EPSILON)\r
- {\r
- double lx = log(x);\r
- ++m;\r
- double gdelta = lx - gmean;\r
- gmean += gdelta / m;\r
- gstddev += gdelta * (lx - gmean);\r
- }\r
- ++n;\r
- double delta = x - mean;\r
- mean += delta / n;\r
- stddev += delta * (x - mean);\r
- }\r
-\r
- metrics.mean = mean;\r
- metrics.gmean = exp(gmean);\r
- metrics.gstddev = m > 1 ? sqrt(gstddev / (m - 1)) : 0;\r
- metrics.stddev = n > 1 ? sqrt(stddev / (n - 1)) : 0;\r
- metrics.median = n % 2\r
- ? (double)times[offset + n / 2]\r
- : 0.5 * (times[offset + n / 2] + times[offset + n / 2 - 1]);\r
-\r
- metrics.median /= runsPerIteration;\r
-\r
- return metrics;\r
-}\r
-\r
-void TestBase::validateMetrics()\r
-{\r
- performance_metrics& m = calcMetrics();\r
-\r
- if (HasFailure()) return;\r
-\r
- ASSERT_GE(m.samples, 1u)\r
- << " No time measurements was performed.\nstartTimer() and stopTimer() commands are required for performance tests.";\r
-\r
- EXPECT_GE(m.samples, param_min_samples)\r
- << " Only a few samples are collected.\nPlease increase number of iterations or/and time limit to get reliable performance measurements.";\r
-\r
- if (m.gstddev > DBL_EPSILON)\r
- {\r
- EXPECT_GT(/*m.gmean * */1., /*m.gmean * */ 2 * sinh(m.gstddev * param_max_deviation))\r
- << " Test results are not reliable ((mean-sigma,mean+sigma) deviation interval is bigger than measured time interval).";\r
- }\r
-\r
- EXPECT_LE(m.outliers, std::max((unsigned int)cvCeil(m.samples * param_max_outliers / 100.), 1u))\r
- << " Test results are not reliable (too many outliers).";\r
-}\r
-\r
-void TestBase::reportMetrics(bool toJUnitXML)\r
-{\r
- performance_metrics& m = calcMetrics();\r
-\r
- if (toJUnitXML)\r
- {\r
- RecordProperty("bytesIn", (int)m.bytesIn);\r
- RecordProperty("bytesOut", (int)m.bytesOut);\r
- RecordProperty("term", m.terminationReason);\r
- RecordProperty("samples", (int)m.samples);\r
- RecordProperty("outliers", (int)m.outliers);\r
- RecordProperty("frequency", cv::format("%.0f", m.frequency).c_str());\r
- RecordProperty("min", cv::format("%.0f", m.min).c_str());\r
- RecordProperty("median", cv::format("%.0f", m.median).c_str());\r
- RecordProperty("gmean", cv::format("%.0f", m.gmean).c_str());\r
- RecordProperty("gstddev", cv::format("%.6f", m.gstddev).c_str());\r
- RecordProperty("mean", cv::format("%.0f", m.mean).c_str());\r
- RecordProperty("stddev", cv::format("%.0f", m.stddev).c_str());\r
- }\r
- else\r
- {\r
- const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();\r
- const char* type_param = test_info->type_param();\r
- const char* value_param = test_info->value_param();\r
-\r
-#if defined(ANDROID) && defined(USE_ANDROID_LOGGING)\r
- LOGD("[ FAILED ] %s.%s", test_info->test_case_name(), test_info->name());\r
-#endif\r
-\r
- if (type_param) LOGD("type = %11s", type_param);\r
- if (value_param) LOGD("params = %11s", value_param);\r
-\r
- switch (m.terminationReason)\r
- {\r
- case performance_metrics::TERM_ITERATIONS:\r
- LOGD("termination reason: reached maximum number of iterations");\r
- break;\r
- case performance_metrics::TERM_TIME:\r
- LOGD("termination reason: reached time limit");\r
- break;\r
- case performance_metrics::TERM_INTERRUPT:\r
- LOGD("termination reason: aborted by the performance testing framework");\r
- break;\r
- case performance_metrics::TERM_EXCEPTION:\r
- LOGD("termination reason: unhandled exception");\r
- break;\r
- case performance_metrics::TERM_UNKNOWN:\r
- default:\r
- LOGD("termination reason: unknown");\r
- break;\r
- };\r
-\r
- LOGD("bytesIn =%11lu", (unsigned long)m.bytesIn);\r
- LOGD("bytesOut =%11lu", (unsigned long)m.bytesOut);\r
- if (nIters == (unsigned int)-1 || m.terminationReason == performance_metrics::TERM_ITERATIONS)\r
- LOGD("samples =%11u", m.samples);\r
- else\r
- LOGD("samples =%11u of %u", m.samples, nIters);\r
- LOGD("outliers =%11u", m.outliers);\r
- LOGD("frequency =%11.0f", m.frequency);\r
- if (m.samples > 0)\r
- {\r
- LOGD("min =%11.0f = %.2fms", m.min, m.min * 1e3 / m.frequency);\r
- LOGD("median =%11.0f = %.2fms", m.median, m.median * 1e3 / m.frequency);\r
- LOGD("gmean =%11.0f = %.2fms", m.gmean, m.gmean * 1e3 / m.frequency);\r
- LOGD("gstddev =%11.8f = %.2fms for 97%% dispersion interval", m.gstddev, m.gmean * 2 * sinh(m.gstddev * 3) * 1e3 / m.frequency);\r
- LOGD("mean =%11.0f = %.2fms", m.mean, m.mean * 1e3 / m.frequency);\r
- LOGD("stddev =%11.0f = %.2fms", m.stddev, m.stddev * 1e3 / m.frequency);\r
- }\r
- }\r
-}\r
-\r
-void TestBase::SetUp()\r
-{\r
-#ifdef HAVE_TBB\r
- if (param_tbb_nthreads > 0) {\r
- p_tbb_initializer.release();\r
- p_tbb_initializer=new tbb::task_scheduler_init(param_tbb_nthreads);\r
- }\r
-#endif\r
-#ifdef ANDROID\r
- if (param_affinity_mask)\r
- setCurrentThreadAffinityMask(param_affinity_mask);\r
-#endif\r
- lastTime = 0;\r
- totalTime = 0;\r
- runsPerIteration = 1;\r
- nIters = iterationsLimitDefault;\r
- currentIter = (unsigned int)-1;\r
- timeLimit = timeLimitDefault;\r
- times.clear();\r
- cv::theRNG().state = param_seed;//this rng should generate same numbers for each run\r
-}\r
-\r
-void TestBase::TearDown()\r
-{\r
- validateMetrics();\r
- if (HasFailure())\r
- reportMetrics(false);\r
- else\r
- {\r
- const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();\r
- const char* type_param = test_info->type_param();\r
- const char* value_param = test_info->value_param();\r
- if (value_param) printf("[ VALUE ] \t%s\n", value_param), fflush(stdout);\r
- if (type_param) printf("[ TYPE ] \t%s\n", type_param), fflush(stdout);\r
- reportMetrics(true);\r
- }\r
-#ifdef HAVE_TBB\r
- p_tbb_initializer.release();\r
-#endif\r
-}\r
-\r
-std::string TestBase::getDataPath(const std::string& relativePath)\r
-{\r
- if (relativePath.empty())\r
- {\r
- ADD_FAILURE() << " Bad path to test resource";\r
- throw PerfEarlyExitException();\r
- }\r
-\r
- const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");\r
- const char *path_separator = "/";\r
-\r
- std::string path;\r
- if (data_path_dir)\r
- {\r
- int len = (int)strlen(data_path_dir) - 1;\r
- if (len < 0) len = 0;\r
- path = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))\r
- + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator);\r
- }\r
- else\r
- {\r
- path = ".";\r
- path += path_separator;\r
- }\r
-\r
- if (relativePath[0] == '/' || relativePath[0] == '\\')\r
- path += relativePath.substr(1);\r
- else\r
- path += relativePath;\r
-\r
- FILE* fp = fopen(path.c_str(), "r");\r
- if (fp)\r
- fclose(fp);\r
- else\r
- {\r
- ADD_FAILURE() << " Requested file \"" << path << "\" does not exist.";\r
- throw PerfEarlyExitException();\r
- }\r
- return path;\r
-}\r
-\r
-void TestBase::RunPerfTestBody()\r
-{\r
- try\r
- {\r
- this->PerfTestBody();\r
- }\r
- catch(PerfEarlyExitException)\r
- {\r
- metrics.terminationReason = performance_metrics::TERM_INTERRUPT;\r
- return;//no additional failure logging\r
- }\r
- catch(cv::Exception e)\r
- {\r
- metrics.terminationReason = performance_metrics::TERM_EXCEPTION;\r
- FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws:\n " << e.what();\r
- }\r
- catch(...)\r
- {\r
- metrics.terminationReason = performance_metrics::TERM_EXCEPTION;\r
- FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws.";\r
- }\r
-}\r
-\r
-/*****************************************************************************************\\r
-* ::perf::TestBase::_declareHelper\r
-\*****************************************************************************************/\r
-TestBase::_declareHelper& TestBase::_declareHelper::iterations(unsigned int n)\r
-{\r
- test->times.clear();\r
- test->times.reserve(n);\r
- test->nIters = std::min(n, TestBase::iterationsLimitDefault);\r
- test->currentIter = (unsigned int)-1;\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::time(double timeLimitSecs)\r
-{\r
- test->times.clear();\r
- test->currentIter = (unsigned int)-1;\r
- test->timeLimit = (int64)(timeLimitSecs * cv::getTickFrequency());\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::tbb_threads(int n)\r
-{\r
-#ifdef HAVE_TBB\r
- test->p_tbb_initializer.release();\r
- if (n > 0)\r
- test->p_tbb_initializer=new tbb::task_scheduler_init(n);\r
-#endif\r
- (void)n;\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::runs(unsigned int runsNumber)\r
-{\r
- test->runsPerIteration = runsNumber;\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, int wtype)\r
-{\r
- if (!test->times.empty()) return *this;\r
- TestBase::declareArray(test->inputData, a1, wtype);\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)\r
-{\r
- if (!test->times.empty()) return *this;\r
- TestBase::declareArray(test->inputData, a1, wtype);\r
- TestBase::declareArray(test->inputData, a2, wtype);\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)\r
-{\r
- if (!test->times.empty()) return *this;\r
- TestBase::declareArray(test->inputData, a1, wtype);\r
- TestBase::declareArray(test->inputData, a2, wtype);\r
- TestBase::declareArray(test->inputData, a3, wtype);\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)\r
-{\r
- if (!test->times.empty()) return *this;\r
- TestBase::declareArray(test->inputData, a1, wtype);\r
- TestBase::declareArray(test->inputData, a2, wtype);\r
- TestBase::declareArray(test->inputData, a3, wtype);\r
- TestBase::declareArray(test->inputData, a4, wtype);\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, int wtype)\r
-{\r
- if (!test->times.empty()) return *this;\r
- TestBase::declareArray(test->outputData, a1, wtype);\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)\r
-{\r
- if (!test->times.empty()) return *this;\r
- TestBase::declareArray(test->outputData, a1, wtype);\r
- TestBase::declareArray(test->outputData, a2, wtype);\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)\r
-{\r
- if (!test->times.empty()) return *this;\r
- TestBase::declareArray(test->outputData, a1, wtype);\r
- TestBase::declareArray(test->outputData, a2, wtype);\r
- TestBase::declareArray(test->outputData, a3, wtype);\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)\r
-{\r
- if (!test->times.empty()) return *this;\r
- TestBase::declareArray(test->outputData, a1, wtype);\r
- TestBase::declareArray(test->outputData, a2, wtype);\r
- TestBase::declareArray(test->outputData, a3, wtype);\r
- TestBase::declareArray(test->outputData, a4, wtype);\r
- return *this;\r
-}\r
-\r
-TestBase::_declareHelper::_declareHelper(TestBase* t) : test(t)\r
-{\r
-}\r
-\r
-/*****************************************************************************************\\r
-* ::perf::PrintTo\r
-\*****************************************************************************************/\r
-namespace perf\r
-{\r
-\r
-void PrintTo(const MatType& t, ::std::ostream* os)\r
-{\r
- switch( CV_MAT_DEPTH((int)t) )\r
- {\r
- case CV_8U: *os << "8U"; break;\r
- case CV_8S: *os << "8S"; break;\r
- case CV_16U: *os << "16U"; break;\r
- case CV_16S: *os << "16S"; break;\r
- case CV_32S: *os << "32S"; break;\r
- case CV_32F: *os << "32F"; break;\r
- case CV_64F: *os << "64F"; break;\r
- case CV_USRTYPE1: *os << "USRTYPE1"; break;\r
- default: *os << "INVALID_TYPE"; break;\r
- }\r
- *os << 'C' << CV_MAT_CN((int)t);\r
-}\r
-\r
-} //namespace perf\r
-\r
-/*****************************************************************************************\\r
-* ::cv::PrintTo\r
-\*****************************************************************************************/\r
-namespace cv {\r
-\r
-void PrintTo(const Size& sz, ::std::ostream* os)\r
-{\r
- *os << /*"Size:" << */sz.width << "x" << sz.height;\r
-}\r
-\r
-} // namespace cv\r
-\r
-\r
-/*****************************************************************************************\\r
-* ::cv::PrintTo\r
-\*****************************************************************************************/\r
+#include "precomp.hpp"
+
+#ifdef ANDROID
+# include <sys/time.h>
+#endif
+
+using namespace perf;
+
+int64 TestBase::timeLimitDefault = 0;
+unsigned int TestBase::iterationsLimitDefault = (unsigned int)(-1);
+int64 TestBase::_timeadjustment = 0;
+
+const char *command_line_keys =
+{
+ "{ |perf_max_outliers |8 |percent of allowed outliers}"
+ "{ |perf_min_samples |10 |minimal required numer of samples}"
+ "{ |perf_force_samples |100 |force set maximum number of samples for all tests}"
+ "{ |perf_seed |809564 |seed for random numbers generator}"
+ "{ |perf_tbb_nthreads |-1 |if TBB is enabled, the number of TBB threads}"
+ "{ |perf_write_sanity |false |allow to create new records for sanity checks}"
+ #ifdef ANDROID
+ "{ |perf_time_limit |6.0 |default time limit for a single test (in seconds)}"
+ "{ |perf_affinity_mask |0 |set affinity mask for the main thread}"
+ "{ |perf_log_power_checkpoints |false |additional xml logging for power measurement}"
+ #else
+ "{ |perf_time_limit |3.0 |default time limit for a single test (in seconds)}"
+ #endif
+ "{ |perf_max_deviation |1.0 |}"
+ "{h |help |false |}"
+};
+
+static double param_max_outliers;
+static double param_max_deviation;
+static unsigned int param_min_samples;
+static unsigned int param_force_samples;
+static uint64 param_seed;
+static double param_time_limit;
+static int param_tbb_nthreads;
+static bool param_write_sanity;
+#ifdef ANDROID
+static int param_affinity_mask;
+static bool log_power_checkpoints;
+
+#include <sys/syscall.h>
+#include <pthread.h>
+static void setCurrentThreadAffinityMask(int mask)
+{
+ pid_t pid=gettid();
+ int syscallres=syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);
+ if (syscallres)
+ {
+ int err=errno;
+ err=err;//to avoid warnings about unused variables
+ LOGE("Error in the syscall setaffinity: mask=%d=0x%x err=%d=0x%x", mask, mask, err, err);
+ }
+}
+
+#endif
+
+static void randu(cv::Mat& m)
+{
+ const int bigValue = 0x00000FFF;
+ if (m.depth() < CV_32F)
+ {
+ int minmax[] = {0, 256};
+ cv::Mat mr = cv::Mat(m.rows, (int)(m.cols * m.elemSize()), CV_8U, m.ptr(), m.step[0]);
+ cv::randu(mr, cv::Mat(1, 1, CV_32S, minmax), cv::Mat(1, 1, CV_32S, minmax + 1));
+ }
+ else if (m.depth() == CV_32F)
+ {
+ //float minmax[] = {-FLT_MAX, FLT_MAX};
+ float minmax[] = {-bigValue, bigValue};
+ cv::Mat mr = m.reshape(1);
+ cv::randu(mr, cv::Mat(1, 1, CV_32F, minmax), cv::Mat(1, 1, CV_32F, minmax + 1));
+ }
+ else
+ {
+ //double minmax[] = {-DBL_MAX, DBL_MAX};
+ double minmax[] = {-bigValue, bigValue};
+ cv::Mat mr = m.reshape(1);
+ cv::randu(mr, cv::Mat(1, 1, CV_64F, minmax), cv::Mat(1, 1, CV_64F, minmax + 1));
+ }
+}
+
+/*****************************************************************************************\
+* inner exception class for early termination
+\*****************************************************************************************/
+
+class PerfEarlyExitException: public cv::Exception {};
+
+/*****************************************************************************************\
+* ::perf::Regression
+\*****************************************************************************************/
+
+Regression& Regression::instance()
+{
+ static Regression single;
+ return single;
+}
+
+Regression& Regression::add(const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
+{
+ return instance()(name, array, eps, err);
+}
+
+void Regression::Init(const std::string& testSuitName, const std::string& ext)
+{
+ instance().init(testSuitName, ext);
+}
+
+void Regression::init(const std::string& testSuitName, const std::string& ext)
+{
+ if (!storageInPath.empty())
+ {
+ LOGE("Subsequent initialisation of Regression utility is not allowed.");
+ return;
+ }
+
+ const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
+ const char *path_separator = "/";
+
+ if (data_path_dir)
+ {
+ int len = (int)strlen(data_path_dir)-1;
+ if (len < 0) len = 0;
+ std::string path_base = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
+ + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator)
+ + "perf"
+ + path_separator;
+
+ storageInPath = path_base + testSuitName + ext;
+ storageOutPath = path_base + testSuitName;
+ }
+ else
+ {
+ storageInPath = testSuitName + ext;
+ storageOutPath = testSuitName;
+ }
+
+ try
+ {
+ if (storageIn.open(storageInPath, cv::FileStorage::READ))
+ {
+ rootIn = storageIn.root();
+ if (storageInPath.length() > 3 && storageInPath.substr(storageInPath.length()-3) == ".gz")
+ storageOutPath += "_new";
+ storageOutPath += ext;
+ }
+ }
+ catch(cv::Exception&)
+ {
+ LOGE("Failed to open sanity data for reading: %s", storageInPath.c_str());
+ }
+
+ if(!storageIn.isOpened())
+ storageOutPath = storageInPath;
+}
+
+Regression::Regression() : regRNG(cv::getTickCount())//this rng should be really random
+{
+}
+
+Regression::~Regression()
+{
+ if (storageIn.isOpened())
+ storageIn.release();
+ if (storageOut.isOpened())
+ {
+ if (!currentTestNodeName.empty())
+ storageOut << "}";
+ storageOut.release();
+ }
+}
+
+cv::FileStorage& Regression::write()
+{
+ if (!storageOut.isOpened() && !storageOutPath.empty())
+ {
+ int mode = (storageIn.isOpened() && storageInPath == storageOutPath)
+ ? cv::FileStorage::APPEND : cv::FileStorage::WRITE;
+ storageOut.open(storageOutPath, mode);
+ if (!storageOut.isOpened())
+ {
+ LOGE("Could not open \"%s\" file for writing", storageOutPath.c_str());
+ storageOutPath.clear();
+ }
+ else if (mode == cv::FileStorage::WRITE && !rootIn.empty())
+ {
+ //TODO: write content of rootIn node into the storageOut
+ }
+ }
+ return storageOut;
+}
+
+std::string Regression::getCurrentTestNodeName()
+{
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+
+ if (test_info == 0)
+ return "undefined";
+
+ std::string nodename = std::string(test_info->test_case_name()) + "--" + test_info->name();
+ size_t idx = nodename.find_first_of('/');
+ if (idx != std::string::npos)
+ nodename.erase(idx);
+
+ const char* type_param = test_info->type_param();
+ if (type_param != 0)
+ (nodename += "--") += type_param;
+
+ const char* value_param = test_info->value_param();
+ if (value_param != 0)
+ (nodename += "--") += value_param;
+
+ for(size_t i = 0; i < nodename.length(); ++i)
+ if (!isalnum(nodename[i]) && '_' != nodename[i])
+ nodename[i] = '-';
+
+ return nodename;
+}
+
+bool Regression::isVector(cv::InputArray a)
+{
+ return a.kind() == cv::_InputArray::STD_VECTOR_MAT || a.kind() == cv::_InputArray::STD_VECTOR_VECTOR;
+}
+
+double Regression::getElem(cv::Mat& m, int y, int x, int cn)
+{
+ switch (m.depth())
+ {
+ case CV_8U: return *(m.ptr<unsigned char>(y, x) + cn);
+ case CV_8S: return *(m.ptr<signed char>(y, x) + cn);
+ case CV_16U: return *(m.ptr<unsigned short>(y, x) + cn);
+ case CV_16S: return *(m.ptr<signed short>(y, x) + cn);
+ case CV_32S: return *(m.ptr<signed int>(y, x) + cn);
+ case CV_32F: return *(m.ptr<float>(y, x) + cn);
+ case CV_64F: return *(m.ptr<double>(y, x) + cn);
+ default: return 0;
+ }
+}
+
+void Regression::write(cv::Mat m)
+{
+ double min, max;
+ cv::minMaxLoc(m, &min, &max);
+ write() << "min" << min << "max" << max;
+
+ write() << "last" << "{" << "x" << m.cols-1 << "y" << m.rows-1
+ << "val" << getElem(m, m.rows-1, m.cols-1, m.channels()-1) << "}";
+
+ int x, y, cn;
+ x = regRNG.uniform(0, m.cols);
+ y = regRNG.uniform(0, m.rows);
+ cn = regRNG.uniform(0, m.channels());
+ write() << "rng1" << "{" << "x" << x << "y" << y;
+ if(cn > 0) write() << "cn" << cn;
+ write() << "val" << getElem(m, y, x, cn) << "}";
+
+ x = regRNG.uniform(0, m.cols);
+ y = regRNG.uniform(0, m.rows);
+ cn = regRNG.uniform(0, m.channels());
+ write() << "rng2" << "{" << "x" << x << "y" << y;
+ if (cn > 0) write() << "cn" << cn;
+ write() << "val" << getElem(m, y, x, cn) << "}";
+}
+
+static double evalEps(double expected, double actual, double _eps, ERROR_TYPE err)
+{
+ if (err == ERROR_ABSOLUTE)
+ return _eps;
+ else if (err == ERROR_RELATIVE)
+ return std::max(std::abs(expected), std::abs(actual)) * err;
+ return 0;
+}
+
+void Regression::verify(cv::FileNode node, cv::Mat actual, double _eps, std::string argname, ERROR_TYPE err)
+{
+ double actual_min, actual_max;
+ cv::minMaxLoc(actual, &actual_min, &actual_max);
+
+ double eps = evalEps((double)node["min"], actual_min, _eps, err);
+ ASSERT_NEAR((double)node["min"], actual_min, eps)
+ << " " << argname << " has unexpected minimal value";
+
+ eps = evalEps((double)node["max"], actual_max, _eps, err);
+ ASSERT_NEAR((double)node["max"], actual_max, eps)
+ << " " << argname << " has unexpected maximal value";
+
+ cv::FileNode last = node["last"];
+ double actualLast = getElem(actual, actual.rows - 1, actual.cols - 1, actual.channels() - 1);
+ ASSERT_EQ((int)last["x"], actual.cols - 1)
+ << " " << argname << " has unexpected number of columns";
+ ASSERT_EQ((int)last["y"], actual.rows - 1)
+ << " " << argname << " has unexpected number of rows";
+
+ eps = evalEps((double)last["val"], actualLast, _eps, err);
+ ASSERT_NEAR((double)last["val"], actualLast, eps)
+ << " " << argname << " has unexpected value of last element";
+
+ cv::FileNode rng1 = node["rng1"];
+ int x1 = rng1["x"];
+ int y1 = rng1["y"];
+ int cn1 = rng1["cn"];
+
+ eps = evalEps((double)rng1["val"], getElem(actual, y1, x1, cn1), _eps, err);
+ ASSERT_NEAR((double)rng1["val"], getElem(actual, y1, x1, cn1), eps)
+ << " " << argname << " has unexpected value of ["<< x1 << ":" << y1 << ":" << cn1 <<"] element";
+
+ cv::FileNode rng2 = node["rng2"];
+ int x2 = rng2["x"];
+ int y2 = rng2["y"];
+ int cn2 = rng2["cn"];
+
+ eps = evalEps((double)rng2["val"], getElem(actual, y2, x2, cn2), _eps, err);
+ ASSERT_NEAR((double)rng2["val"], getElem(actual, y2, x2, cn2), eps)
+ << " " << argname << " has unexpected value of ["<< x2 << ":" << y2 << ":" << cn2 <<"] element";
+}
+
+void Regression::write(cv::InputArray array)
+{
+ write() << "kind" << array.kind();
+ write() << "type" << array.type();
+ if (isVector(array))
+ {
+ int total = (int)array.total();
+ int idx = regRNG.uniform(0, total);
+ write() << "len" << total;
+ write() << "idx" << idx;
+
+ cv::Mat m = array.getMat(idx);
+
+ if (m.total() * m.channels() < 26) //5x5 or smaller
+ write() << "val" << m;
+ else
+ write(m);
+ }
+ else
+ {
+ if (array.total() * array.channels() < 26) //5x5 or smaller
+ write() << "val" << array.getMat();
+ else
+ write(array.getMat());
+ }
+}
+
+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)
+{
+ cv::Mat diff64f;
+ diff.reshape(1).convertTo(diff64f, CV_64F);
+
+ cv::Mat expected_abs = cv::abs(expected.reshape(1));
+ cv::Mat actual_abs = cv::abs(actual.reshape(1));
+ cv::Mat maximum, mask;
+ cv::max(expected_abs, actual_abs, maximum);
+ cv::multiply(maximum, cv::Vec<double, 1>(eps), maximum, CV_64F);
+ cv::compare(diff64f, maximum, mask, cv::CMP_GT);
+
+ int v = cv::countNonZero(mask);
+
+ if (v > 0 && max_violation != 0 && max_allowed != 0)
+ {
+ int loc[10];
+ cv::minMaxIdx(maximum, 0, max_allowed, 0, loc, mask);
+ *max_violation = diff64f.at<double>(loc[1], loc[0]);
+ }
+
+ return v;
+}
+
+void Regression::verify(cv::FileNode node, cv::InputArray array, double eps, ERROR_TYPE err)
+{
+ ASSERT_EQ((int)node["kind"], array.kind()) << " Argument \"" << node.name() << "\" has unexpected kind";
+ ASSERT_EQ((int)node["type"], array.type()) << " Argument \"" << node.name() << "\" has unexpected type";
+
+ cv::FileNode valnode = node["val"];
+ if (isVector(array))
+ {
+ ASSERT_EQ((int)node["len"], (int)array.total()) << " Vector \"" << node.name() << "\" has unexpected length";
+ int idx = node["idx"];
+
+ cv::Mat actual = array.getMat(idx);
+
+ if (valnode.isNone())
+ {
+ ASSERT_LE((size_t)26, actual.total() * (size_t)actual.channels())
+ << " \"" << node.name() << "[" << idx << "]\" has unexpected number of elements";
+ verify(node, actual, eps, cv::format("%s[%d]", node.name().c_str(), idx), err);
+ }
+ else
+ {
+ cv::Mat expected;
+ valnode >> expected;
+
+ ASSERT_EQ(expected.size(), actual.size())
+ << " " << node.name() << "[" << idx<< "] has unexpected size";
+
+ cv::Mat diff;
+ cv::absdiff(expected, actual, diff);
+
+ if (err == ERROR_ABSOLUTE)
+ {
+ if (!cv::checkRange(diff, true, 0, 0, eps))
+ {
+ double max;
+ cv::minMaxLoc(diff.reshape(1), 0, &max);
+ FAIL() << " Absolute difference (=" << max << ") between argument \""
+ << node.name() << "[" << idx << "]\" and expected value is bugger than " << eps;
+ }
+ }
+ else if (err == ERROR_RELATIVE)
+ {
+ double maxv, maxa;
+ int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
+ if (violations > 0)
+ {
+ FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \""
+ << node.name() << "[" << idx << "]\" and expected value is bugger than " << eps << " in " << violations << " points";
+ }
+ }
+ }
+ }
+ else
+ {
+ if (valnode.isNone())
+ {
+ ASSERT_LE((size_t)26, array.total() * (size_t)array.channels())
+ << " Argument \"" << node.name() << "\" has unexpected number of elements";
+ verify(node, array.getMat(), eps, "Argument " + node.name(), err);
+ }
+ else
+ {
+ cv::Mat expected;
+ valnode >> expected;
+ cv::Mat actual = array.getMat();
+
+ ASSERT_EQ(expected.size(), actual.size())
+ << " Argument \"" << node.name() << "\" has unexpected size";
+
+ cv::Mat diff;
+ cv::absdiff(expected, actual, diff);
+
+ if (err == ERROR_ABSOLUTE)
+ {
+ if (!cv::checkRange(diff, true, 0, 0, eps))
+ {
+ double max;
+ cv::minMaxLoc(diff.reshape(1), 0, &max);
+ FAIL() << " Difference (=" << max << ") between argument \"" << node.name()
+ << "\" and expected value is bugger than " << eps;
+ }
+ }
+ else if (err == ERROR_RELATIVE)
+ {
+ double maxv, maxa;
+ int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
+ if (violations > 0)
+ {
+ FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \"" << node.name()
+ << "\" and expected value is bugger than " << eps << " in " << violations << " points";
+ }
+ }
+ }
+ }
+}
+
+Regression& Regression::operator() (const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
+{
+ std::string nodename = getCurrentTestNodeName();
+
+ cv::FileNode n = rootIn[nodename];
+ if(n.isNone())
+ {
+ if(param_write_sanity)
+ {
+ if (nodename != currentTestNodeName)
+ {
+ if (!currentTestNodeName.empty())
+ write() << "}";
+ currentTestNodeName = nodename;
+
+ write() << nodename << "{";
+ }
+ write() << name << "{";
+ write(array);
+ write() << "}";
+ }
+ }
+ else
+ {
+ cv::FileNode this_arg = n[name];
+ if (!this_arg.isMap())
+ ADD_FAILURE() << " No regression data for " << name << " argument";
+ else
+ verify(this_arg, array, eps, err);
+ }
+ return *this;
+}
+
+
+/*****************************************************************************************\
+* ::perf::performance_metrics
+\*****************************************************************************************/
+performance_metrics::performance_metrics()
+{
+ bytesIn = 0;
+ bytesOut = 0;
+ samples = 0;
+ outliers = 0;
+ gmean = 0;
+ gstddev = 0;
+ mean = 0;
+ stddev = 0;
+ median = 0;
+ min = 0;
+ frequency = 0;
+ terminationReason = TERM_UNKNOWN;
+}
+
+
+/*****************************************************************************************\
+* ::perf::TestBase
+\*****************************************************************************************/
+
+
+void TestBase::Init(int argc, const char* const argv[])
+{
+ cv::CommandLineParser args(argc, argv, command_line_keys);
+ param_max_outliers = std::min(100., std::max(0., args.get<double>("perf_max_outliers")));
+ param_min_samples = std::max(1u, args.get<unsigned int>("perf_min_samples"));
+ param_max_deviation = std::max(0., args.get<double>("perf_max_deviation"));
+ param_seed = args.get<uint64>("perf_seed");
+ param_time_limit = std::max(0., args.get<double>("perf_time_limit"));
+ param_force_samples = args.get<unsigned int>("perf_force_samples");
+ param_write_sanity = args.get<bool>("perf_write_sanity");
+ param_tbb_nthreads = args.get<int>("perf_tbb_nthreads");
+#ifdef ANDROID
+ param_affinity_mask = args.get<int>("perf_affinity_mask");
+ log_power_checkpoints = args.get<bool>("perf_log_power_checkpoints");
+#endif
+
+ if (args.get<bool>("help"))
+ {
+ args.printParams();
+ printf("\n\n");
+ return;
+ }
+
+ timeLimitDefault = param_time_limit == 0.0 ? 1 : (int64)(param_time_limit * cv::getTickFrequency());
+ iterationsLimitDefault = param_force_samples == 0 ? (unsigned)(-1) : param_force_samples;
+ _timeadjustment = _calibrate();
+}
+
+int64 TestBase::_calibrate()
+{
+ class _helper : public ::perf::TestBase
+ {
+ public:
+ performance_metrics& getMetrics() { return calcMetrics(); }
+ virtual void TestBody() {}
+ virtual void PerfTestBody()
+ {
+ //the whole system warmup
+ SetUp();
+ cv::Mat a(2048, 2048, CV_32S, cv::Scalar(1));
+ cv::Mat b(2048, 2048, CV_32S, cv::Scalar(2));
+ declare.time(30);
+ double s = 0;
+ for(declare.iterations(20); startTimer(), next(); stopTimer())
+ s+=a.dot(b);
+ declare.time(s);
+
+ //self calibration
+ SetUp();
+ for(declare.iterations(1000); startTimer(), next(); stopTimer()){}
+ }
+ };
+
+ _timeadjustment = 0;
+ _helper h;
+ h.PerfTestBody();
+ double compensation = h.getMetrics().min;
+ LOGD("Time compensation is %.0f", compensation);
+ return (int64)compensation;
+}
+
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4355) // 'this' : used in base member initializer list
+#endif
+TestBase::TestBase(): declare(this)
+{
+}
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+
+void TestBase::declareArray(SizeVector& sizes, cv::InputOutputArray a, int wtype)
+{
+ if (!a.empty())
+ {
+ sizes.push_back(std::pair<int, cv::Size>(getSizeInBytes(a), getSize(a)));
+ warmup(a, wtype);
+ }
+ else if (a.kind() != cv::_InputArray::NONE)
+ ADD_FAILURE() << " Uninitialized input/output parameters are not allowed for performance tests";
+}
+
+void TestBase::warmup(cv::InputOutputArray a, int wtype)
+{
+ if (a.empty()) return;
+ if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
+ warmup_impl(a.getMat(), wtype);
+ else
+ {
+ size_t total = a.total();
+ for (size_t i = 0; i < total; ++i)
+ warmup_impl(a.getMat((int)i), wtype);
+ }
+}
+
+int TestBase::getSizeInBytes(cv::InputArray a)
+{
+ if (a.empty()) return 0;
+ int total = (int)a.total();
+ if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
+ return total * CV_ELEM_SIZE(a.type());
+
+ int size = 0;
+ for (int i = 0; i < total; ++i)
+ size += (int)a.total(i) * CV_ELEM_SIZE(a.type(i));
+
+ return size;
+}
+
+cv::Size TestBase::getSize(cv::InputArray a)
+{
+ if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
+ return a.size();
+ return cv::Size();
+}
+
+bool TestBase::next()
+{
+ bool has_next = ++currentIter < nIters && totalTime < timeLimit;
+#ifdef ANDROID
+ if (log_power_checkpoints)
+ {
+ timeval tim;
+ gettimeofday(&tim, NULL);
+ unsigned long long t1 = tim.tv_sec * 1000LLU + (unsigned long long)(tim.tv_usec / 1000.f);
+
+ if (currentIter == 1) RecordProperty("test_start", cv::format("%llu",t1).c_str());
+ if (!has_next) RecordProperty("test_complete", cv::format("%llu",t1).c_str());
+ }
+#endif
+ return has_next;
+}
+
+void TestBase::warmup_impl(cv::Mat m, int wtype)
+{
+ switch(wtype)
+ {
+ case WARMUP_READ:
+ cv::sum(m.reshape(1));
+ return;
+ case WARMUP_WRITE:
+ m.reshape(1).setTo(cv::Scalar::all(0));
+ return;
+ case WARMUP_RNG:
+ randu(m);
+ return;
+ default:
+ return;
+ }
+}
+
+unsigned int TestBase::getTotalInputSize() const
+{
+ unsigned int res = 0;
+ for (SizeVector::const_iterator i = inputData.begin(); i != inputData.end(); ++i)
+ res += i->first;
+ return res;
+}
+
+unsigned int TestBase::getTotalOutputSize() const
+{
+ unsigned int res = 0;
+ for (SizeVector::const_iterator i = outputData.begin(); i != outputData.end(); ++i)
+ res += i->first;
+ return res;
+}
+
+void TestBase::startTimer()
+{
+ lastTime = cv::getTickCount();
+}
+
+void TestBase::stopTimer()
+{
+ int64 time = cv::getTickCount();
+ if (lastTime == 0)
+ ADD_FAILURE() << " stopTimer() is called before startTimer()";
+ lastTime = time - lastTime;
+ totalTime += lastTime;
+ lastTime -= _timeadjustment;
+ if (lastTime < 0) lastTime = 0;
+ times.push_back(lastTime);
+ lastTime = 0;
+}
+
+performance_metrics& TestBase::calcMetrics()
+{
+ if ((metrics.samples == (unsigned int)currentIter) || times.size() == 0)
+ return metrics;
+
+ metrics.bytesIn = getTotalInputSize();
+ metrics.bytesOut = getTotalOutputSize();
+ metrics.frequency = cv::getTickFrequency();
+ metrics.samples = (unsigned int)times.size();
+ metrics.outliers = 0;
+
+ if (metrics.terminationReason != performance_metrics::TERM_INTERRUPT && metrics.terminationReason != performance_metrics::TERM_EXCEPTION)
+ {
+ if (currentIter == nIters)
+ metrics.terminationReason = performance_metrics::TERM_ITERATIONS;
+ else if (totalTime >= timeLimit)
+ metrics.terminationReason = performance_metrics::TERM_TIME;
+ else
+ metrics.terminationReason = performance_metrics::TERM_UNKNOWN;
+ }
+
+ std::sort(times.begin(), times.end());
+
+ //estimate mean and stddev for log(time)
+ double gmean = 0;
+ double gstddev = 0;
+ int n = 0;
+ for(TimeVector::const_iterator i = times.begin(); i != times.end(); ++i)
+ {
+ double x = static_cast<double>(*i)/runsPerIteration;
+ if (x < DBL_EPSILON) continue;
+ double lx = log(x);
+
+ ++n;
+ double delta = lx - gmean;
+ gmean += delta / n;
+ gstddev += delta * (lx - gmean);
+ }
+
+ gstddev = n > 1 ? sqrt(gstddev / (n - 1)) : 0;
+
+ TimeVector::const_iterator start = times.begin();
+ TimeVector::const_iterator end = times.end();
+
+ //filter outliers assuming log-normal distribution
+ //http://stackoverflow.com/questions/1867426/modeling-distribution-of-performance-measurements
+ int offset = 0;
+ if (gstddev > DBL_EPSILON)
+ {
+ double minout = exp(gmean - 3 * gstddev) * runsPerIteration;
+ double maxout = exp(gmean + 3 * gstddev) * runsPerIteration;
+ while(*start < minout) ++start, ++metrics.outliers, ++offset;
+ do --end, ++metrics.outliers; while(*end > maxout);
+ ++end, --metrics.outliers;
+ }
+
+ metrics.min = static_cast<double>(*start)/runsPerIteration;
+ //calc final metrics
+ n = 0;
+ gmean = 0;
+ gstddev = 0;
+ double mean = 0;
+ double stddev = 0;
+ int m = 0;
+ for(; start != end; ++start)
+ {
+ double x = static_cast<double>(*start)/runsPerIteration;
+ if (x > DBL_EPSILON)
+ {
+ double lx = log(x);
+ ++m;
+ double gdelta = lx - gmean;
+ gmean += gdelta / m;
+ gstddev += gdelta * (lx - gmean);
+ }
+ ++n;
+ double delta = x - mean;
+ mean += delta / n;
+ stddev += delta * (x - mean);
+ }
+
+ metrics.mean = mean;
+ metrics.gmean = exp(gmean);
+ metrics.gstddev = m > 1 ? sqrt(gstddev / (m - 1)) : 0;
+ metrics.stddev = n > 1 ? sqrt(stddev / (n - 1)) : 0;
+ metrics.median = n % 2
+ ? (double)times[offset + n / 2]
+ : 0.5 * (times[offset + n / 2] + times[offset + n / 2 - 1]);
+
+ metrics.median /= runsPerIteration;
+
+ return metrics;
+}
+
+void TestBase::validateMetrics()
+{
+ performance_metrics& m = calcMetrics();
+
+ if (HasFailure()) return;
+
+ ASSERT_GE(m.samples, 1u)
+ << " No time measurements was performed.\nstartTimer() and stopTimer() commands are required for performance tests.";
+
+ EXPECT_GE(m.samples, param_min_samples)
+ << " Only a few samples are collected.\nPlease increase number of iterations or/and time limit to get reliable performance measurements.";
+
+ if (m.gstddev > DBL_EPSILON)
+ {
+ EXPECT_GT(/*m.gmean * */1., /*m.gmean * */ 2 * sinh(m.gstddev * param_max_deviation))
+ << " Test results are not reliable ((mean-sigma,mean+sigma) deviation interval is bigger than measured time interval).";
+ }
+
+ EXPECT_LE(m.outliers, std::max((unsigned int)cvCeil(m.samples * param_max_outliers / 100.), 1u))
+ << " Test results are not reliable (too many outliers).";
+}
+
+void TestBase::reportMetrics(bool toJUnitXML)
+{
+ performance_metrics& m = calcMetrics();
+
+ if (toJUnitXML)
+ {
+ RecordProperty("bytesIn", (int)m.bytesIn);
+ RecordProperty("bytesOut", (int)m.bytesOut);
+ RecordProperty("term", m.terminationReason);
+ RecordProperty("samples", (int)m.samples);
+ RecordProperty("outliers", (int)m.outliers);
+ RecordProperty("frequency", cv::format("%.0f", m.frequency).c_str());
+ RecordProperty("min", cv::format("%.0f", m.min).c_str());
+ RecordProperty("median", cv::format("%.0f", m.median).c_str());
+ RecordProperty("gmean", cv::format("%.0f", m.gmean).c_str());
+ RecordProperty("gstddev", cv::format("%.6f", m.gstddev).c_str());
+ RecordProperty("mean", cv::format("%.0f", m.mean).c_str());
+ RecordProperty("stddev", cv::format("%.0f", m.stddev).c_str());
+ }
+ else
+ {
+ const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
+ const char* type_param = test_info->type_param();
+ const char* value_param = test_info->value_param();
+
+#if defined(ANDROID) && defined(USE_ANDROID_LOGGING)
+ LOGD("[ FAILED ] %s.%s", test_info->test_case_name(), test_info->name());
+#endif
+
+ if (type_param) LOGD("type = %11s", type_param);
+ if (value_param) LOGD("params = %11s", value_param);
+
+ switch (m.terminationReason)
+ {
+ case performance_metrics::TERM_ITERATIONS:
+ LOGD("termination reason: reached maximum number of iterations");
+ break;
+ case performance_metrics::TERM_TIME:
+ LOGD("termination reason: reached time limit");
+ break;
+ case performance_metrics::TERM_INTERRUPT:
+ LOGD("termination reason: aborted by the performance testing framework");
+ break;
+ case performance_metrics::TERM_EXCEPTION:
+ LOGD("termination reason: unhandled exception");
+ break;
+ case performance_metrics::TERM_UNKNOWN:
+ default:
+ LOGD("termination reason: unknown");
+ break;
+ };
+
+ LOGD("bytesIn =%11lu", (unsigned long)m.bytesIn);
+ LOGD("bytesOut =%11lu", (unsigned long)m.bytesOut);
+ if (nIters == (unsigned int)-1 || m.terminationReason == performance_metrics::TERM_ITERATIONS)
+ LOGD("samples =%11u", m.samples);
+ else
+ LOGD("samples =%11u of %u", m.samples, nIters);
+ LOGD("outliers =%11u", m.outliers);
+ LOGD("frequency =%11.0f", m.frequency);
+ if (m.samples > 0)
+ {
+ LOGD("min =%11.0f = %.2fms", m.min, m.min * 1e3 / m.frequency);
+ LOGD("median =%11.0f = %.2fms", m.median, m.median * 1e3 / m.frequency);
+ LOGD("gmean =%11.0f = %.2fms", m.gmean, m.gmean * 1e3 / m.frequency);
+ LOGD("gstddev =%11.8f = %.2fms for 97%% dispersion interval", m.gstddev, m.gmean * 2 * sinh(m.gstddev * 3) * 1e3 / m.frequency);
+ LOGD("mean =%11.0f = %.2fms", m.mean, m.mean * 1e3 / m.frequency);
+ LOGD("stddev =%11.0f = %.2fms", m.stddev, m.stddev * 1e3 / m.frequency);
+ }
+ }
+}
+
+void TestBase::SetUp()
+{
+#ifdef HAVE_TBB
+ if (param_tbb_nthreads > 0) {
+ p_tbb_initializer.release();
+ p_tbb_initializer=new tbb::task_scheduler_init(param_tbb_nthreads);
+ }
+#endif
+#ifdef ANDROID
+ if (param_affinity_mask)
+ setCurrentThreadAffinityMask(param_affinity_mask);
+#endif
+ lastTime = 0;
+ totalTime = 0;
+ runsPerIteration = 1;
+ nIters = iterationsLimitDefault;
+ currentIter = (unsigned int)-1;
+ timeLimit = timeLimitDefault;
+ times.clear();
+ cv::theRNG().state = param_seed;//this rng should generate same numbers for each run
+}
+
+void TestBase::TearDown()
+{
+ validateMetrics();
+ if (HasFailure())
+ reportMetrics(false);
+ else
+ {
+ const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
+ const char* type_param = test_info->type_param();
+ const char* value_param = test_info->value_param();
+ if (value_param) printf("[ VALUE ] \t%s\n", value_param), fflush(stdout);
+ if (type_param) printf("[ TYPE ] \t%s\n", type_param), fflush(stdout);
+ reportMetrics(true);
+ }
+#ifdef HAVE_TBB
+ p_tbb_initializer.release();
+#endif
+}
+
+std::string TestBase::getDataPath(const std::string& relativePath)
+{
+ if (relativePath.empty())
+ {
+ ADD_FAILURE() << " Bad path to test resource";
+ throw PerfEarlyExitException();
+ }
+
+ const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
+ const char *path_separator = "/";
+
+ std::string path;
+ if (data_path_dir)
+ {
+ int len = (int)strlen(data_path_dir) - 1;
+ if (len < 0) len = 0;
+ path = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
+ + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator);
+ }
+ else
+ {
+ path = ".";
+ path += path_separator;
+ }
+
+ if (relativePath[0] == '/' || relativePath[0] == '\\')
+ path += relativePath.substr(1);
+ else
+ path += relativePath;
+
+ FILE* fp = fopen(path.c_str(), "r");
+ if (fp)
+ fclose(fp);
+ else
+ {
+ ADD_FAILURE() << " Requested file \"" << path << "\" does not exist.";
+ throw PerfEarlyExitException();
+ }
+ return path;
+}
+
+void TestBase::RunPerfTestBody()
+{
+ try
+ {
+ this->PerfTestBody();
+ }
+ catch(PerfEarlyExitException)
+ {
+ metrics.terminationReason = performance_metrics::TERM_INTERRUPT;
+ return;//no additional failure logging
+ }
+ catch(cv::Exception e)
+ {
+ metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
+ FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws:\n " << e.what();
+ }
+ catch(...)
+ {
+ metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
+ FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws.";
+ }
+}
+
+/*****************************************************************************************\
+* ::perf::TestBase::_declareHelper
+\*****************************************************************************************/
+TestBase::_declareHelper& TestBase::_declareHelper::iterations(unsigned int n)
+{
+ test->times.clear();
+ test->times.reserve(n);
+ test->nIters = std::min(n, TestBase::iterationsLimitDefault);
+ test->currentIter = (unsigned int)-1;
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::time(double timeLimitSecs)
+{
+ test->times.clear();
+ test->currentIter = (unsigned int)-1;
+ test->timeLimit = (int64)(timeLimitSecs * cv::getTickFrequency());
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::tbb_threads(int n)
+{
+#ifdef HAVE_TBB
+ test->p_tbb_initializer.release();
+ if (n > 0)
+ test->p_tbb_initializer=new tbb::task_scheduler_init(n);
+#endif
+ (void)n;
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::runs(unsigned int runsNumber)
+{
+ test->runsPerIteration = runsNumber;
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, int wtype)
+{
+ if (!test->times.empty()) return *this;
+ TestBase::declareArray(test->inputData, a1, wtype);
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)
+{
+ if (!test->times.empty()) return *this;
+ TestBase::declareArray(test->inputData, a1, wtype);
+ TestBase::declareArray(test->inputData, a2, wtype);
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)
+{
+ if (!test->times.empty()) return *this;
+ TestBase::declareArray(test->inputData, a1, wtype);
+ TestBase::declareArray(test->inputData, a2, wtype);
+ TestBase::declareArray(test->inputData, a3, wtype);
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)
+{
+ if (!test->times.empty()) return *this;
+ TestBase::declareArray(test->inputData, a1, wtype);
+ TestBase::declareArray(test->inputData, a2, wtype);
+ TestBase::declareArray(test->inputData, a3, wtype);
+ TestBase::declareArray(test->inputData, a4, wtype);
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, int wtype)
+{
+ if (!test->times.empty()) return *this;
+ TestBase::declareArray(test->outputData, a1, wtype);
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)
+{
+ if (!test->times.empty()) return *this;
+ TestBase::declareArray(test->outputData, a1, wtype);
+ TestBase::declareArray(test->outputData, a2, wtype);
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)
+{
+ if (!test->times.empty()) return *this;
+ TestBase::declareArray(test->outputData, a1, wtype);
+ TestBase::declareArray(test->outputData, a2, wtype);
+ TestBase::declareArray(test->outputData, a3, wtype);
+ return *this;
+}
+
+TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)
+{
+ if (!test->times.empty()) return *this;
+ TestBase::declareArray(test->outputData, a1, wtype);
+ TestBase::declareArray(test->outputData, a2, wtype);
+ TestBase::declareArray(test->outputData, a3, wtype);
+ TestBase::declareArray(test->outputData, a4, wtype);
+ return *this;
+}
+
+TestBase::_declareHelper::_declareHelper(TestBase* t) : test(t)
+{
+}
+
+/*****************************************************************************************\
+* ::perf::PrintTo
+\*****************************************************************************************/
+namespace perf
+{
+
+void PrintTo(const MatType& t, ::std::ostream* os)
+{
+ switch( CV_MAT_DEPTH((int)t) )
+ {
+ case CV_8U: *os << "8U"; break;
+ case CV_8S: *os << "8S"; break;
+ case CV_16U: *os << "16U"; break;
+ case CV_16S: *os << "16S"; break;
+ case CV_32S: *os << "32S"; break;
+ case CV_32F: *os << "32F"; break;
+ case CV_64F: *os << "64F"; break;
+ case CV_USRTYPE1: *os << "USRTYPE1"; break;
+ default: *os << "INVALID_TYPE"; break;
+ }
+ *os << 'C' << CV_MAT_CN((int)t);
+}
+
+} //namespace perf
+
+/*****************************************************************************************\
+* ::cv::PrintTo
+\*****************************************************************************************/
+namespace cv {
+
+void PrintTo(const Size& sz, ::std::ostream* os)
+{
+ *os << /*"Size:" << */sz.width << "x" << sz.height;
+}
+
+} // namespace cv
+
+
+/*****************************************************************************************\
+* ::cv::PrintTo
+\*****************************************************************************************/