├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bdcpp ├── bdcpp.cpp └── bdcpp.h ├── conanfile.txt └── example.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # BDcpp -- Simple Bjontegaard Delta metric implementation for C++. 3 | # 4 | # MIT License 5 | # 6 | # Copyright (c) 2022 Tim Bruylants 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | # ============================================================================= 26 | 27 | cmake_minimum_required(VERSION 3.12 FATAL_ERROR) 28 | 29 | project(bdcpp LANGUAGES CXX C) 30 | 31 | # Setup Conan package finding. 32 | set(CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) 33 | set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) 34 | 35 | # Language settings. 36 | set(CMAKE_CXX_STANDARD 17) # C++17 37 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 38 | set(CMAKE_CXX_EXTENSIONS OFF) 39 | 40 | # # Some global settings. 41 | # if(MSVC) 42 | # # Disable some warnings on MSVC. 43 | # add_compile_definitions(_SCL_SECURE_NO_WARNINGS) 44 | # add_compile_options("/wd\"26451\"") 45 | # endif() 46 | 47 | if (MSVC) 48 | add_compile_options(/W4 /bigobj "/wd\"4127\"") 49 | endif() 50 | 51 | # Get everything into one final folder. 52 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 53 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 54 | 55 | # External packages. 56 | find_package(Eigen3 CONFIG) 57 | 58 | # ----------------------------------- 59 | # Library code. 60 | # ----------------------------------- 61 | set(SRC_BDCPP_LIB_PRIVATE 62 | bdcpp/bdcpp.cpp 63 | bdcpp/bdcpp.h 64 | ) 65 | 66 | add_library(bdcpp STATIC ${SRC_BDCPP_LIB_PRIVATE}) 67 | target_link_libraries(bdcpp PRIVATE Eigen3::Eigen) 68 | target_include_directories(bdcpp PUBLIC "bdcpp/") 69 | 70 | # ----------------------------------- 71 | # Main example application. 72 | # ----------------------------------- 73 | set(SRC_FILES 74 | example.cpp 75 | ) 76 | add_executable(example ${SRC_FILES}) 77 | target_link_libraries(example PRIVATE bdcpp) 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Tim Bruylants 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bjontegaard Metric implementation for C++17 (or later). 2 | 3 | ## Description: 4 | 5 | This project contains an implementation of the Bjontegaard metric to calculate Delta SNR and Delta Rate, using arbitrary number of data points. The calculated results comply with VCEG-M33 when using 4 data points. This code relies on the Eigen3 library to perform the polynomial fitting of curves. 6 | 7 | The library provides two functions (see `bdcpp.h` for type details): 8 | 9 | ```cpp 10 | value_type bdsnr(const curve_data_type& curveA, const curve_data_type& curveB, const int polyOrder = 3); 11 | 12 | value_type bdbr(const curve_data_type& curveA, const curve_data_type& curveB, const int polyOrder = 3); 13 | ``` 14 | 15 | Normally, curve fitting is done using a 3rd order polynomial, however this code can also do higher order approximations to improve precision. 16 | 17 | Note: This code is a port of the original VBA implementation (see [Bjontegaard ETRO project](https://github.com/tbr/bjontegaard_etro)) to C++. 18 | 19 | ## Building: 20 | 21 | No special steps are required. 22 | 23 | The example build uses Conan and CMake. Create a build folder and from there issue: 24 | 25 | ``` 26 | conan install -s build_type=Release 27 | conan install -s build_type=Debug 28 | cmake 29 | ``` 30 | 31 | ## Author: 32 | 33 | Tim Bruylants 34 | 35 | ## License: 36 | 37 | MIT license, see included LICENSE.txt file. 38 | 39 | ## References: 40 | 41 | [1] G. Bjontegaard, Calculation of average PSNR differences between RD-curves (VCEG-M33) 42 | 43 | _Copyright (C) 2022 Tim Bruylants._ 44 | -------------------------------------------------------------------------------- /bdcpp/bdcpp.cpp: -------------------------------------------------------------------------------- 1 | /*++ 2 | BDcpp -- Simple Bjontegaard Delta metric implementation for C++. 3 | 4 | MIT License 5 | 6 | Copyright (c) 2022 Tim Bruylants 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | --*/ 26 | 27 | #include "bdcpp.h" 28 | 29 | #include 30 | #include 31 | 32 | namespace bdcpp 33 | { 34 | namespace details 35 | { 36 | // Fit a polynomial of given order on the curve (minimizing squared distance). 37 | std::vector polyFit(const curve_data_type& curve, const size_t order) 38 | { 39 | const size_t numCoefficients = order + 1; 40 | const size_t nCount = curve.size(); 41 | 42 | Eigen::MatrixX X(nCount, numCoefficients); 43 | Eigen::MatrixX Y(nCount, 1); 44 | 45 | // fill X and Y matrices (X is a Vandermonde matrix) 46 | for (size_t row = 0; row < nCount; ++row) 47 | { 48 | Y(row, 0) = curve[row].second; 49 | value_type v = (value_type)1; 50 | for (size_t col = 0; col < numCoefficients; ++col) 51 | { 52 | X(row, col) = v; 53 | v *= curve[row].first; 54 | } 55 | } 56 | 57 | // Solve for the polynomial coefficients (one column) and return. 58 | const Eigen::VectorX coefficients = X.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(Y); 59 | return std::vector(coefficients.data(), coefficients.data() + numCoefficients); 60 | } 61 | 62 | // Calculates Y(x), where the polynomial coefficients are given. 63 | value_type polyVal(const std::vector& coefficients, const value_type x) 64 | { 65 | assert(!coefficients.empty()); 66 | size_t c = coefficients.size(); 67 | value_type r = coefficients[--c]; 68 | while (c != 0) 69 | { 70 | r *= x; 71 | r += coefficients[--c]; 72 | } 73 | return r; 74 | } 75 | 76 | std::vector polyIntegrate(const std::vector& coefficients, const value_type constant = 0) 77 | { 78 | const size_t numCoefficients = coefficients.size(); 79 | std::vector ic(numCoefficients + 1); 80 | ic[0] = constant; 81 | for (size_t c = 0; c < numCoefficients; ++c) 82 | { 83 | ic[c + 1] = coefficients[c] / (c + 1); 84 | } 85 | return ic; 86 | } 87 | 88 | // The main Bjontegaard calculation to get the area surface difference between two curves. 89 | value_type bdDiff(const curve_data_type& curveA, const curve_data_type& curveB, const int polyOrder) 90 | { 91 | assert(polyOrder >= 3); 92 | // Take lowest and highest X values (assumes sorted curves and relevant range overlap). 93 | const auto lowX = std::max(curveA.front().first, curveB.front().first); 94 | const auto highX = std::min(curveA.back().first, curveB.back().first); 95 | 96 | // Fit curves as polynomials and integrate them. 97 | const auto iCoefficientsA = polyIntegrate(polyFit(curveA, (size_t)polyOrder)); 98 | const auto iCoefficientsB = polyIntegrate(polyFit(curveB, (size_t)polyOrder)); 99 | 100 | // Calculate the definite integrals. 101 | const auto intA = polyVal(iCoefficientsA, highX) - polyVal(iCoefficientsA, lowX); 102 | const auto intB = polyVal(iCoefficientsB, highX) - polyVal(iCoefficientsB, lowX); 103 | 104 | // Return the BD diff (as the area over range). 105 | return (intB - intA) / (highX - lowX); 106 | } 107 | 108 | // Prepare curve points for BD calculations. 109 | template 110 | curve_data_type prepareCurve(const curve_data_type& curve) 111 | { 112 | assert(curve.size() >= 4); 113 | auto newCurve(curve); 114 | std::sort(newCurve.begin(), newCurve.end(), [](const curve_data_point_type& vA, const curve_data_point_type& vB) 115 | { 116 | return vA.first < vB.first; 117 | }); 118 | std::for_each(newCurve.begin(), newCurve.end(), [](curve_data_point_type& v) 119 | { 120 | v.first = std::log(v.first); 121 | if constexpr (TRANSPOSE) 122 | { 123 | std::swap(v.first, v.second); 124 | } 125 | }); 126 | return newCurve; 127 | } 128 | } 129 | 130 | // Calculate the BD-SNR for the two given curves (returns a distortion improvement in dB). 131 | value_type bdsnr(const curve_data_type& curveA, const curve_data_type& curveB, const int polyOrder) 132 | { 133 | return details::bdDiff(details::prepareCurve(curveA), details::prepareCurve(curveB), polyOrder); 134 | } 135 | 136 | // Calculate the BD-BR for the two given curves (returns a rate improvement in %). 137 | value_type bdbr(const curve_data_type& curveA, const curve_data_type& curveB, const int polyOrder) 138 | { 139 | return (std::exp(details::bdDiff(details::prepareCurve(curveA), details::prepareCurve(curveB), polyOrder)) - 1) * 100; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /bdcpp/bdcpp.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | BDcpp -- Simple Bjontegaard Delta metric implementation for C++. 3 | 4 | MIT License 5 | 6 | Copyright (c) 2022 Tim Bruylants 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | --*/ 26 | 27 | #pragma once 28 | 29 | #include 30 | 31 | namespace bdcpp 32 | { 33 | typedef double value_type; // base value type 34 | typedef std::pair curve_data_point_type; // one (x, y) pair as (rate, psnr) 35 | typedef std::vector curve_data_type; // (X, Y) values of an RD curve (X=rate, Y=psnr) 36 | 37 | value_type bdsnr(const curve_data_type& curveA, const curve_data_type& curveB, const int polyOrder = 3); 38 | value_type bdbr(const curve_data_type& curveA, const curve_data_type& curveB, const int polyOrder = 3); 39 | } -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | eigen/3.4.0 3 | 4 | [generators] 5 | cmake_find_package_multi 6 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | /*++ 2 | BDcpp -- Simple Bjontegaard Delta metric implementation for C++. 3 | 4 | Example code to demonstrate usage. 5 | 6 | Copyright (c) Tim bruylants. All rights reserved. 7 | --*/ 8 | 9 | #include "bdcpp.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | bdcpp::value_type round(const bdcpp::value_type& v, const int digits) 16 | { 17 | const double p = std::pow(10., digits); 18 | return (bdcpp::value_type)((int64_t)(v * p + (v > 0 ? 0.5 : -0.5)) / p); 19 | } 20 | 21 | int main(int argc, char* argv[]) 22 | { 23 | (void)argc; 24 | (void)argv; 25 | 26 | // VCEG-M33 numbers. 27 | bdcpp::curve_data_type m33_curveA = { 28 | {90.33, 27.95}, 29 | {181.03, 31.17}, 30 | {332.99, 34.44}, 31 | {547.79, 38.11} 32 | }; 33 | bdcpp::curve_data_type m33_curveB = { 34 | {127.1719, 29.9286}, 35 | {186.14, 32.4165}, 36 | {307.2924, 34.7013}, 37 | {481.5588, 37.4561} 38 | }; 39 | 40 | // ETRO barb512 numbers. 41 | bdcpp::curve_data_type etro_curveA = { 42 | {2.99899, 48.6681}, 43 | {1.99884, 43.8357}, 44 | {1.49673, 41.3982}, 45 | {0.99707, 38.0124}, 46 | {0.745941, 35.5122}, 47 | {0.596436, 33.8673}, 48 | {0.495148, 32.6658}, 49 | {0.29425, 29.4505}, 50 | {0.244049, 28.484}, 51 | }; 52 | bdcpp::curve_data_type etro_curveB = { 53 | {2.997253, 48.5637}, 54 | {1.968292, 43.7318}, 55 | {1.472565, 41.2727}, 56 | {0.994965, 38.0001}, 57 | {0.748871, 35.6375}, 58 | {0.58902, 33.9057}, 59 | {0.499176, 32.8318}, 60 | {0.296753, 29.6851}, 61 | {0.249634, 28.8244}, 62 | }; 63 | 64 | { 65 | const auto bdsnr = bdcpp::bdsnr(m33_curveA, m33_curveB); 66 | const auto bdbr = bdcpp::bdbr(m33_curveA, m33_curveB); 67 | std::cout << bdsnr << " -- " << bdbr << "\n"; 68 | assert(round(bdsnr, 6) == 0.800713); 69 | assert(round(bdbr, 6) == -13.261773); 70 | } 71 | 72 | { 73 | const auto bdsnr = bdcpp::bdsnr(etro_curveA, etro_curveB); 74 | const auto bdbr = bdcpp::bdbr(etro_curveA, etro_curveB); 75 | std::cout << bdsnr << " -- " << bdbr << "\n"; 76 | assert(round(bdsnr, 6) == 0.072297); 77 | assert(round(bdbr, 6) == -0.903450); 78 | } 79 | 80 | return 0; 81 | } --------------------------------------------------------------------------------