openvino/samples/cpp/common/utils/include/samples/common.hpp

591 lines
21 KiB
C++

// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
/**
* @brief a header file with common samples functionality
* @file common.hpp
*/
#pragma once
#include <algorithm>
#include <cctype>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <limits>
#include <list>
#include <map>
#include <random>
#include <string>
#include <utility>
#include <vector>
// clang-format off
#include "openvino/openvino.hpp"
#include "slog.hpp"
// clang-format on
// @brief performance counters sort
static constexpr char pcSort[] = "sort";
static constexpr char pcNoSort[] = "no_sort";
static constexpr char pcSimpleSort[] = "simple_sort";
#ifndef UNUSED
# if defined(_MSC_VER) && !defined(__clang__)
# define UNUSED
# else
# define UNUSED __attribute__((unused))
# endif
#endif
/**
* @brief Unicode string wrappers
*/
#if defined(ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32)
# define tchar wchar_t
# define tstring std::wstring
# define tmain wmain
# define TSTRING2STRING(tstr) wstring2string(tstr)
#else
# define tchar char
# define tstring std::string
# define tmain main
# define TSTRING2STRING(tstr) tstr
#endif
#if defined(ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32)
/**
* @brief Convert wstring to string
* @param ref on wstring
* @return string
*/
inline std::string wstring2string(const std::wstring& wstr) {
std::string str;
for (auto&& wc : wstr)
str += static_cast<char>(wc);
return str;
}
#endif
/**
* @brief trim from start (in place)
* @param s - string to trim
*/
inline void ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int c) {
return !std::isspace(c);
}));
}
/**
* @brief trim from end (in place)
* @param s - string to trim
*/
inline void rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(),
s.rend(),
[](int c) {
return !std::isspace(c);
})
.base(),
s.end());
}
/**
* @brief trim from both ends (in place)
* @param s - string to trim
*/
inline std::string& trim(std::string& s) {
ltrim(s);
rtrim(s);
return s;
}
/**
* @brief Gets filename without extension
* @param filepath - full file name
* @return filename without extension
*/
inline std::string fileNameNoExt(const std::string& filepath) {
auto pos = filepath.rfind('.');
if (pos == std::string::npos)
return filepath;
return filepath.substr(0, pos);
}
/**
* @brief Get extension from filename
* @param filename - name of the file which extension should be extracted
* @return string with extracted file extension
*/
inline std::string fileExt(const std::string& filename) {
auto pos = filename.rfind('.');
if (pos == std::string::npos)
return "";
return filename.substr(pos + 1);
}
inline slog::LogStream& operator<<(slog::LogStream& os, const ov::Version& version) {
os << "Build ................................. ";
os << version.buildNumber << slog::endl;
return os;
}
inline slog::LogStream& operator<<(slog::LogStream& os, const std::map<std::string, ov::Version>& versions) {
for (auto&& version : versions) {
os << version.first << slog::endl;
os << version.second << slog::endl;
}
return os;
}
/**
* @brief Writes output data to BMP image
* @param name - image name
* @param data - output data
* @param height - height of the target image
* @param width - width of the target image
* @return false if error else true
*/
static UNUSED bool writeOutputBmp(std::string name, unsigned char* data, size_t height, size_t width) {
std::ofstream outFile;
outFile.open(name, std::ofstream::binary);
if (!outFile.is_open()) {
return false;
}
unsigned char file[14] = {
'B',
'M', // magic
0,
0,
0,
0, // size in bytes
0,
0, // app data
0,
0, // app data
40 + 14,
0,
0,
0 // start of data offset
};
unsigned char info[40] = {
40, 0, 0, 0, // info hd size
0, 0, 0, 0, // width
0, 0, 0, 0, // height
1, 0, // number color planes
24, 0, // bits per pixel
0, 0, 0, 0, // compression is none
0, 0, 0, 0, // image bits size
0x13, 0x0B, 0, 0, // horz resolution in pixel / m
0x13, 0x0B, 0, 0, // vert resolution (0x03C3 = 96 dpi, 0x0B13 = 72 dpi)
0, 0, 0, 0, // #colors in palette
0, 0, 0, 0, // #important colors
};
OPENVINO_ASSERT(
height < (size_t)std::numeric_limits<int32_t>::max && width < (size_t)std::numeric_limits<int32_t>::max,
"File size is too big: ",
height,
" X ",
width);
int padSize = static_cast<int>(4 - (width * 3) % 4) % 4;
int sizeData = static_cast<int>(width * height * 3 + height * padSize);
int sizeAll = sizeData + sizeof(file) + sizeof(info);
file[2] = (unsigned char)(sizeAll);
file[3] = (unsigned char)(sizeAll >> 8);
file[4] = (unsigned char)(sizeAll >> 16);
file[5] = (unsigned char)(sizeAll >> 24);
info[4] = (unsigned char)(width);
info[5] = (unsigned char)(width >> 8);
info[6] = (unsigned char)(width >> 16);
info[7] = (unsigned char)(width >> 24);
int32_t negativeHeight = -(int32_t)height;
info[8] = (unsigned char)(negativeHeight);
info[9] = (unsigned char)(negativeHeight >> 8);
info[10] = (unsigned char)(negativeHeight >> 16);
info[11] = (unsigned char)(negativeHeight >> 24);
info[20] = (unsigned char)(sizeData);
info[21] = (unsigned char)(sizeData >> 8);
info[22] = (unsigned char)(sizeData >> 16);
info[23] = (unsigned char)(sizeData >> 24);
outFile.write(reinterpret_cast<char*>(file), sizeof(file));
outFile.write(reinterpret_cast<char*>(info), sizeof(info));
unsigned char pad[3] = {0, 0, 0};
for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) {
unsigned char pixel[3];
pixel[0] = data[y * width * 3 + x * 3];
pixel[1] = data[y * width * 3 + x * 3 + 1];
pixel[2] = data[y * width * 3 + x * 3 + 2];
outFile.write(reinterpret_cast<char*>(pixel), 3);
}
outFile.write(reinterpret_cast<char*>(pad), padSize);
}
return true;
}
/**
* @brief Adds colored rectangles to the image
* @param data - data where rectangles are put
* @param height - height of the rectangle
* @param width - width of the rectangle
* @param rectangles - vector points for the rectangle, should be 4x compared to num classes
* @param classes - vector of classes
* @param thickness - thickness of a line (in pixels) to be used for bounding boxes
*/
static UNUSED void addRectangles(unsigned char* data,
size_t height,
size_t width,
std::vector<int> rectangles,
std::vector<int> classes,
int thickness) {
struct Color {
unsigned char red;
unsigned char green;
unsigned char blue;
};
std::vector<Color> colors = {// colors to be used for bounding boxes
{128, 64, 128}, {232, 35, 244}, {70, 70, 70}, {156, 102, 102}, {153, 153, 190},
{153, 153, 153}, {30, 170, 250}, {0, 220, 220}, {35, 142, 107}, {152, 251, 152},
{180, 130, 70}, {60, 20, 220}, {0, 0, 255}, {142, 0, 0}, {70, 0, 0},
{100, 60, 0}, {90, 0, 0}, {230, 0, 0}, {32, 11, 119}, {0, 74, 111},
{81, 0, 81}};
if (rectangles.size() % 4 != 0 || rectangles.size() / 4 != classes.size()) {
return;
}
for (size_t i = 0; i < classes.size(); i++) {
int x = rectangles.at(i * 4);
int y = rectangles.at(i * 4 + 1);
int w = rectangles.at(i * 4 + 2);
int h = rectangles.at(i * 4 + 3);
int cls = classes.at(i) % colors.size(); // color of a bounding box line
if (x < 0)
x = 0;
if (y < 0)
y = 0;
if (w < 0)
w = 0;
if (h < 0)
h = 0;
if (static_cast<std::size_t>(x) >= width) {
x = static_cast<int>(width - 1);
w = 0;
thickness = 1;
}
if (static_cast<std::size_t>(y) >= height) {
y = static_cast<int>(height - 1);
h = 0;
thickness = 1;
}
if ((static_cast<std::size_t>(x) + w) >= width) {
w = static_cast<int>(width - x - 1);
}
if ((static_cast<std::size_t>(y) + h) >= height) {
h = static_cast<int>(height - y - 1);
}
thickness = std::min(std::min(thickness, w / 2 + 1), h / 2 + 1);
size_t shift_first;
size_t shift_second;
for (int t = 0; t < thickness; t++) {
shift_first = (y + t) * width * 3;
shift_second = (y + h - t) * width * 3;
for (int ii = x; ii < x + w + 1; ii++) {
data[shift_first + ii * 3] = colors.at(cls).red;
data[shift_first + ii * 3 + 1] = colors.at(cls).green;
data[shift_first + ii * 3 + 2] = colors.at(cls).blue;
data[shift_second + ii * 3] = colors.at(cls).red;
data[shift_second + ii * 3 + 1] = colors.at(cls).green;
data[shift_second + ii * 3 + 2] = colors.at(cls).blue;
}
}
for (int t = 0; t < thickness; t++) {
shift_first = (x + t) * 3;
shift_second = (x + w - t) * 3;
for (int ii = y; ii < y + h + 1; ii++) {
data[shift_first + ii * width * 3] = colors.at(cls).red;
data[shift_first + ii * width * 3 + 1] = colors.at(cls).green;
data[shift_first + ii * width * 3 + 2] = colors.at(cls).blue;
data[shift_second + ii * width * 3] = colors.at(cls).red;
data[shift_second + ii * width * 3 + 1] = colors.at(cls).green;
data[shift_second + ii * width * 3 + 2] = colors.at(cls).blue;
}
}
}
}
inline void showAvailableDevices() {
ov::Core core;
std::vector<std::string> devices = core.get_available_devices();
std::cout << std::endl;
std::cout << "Available target devices:";
for (const auto& device : devices) {
std::cout << " " << device;
}
std::cout << std::endl;
}
inline std::string getFullDeviceName(ov::Core& core, std::string device) {
try {
return core.get_property(device, ov::device::full_name);
} catch (ov::Exception&) {
return {};
}
}
static UNUSED void printPerformanceCounts(std::vector<ov::ProfilingInfo> performanceData,
std::ostream& stream,
std::string deviceName,
bool bshowHeader = true,
int precision = 3) {
std::chrono::microseconds totalTime = std::chrono::microseconds::zero();
std::chrono::microseconds totalTimeCpu = std::chrono::microseconds::zero();
// Print performance counts
if (bshowHeader) {
stream << std::endl << "Performance counts:" << std::endl << std::endl;
}
std::ios::fmtflags fmt(std::cout.flags());
stream << std::fixed << std::setprecision(precision);
for (const auto& it : performanceData) {
if (it.real_time.count() > 0) {
totalTime += it.real_time;
}
if (it.cpu_time.count() > 0) {
totalTimeCpu += it.cpu_time;
}
std::string toPrint(it.node_name);
const int maxPrintLength = 20;
if (it.node_name.length() >= maxPrintLength) {
toPrint = it.node_name.substr(0, maxPrintLength - 5);
toPrint += "...";
}
stream << std::setw(maxPrintLength) << std::left << toPrint << " ";
switch (it.status) {
case ov::ProfilingInfo::Status::EXECUTED:
stream << std::setw(21) << std::left << "EXECUTED ";
break;
case ov::ProfilingInfo::Status::NOT_RUN:
stream << std::setw(21) << std::left << "NOT_RUN ";
break;
case ov::ProfilingInfo::Status::OPTIMIZED_OUT:
stream << std::setw(21) << std::left << "OPTIMIZED_OUT ";
break;
}
stream << "layerType: ";
if (it.node_type.length() >= maxPrintLength) {
stream << std::setw(maxPrintLength) << std::left << it.node_type.substr(0, maxPrintLength - 3) + "..."
<< " ";
} else {
stream << std::setw(maxPrintLength) << std::left << it.node_type << " ";
}
stream << std::setw(30) << std::left << "execType: " + std::string(it.exec_type) << " ";
stream << "realTime (ms): " << std::setw(10) << std::left << std::fixed << std::setprecision(3)
<< it.real_time.count() / 1000.0 << " ";
stream << "cpuTime (ms): " << std::setw(10) << std::left << std::fixed << std::setprecision(3)
<< it.cpu_time.count() / 1000.0 << " ";
stream << std::endl;
}
stream << std::setw(25) << std::left << "Total time: " << std::fixed << std::setprecision(3)
<< totalTime.count() / 1000.0 << " milliseconds" << std::endl;
stream << std::setw(25) << std::left << "Total CPU time: " << std::fixed << std::setprecision(3)
<< totalTimeCpu.count() / 1000.0 << " milliseconds" << std::endl;
stream << std::endl;
stream << "Full device name: " << deviceName << std::endl;
stream << std::endl;
stream.flags(fmt);
}
static inline std::string double_to_string(const double number) {
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << number;
return ss.str();
}
template <typename T>
using uniformDistribution = typename std::conditional<
std::is_floating_point<T>::value,
std::uniform_real_distribution<T>,
typename std::conditional<std::is_integral<T>::value, std::uniform_int_distribution<T>, void>::type>::type;
template <typename T, typename T2>
static inline void fill_random(ov::Tensor& tensor,
T rand_min = std::numeric_limits<uint8_t>::min(),
T rand_max = std::numeric_limits<uint8_t>::max()) {
std::mt19937 gen(0);
size_t tensor_size = tensor.get_size();
if (0 == tensor_size) {
throw std::runtime_error(
"Models with dynamic shapes aren't supported. Input tensors must have specific shapes before inference");
}
T* data = tensor.data<T>();
uniformDistribution<T2> distribution(rand_min, rand_max);
for (size_t i = 0; i < tensor_size; i++) {
data[i] = static_cast<T>(distribution(gen));
}
}
static inline void fill_tensor_random(ov::Tensor tensor) {
switch (tensor.get_element_type()) {
case ov::element::f32:
fill_random<float, float>(tensor);
break;
case ov::element::f64:
fill_random<double, double>(tensor);
break;
case ov::element::f16:
fill_random<short, short>(tensor);
break;
case ov::element::i32:
fill_random<int32_t, int32_t>(tensor);
break;
case ov::element::i64:
fill_random<int64_t, int64_t>(tensor);
break;
case ov::element::u8:
// uniform_int_distribution<uint8_t> is not allowed in the C++17
// standard and vs2017/19
fill_random<uint8_t, uint32_t>(tensor);
break;
case ov::element::i8:
// uniform_int_distribution<int8_t> is not allowed in the C++17 standard
// and vs2017/19
fill_random<int8_t, int32_t>(tensor, std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
break;
case ov::element::u16:
fill_random<uint16_t, uint16_t>(tensor);
break;
case ov::element::i16:
fill_random<int16_t, int16_t>(tensor);
break;
case ov::element::boolean:
fill_random<uint8_t, uint32_t>(tensor, 0, 1);
break;
default:
OPENVINO_THROW("Input type is not supported for a tensor");
}
}
static UNUSED bool sort_pc_descend(const ov::ProfilingInfo& profiling1, const ov::ProfilingInfo& profiling2) {
return profiling1.real_time > profiling2.real_time;
}
static UNUSED void printPerformanceCountsSort(std::vector<ov::ProfilingInfo> performanceData,
std::ostream& stream,
std::string deviceName,
std::string sorttype,
bool bshowHeader = true,
int precision = 3) {
std::chrono::microseconds totalTime = std::chrono::microseconds::zero();
std::chrono::microseconds totalTimeCpu = std::chrono::microseconds::zero();
// Print performance counts
if (bshowHeader) {
stream << std::endl << "Performance counts:" << std::endl << std::endl;
}
std::ios::fmtflags fmt(std::cout.flags());
stream << std::fixed << std::setprecision(precision);
for (const auto& it : performanceData) {
if (it.real_time.count() > 0) {
totalTime += it.real_time;
}
if (it.cpu_time.count() > 0) {
totalTimeCpu += it.cpu_time;
}
}
if (totalTime.count() != 0) {
std::vector<ov::ProfilingInfo> sortPerfCounts{std::begin(performanceData), std::end(performanceData)};
if (sorttype == pcSort || sorttype == pcSimpleSort) {
std::sort(sortPerfCounts.begin(), sortPerfCounts.end(), sort_pc_descend);
}
for (const auto& it : sortPerfCounts) {
if ((sorttype == pcSimpleSort && it.status == ov::ProfilingInfo::Status::EXECUTED) ||
sorttype != pcSimpleSort) {
std::string toPrint(it.node_name);
const int maxPrintLength = 20;
if (it.node_name.length() >= maxPrintLength) {
toPrint = it.node_name.substr(0, maxPrintLength - 5);
toPrint += "...";
}
stream << std::setw(maxPrintLength) << std::left << toPrint << " ";
switch (it.status) {
case ov::ProfilingInfo::Status::EXECUTED:
stream << std::setw(21) << std::left << "EXECUTED ";
break;
case ov::ProfilingInfo::Status::NOT_RUN:
stream << std::setw(21) << std::left << "NOT_RUN ";
break;
case ov::ProfilingInfo::Status::OPTIMIZED_OUT:
stream << std::setw(21) << std::left << "OPTIMIZED_OUT ";
break;
}
stream << "layerType: ";
if (it.node_type.length() >= maxPrintLength) {
stream << std::setw(maxPrintLength) << std::left
<< it.node_type.substr(0, maxPrintLength - 3) + "..."
<< " ";
} else {
stream << std::setw(maxPrintLength) << std::left << it.node_type << " ";
}
stream << std::setw(30) << std::left << "execType: " + std::string(it.exec_type) << " ";
stream << "realTime (ms): " << std::setw(10) << std::left << std::fixed << std::setprecision(3)
<< it.real_time.count() / 1000.0 << " ";
stream << "cpuTime (ms): " << std::setw(10) << std::left << std::fixed << std::setprecision(3)
<< it.cpu_time.count() / 1000.0 << " ";
double opt_proportion = it.real_time.count() * 100.0 / totalTime.count();
std::stringstream opt_proportion_ss;
opt_proportion_ss << std::fixed << std::setprecision(2) << opt_proportion;
std::string opt_proportion_str = opt_proportion_ss.str();
if (opt_proportion_str == "0.00") {
opt_proportion_str = "N/A";
}
stream << std::setw(20) << std::left << "proportion: " + opt_proportion_str + "%";
stream << std::endl;
}
}
}
stream << std::setw(25) << std::left << "Total time: " + std::to_string(totalTime.count() / 1000.0)
<< " milliseconds" << std::endl;
stream << std::endl;
stream << "Full device name: " << deviceName << std::endl;
stream << std::endl;
stream.flags(fmt);
}