From 4c6de81a5f281d2214255baa422ed29035965464 Mon Sep 17 00:00:00 2001 From: Jerome Date: Mon, 1 May 2023 18:16:13 +0200 Subject: [PATCH] Robustified frame transformations. Fixed image projection. Added quick performance test comparing multithreading with single thread projection. --- .vscode/launch.json | 19 +++ .vscode/tasks.json | 21 +++ cpp/include/Camera.hpp | 3 +- cpp/include/frame_conversions.hpp | 34 ++++- cpp/include/test_framework.hpp | 135 ++++++++++++++++++ cpp/src/Camera.cpp | 3 - cpp/src/Image.cpp | 4 +- cpp/src/Slicer360ToPerspective.cpp | 2 +- cpp/src/frame_conversions.cpp | 23 ++-- cpp/src/tests.cpp | 214 ++++++++++++++++++----------- 10 files changed, 363 insertions(+), 95 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 cpp/include/test_framework.hpp diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..27e69dd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "360 to perspective", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/cpp/obj/360_to_perspective.exe", + "cwd": "${workspaceFolder}/cpp", + "environment": [], + "args": [], + "stopAtEntry": false, + "externalConsole": false + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..8297bc2 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,21 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "D:/Programmes/Octave/Octave-7.3.0/usr/bin/make.exe", + "args": [ + "-f", + "${workspaceFolder}/cpp/Makefile" + ], + "options": { + "cwd": "${workspaceFolder}/cpp" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/cpp/include/Camera.hpp b/cpp/include/Camera.hpp index afead6a..5e9f1fa 100644 --- a/cpp/include/Camera.hpp +++ b/cpp/include/Camera.hpp @@ -3,7 +3,8 @@ #include -#define DEG2RAD 0.017453292519943295 +#define DEG2RAD 0.01745329251994329576923690768488612713442871888541725456097191440171009114603449443682241569634509482 +#define RAD2DEG 57.29577951308232087679815481410517033240547246656432154916024386120284714832155263244096899585111094 /// @brief A camera class to perform frame transformations and projections. class Camera { diff --git a/cpp/include/frame_conversions.hpp b/cpp/include/frame_conversions.hpp index dd9b092..f7c1884 100644 --- a/cpp/include/frame_conversions.hpp +++ b/cpp/include/frame_conversions.hpp @@ -4,6 +4,38 @@ #include #include +#ifndef M_PI +#define M_PI 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 +#endif + +namespace frame_conversions { +/// @brief Computes the modulo of a number with another number, ensuring that the results lies in the interval [0;b]. +/// @param a The number to compute the modulo of. +/// @param b The modulo. +/// @return The modulo of a with b, ensuring that the result lies in the interval [0;b] +template +inline T fmod_pos(T a, T b) { + return a - b*std::floor(a/b); +} + +/// @brief Maps a value x from the range (a,b) to the range (c,d). +/// +/// This function takes an input value x and maps it from the input range (a,b) +/// to the output range (c,d). The resulting mapped value is returned. +/// +/// @param x The value to be mapped. +/// @param a The lower bound of the input range. +/// @param b The upper bound of the input range. +/// @param c The lower bound of the output range. +/// @param d The upper bound of the output range. +/// +/// @return The mapped value of x in the output range. +/// +/// @note Make sure that a != b to avoid a divide-by-zero error. +double map(double x, double a, double b, double c, double d); + +} // namespace frame_conversions + /// @brief Converts a spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+) to a cartesian vector. Eigen::Vector3d sph2cart(const Eigen::Vector3d & sph); @@ -16,7 +48,7 @@ Eigen::Vector3d cart2sph(const Eigen::Vector3d & cart); /// @param width Width of the equirectangular image. /// @param height Height of the equirectangular image. /// @return A spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+). -Eigen::Vector3d equirectangular2sph(unsigned int i, unsigned int j, unsigned int width, unsigned int height); +Eigen::Vector3d equirectangular2sph(int i, int j, unsigned int width, unsigned int height); /// @brief Converts spherical coordinates to pixel coordinates of an equirectangular image. /// @param p_sph A spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+). diff --git a/cpp/include/test_framework.hpp b/cpp/include/test_framework.hpp new file mode 100644 index 0000000..12ef3b9 --- /dev/null +++ b/cpp/include/test_framework.hpp @@ -0,0 +1,135 @@ +#ifndef h_test_framework +#define h_test_framework + +#ifndef TOL_EQ_TEST +#define TOL_EQ_TEST 1e-9 +#endif + +static size_t num_tests = 0; +static size_t num_tests_ok = 0; +static size_t num_tests_ko = 0; + +#define PRINT_VAR(x) std::cout << #x << "\t= " << (x) << std::endl + +// EXPECT_EQ macro +#define EXPECT_EQ(actual, expected) EXPECT_EQ_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_EQ_IMPL(actual, expected, file, line) \ + do { \ + num_tests++; \ + auto&& e = (expected); \ + auto&& a = (actual); \ + if (e != a) { \ + std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \ + num_tests_ko++; \ + } \ + else { \ + num_tests_ok++; \ + } \ + } while (false) + +// EXPECT_DOUBLE_EQ macro +#define EXPECT_DOUBLE_EQ(actual, expected) EXPECT_DOUBLE_EQ_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_DOUBLE_EQ_IMPL(actual, expected, file, line) \ + do { \ + num_tests++; \ + double e = (expected); \ + double a = (actual); \ + if (std::abs(e - a) > TOL_EQ_TEST) { \ + std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \ + num_tests_ko++; \ + } \ + else { \ + num_tests_ok++; \ + } \ + } while (false) + +// EXPECT_LT macro +#define EXPECT_LT(actual, expected) EXPECT_LT_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_LT_IMPL(actual, expected, file, line) \ + do { \ + num_tests++; \ + auto&& e = (expected); \ + auto&& a = (actual); \ + if (a < e) { \ + num_tests_ok++; \ + } \ + else { \ + std::cout << file << ":" << line << " : " << "Expected " << a << " < " << (#expected) << " = " << e << ", actual: " << (#actual) << std::endl; \ + num_tests_ko++; \ + } \ + } while (false) + +// EXPECT_LE macro +#define EXPECT_LE(actual, expected) EXPECT_LE_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_LE_IMPL(actual, expected, file, line) \ + do { \ + num_tests++; \ + auto&& e = (expected); \ + auto&& a = (actual); \ + if (a <= e) { \ + num_tests_ok++; \ + } \ + else { \ + std::cout << file << ":" << line << " : " << "Expected " << a << " <= " << (#expected) << " = " << e << ", actual: " << (#actual) << std::endl; \ + num_tests_ko++; \ + } \ + } while (false) + +// EXPECT_GT macro +#define EXPECT_GT(actual, expected) EXPECT_GT_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_GT_IMPL(actual, expected, file, line) \ + do { \ + num_tests++; \ + auto&& e = (expected); \ + auto&& a = (actual); \ + if (a > e) { \ + num_tests_ok++; \ + } \ + else { \ + std::cout << file << ":" << line << " : " << "Expected " << a << " > " << (#expected) << " = " << e << ", actual: " << (#actual) << std::endl; \ + num_tests_ko++; \ + } \ + } while (false) + +// EXPECT_GE macro +#define EXPECT_GE(actual, expected) EXPECT_GE_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_GE_IMPL(actual, expected, file, line) \ + do { \ + num_tests++; \ + auto&& e = (expected); \ + auto&& a = (actual); \ + if (a >= e) { \ + num_tests_ok++; \ + } \ + else { \ + std::cout << file << ":" << line << " : " << "Expected " << a << " >= " << (#expected) << " = " << e << ", actual: " << (#actual) << std::endl; \ + num_tests_ko++; \ + } \ + } while (false) + +// EXPECT_VEC_EQ macro +#define EXPECT_VEC_EQ(actual, expected) EXPECT_VEC_EQ_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_VEC_EQ_IMPL(actual, expected, file, line) \ + do { \ + for(unsigned int i = 0 ; i < (expected).size() ; i++) { \ + EXPECT_DOUBLE_EQ_IMPL((actual)[i], (expected)[i], file, line); \ + } \ + } while (false) + +// EXPECT_MAT_EQ macro +#define EXPECT_MAT_EQ(actual, expected) EXPECT_MAT_EQ_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_MAT_EQ_IMPL(actual, expected, file, line) \ + do { \ + for(unsigned int i = 0 ; i < (expected).rows() ; i++) { \ + for(unsigned int j = 0 ; j < (expected).rows() ; j++) { \ + EXPECT_DOUBLE_EQ_IMPL((actual)(i,j), (expected)(i,j), file, line); \ + } \ + } \ + } while (false) + +#define PRINT_TESTS_SUMMARY() \ + do { \ + std::cout << "Tests summary: " << num_tests << " tests in total\n " << num_tests_ok << "/" << num_tests << " OK (" << ((100.0*(double)num_tests_ok)/num_tests) << " %)\n " << num_tests_ko << "/" << num_tests << " KO (" << ((100.0*(double)num_tests_ko)/num_tests) << " %)" << std::endl; \ + } while (false) + +#endif diff --git a/cpp/src/Camera.cpp b/cpp/src/Camera.cpp index 6b2ef32..71b4481 100644 --- a/cpp/src/Camera.cpp +++ b/cpp/src/Camera.cpp @@ -1,8 +1,5 @@ #include -#define _USE_MATH_DEFINES -#include - Camera::Camera() : fwd(Eigen::Vector3d::UnitX()), up(Eigen::Vector3d::UnitZ()), R(Eigen::Matrix3d::Identity()), R_T(Eigen::Matrix3d::Identity()), FOV(90.0*DEG2RAD), tanFOV2(tan(FOV/2.0)) { diff --git a/cpp/src/Image.cpp b/cpp/src/Image.cpp index a494d04..645872c 100644 --- a/cpp/src/Image.cpp +++ b/cpp/src/Image.cpp @@ -109,7 +109,9 @@ Eigen::Vector3i Image::GetPixelInterp(Eigen::Vector2d const& pos, InterpMethod c int j = (int)std::floor(pos[1]); double u = pos[0] - i; double v = pos[1] - j; - Eigen::Vector3i rgb = ((1 - u) * (1 - v) * GetPixel(i, j).cast() + u * (1 - v) * GetPixel(i + 1, j).cast() + (1 - u) * v * GetPixel(i, j + 1).cast() + u * v * GetPixel(i + 1, j + 1).cast()).cast(); + int i_plus_1 = (i + 1) % height; + int j_plus_1 = (j + 1) % width; + Eigen::Vector3i rgb = ((1 - u) * (1 - v) * GetPixel(i, j).cast() + u * (1 - v) * GetPixel(i_plus_1, j).cast() + (1 - u) * v * GetPixel(i, j_plus_1).cast() + u * v * GetPixel(i_plus_1, j_plus_1).cast()).cast(); return rgb; } else // By default, use nearest neighbor interpolation diff --git a/cpp/src/Slicer360ToPerspective.cpp b/cpp/src/Slicer360ToPerspective.cpp index e790e4d..768dca5 100644 --- a/cpp/src/Slicer360ToPerspective.cpp +++ b/cpp/src/Slicer360ToPerspective.cpp @@ -60,7 +60,7 @@ Eigen::Vector3i Slicer360ToPerspective::ProjectToCameraPixel(Camera const& camer // Compute ray direction from sensor coordinates Eigen::Vector3d ray_dir = camera.ComputeRayDirInInertialFrame(p_sensor); Eigen::Vector3d ray_dir_sph = cart2sph(ray_dir); - Eigen::Vector2d p_equi = sph2equirectangular_d(ray_dir_sph, output_image_width, output_image_height); + Eigen::Vector2d p_equi = sph2equirectangular_d(ray_dir_sph, panorama_image.GetWidth(), panorama_image.GetHeight()); return panorama_image.GetPixelInterp(p_equi, interpolation_method); } diff --git a/cpp/src/frame_conversions.cpp b/cpp/src/frame_conversions.cpp index 760ac03..a9807bc 100644 --- a/cpp/src/frame_conversions.cpp +++ b/cpp/src/frame_conversions.cpp @@ -1,6 +1,12 @@ #include -#define M_PI 3.14159265358979323846 +namespace frame_conversions { + +double map(double x, double a, double b, double c, double d) { + return c + (x - a) * (d - c) / (b - a); +} + +}// namespace frame_conversions Eigen::Vector3d sph2cart(const Eigen::Vector3d & sph) { double r = sph(0); @@ -18,19 +24,20 @@ Eigen::Vector3d cart2sph(const Eigen::Vector3d & cart) { double z = cart(2); double r = sqrt(x*x + y*y + z*z); double theta = acos(z/r); - double phi = atan2(y,x); + double phi = frame_conversions::fmod_pos(atan2(y,x), 2.0*M_PI); return Eigen::Vector3d(r,theta,phi); } -Eigen::Vector3d equirectangular2sph(unsigned int i, unsigned int j, unsigned int width, unsigned int height) { - return Eigen::Vector3d(1.0, M_PI*(((double)i)/(height-1)), 2.0*M_PI*(((double)j)/width - 0.5)); +Eigen::Vector3d equirectangular2sph(int i, int j, unsigned int width, unsigned int height) { + return Eigen::Vector3d(1.0, std::max(0.0, std::min(M_PI*(((double)i)/(height-1)), M_PI)), frame_conversions::fmod_pos(2.0*M_PI*((double)j)/((double)width), 2.0*M_PI)); } Eigen::Vector2d sph2equirectangular_d(Eigen::Vector3d const& p_sph, unsigned int width, unsigned int height) { - double theta = p_sph[1]; - double phi = p_sph[2]; - double i = std::fmod(((height - 1)*theta)/M_PI, height); - double j = std::fmod((width*(M_PI + phi))/(2.0*M_PI), width); + double theta = std::max(0.0, std::min(p_sph[1], M_PI)); + double phi = frame_conversions::fmod_pos(p_sph[2], 2.0*M_PI); + double i = ((height - 1)*theta)/M_PI; + // double j = std::fmod((width*phi)/(2.0*M_PI), width); + double j = std::fmod(frame_conversions::map(phi, 0.0, 2.0*M_PI, 0.0, width), width); return Eigen::Vector2d(i, j); } diff --git a/cpp/src/tests.cpp b/cpp/src/tests.cpp index f0870f3..2b9f895 100644 --- a/cpp/src/tests.cpp +++ b/cpp/src/tests.cpp @@ -2,80 +2,98 @@ #include #include #include +#include -#define _USE_MATH_DEFINES -#include - -constexpr double TOL_EQ_TEST = 1e-9; -constexpr double M_PI = 3.14159265358979323846; - -static size_t num_tests = 0; -static size_t num_tests_ok = 0; -static size_t num_tests_ko = 0; - -#define PRINT_VAR(x) std::cout << #x << "\t= " << (x) << std::endl - -// EXPECT_EQ macro -#define EXPECT_EQ(actual, expected) EXPECT_EQ_IMPL(actual, expected, __FILE__, __LINE__) -#define EXPECT_EQ_IMPL(actual, expected, file, line) \ - do { \ - num_tests++; \ - auto&& e = (expected); \ - auto&& a = (actual); \ - if (e != a) { \ - std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \ - num_tests_ko++; \ - } \ - else { \ - num_tests_ok++; \ - } \ - } while (false) - -// EXPECT_DOUBLE_EQ macro -#define EXPECT_DOUBLE_EQ(actual, expected) EXPECT_DOUBLE_EQ_IMPL(actual, expected, __FILE__, __LINE__) -#define EXPECT_DOUBLE_EQ_IMPL(actual, expected, file, line) \ - do { \ - num_tests++; \ - double e = (expected); \ - double a = (actual); \ - if (std::abs(e - a) > TOL_EQ_TEST) { \ - std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \ - num_tests_ko++; \ - } \ - else { \ - num_tests_ok++; \ - } \ - } while (false) - -// EXPECT_VEC_EQ macro -#define EXPECT_VEC_EQ(actual, expected) EXPECT_VEC_EQ_IMPL(actual, expected, __FILE__, __LINE__) -#define EXPECT_VEC_EQ_IMPL(actual, expected, file, line) \ - do { \ - for(unsigned int i = 0 ; i < (expected).size() ; i++) { \ - EXPECT_DOUBLE_EQ_IMPL((actual)[i], (expected)[i], file, line); \ - } \ - } while (false) - -// EXPECT_MAT_EQ macro -#define EXPECT_MAT_EQ(actual, expected) EXPECT_MAT_EQ_IMPL(actual, expected, __FILE__, __LINE__) -#define EXPECT_MAT_EQ_IMPL(actual, expected, file, line) \ - do { \ - for(unsigned int i = 0 ; i < (expected).rows() ; i++) { \ - for(unsigned int j = 0 ; j < (expected).rows() ; j++) { \ - EXPECT_DOUBLE_EQ_IMPL((actual)(i,j), (expected)(i,j), file, line); \ - } \ - } \ - } while (false) - -#define PRINT_TESTS_SUMMARY() \ - do { \ - std::cout << "Tests summary: " << num_tests << " tests in total\n " << num_tests_ok << "/" << num_tests << " OK (" << ((100.0*(double)num_tests_ok)/num_tests) << " %)\n " << num_tests_ko << "/" << num_tests << " KO (" << ((100.0*(double)num_tests_ko)/num_tests) << " %)" << std::endl; \ - } while (false) +#include int main() { std::cout.precision(16); std::cout << "Running Unit Tests\n"; + if(0){// frame transforms + std::cout << "frame transforms\n"; + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, 0., 0.), 100, 50).transpose()); + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, 0., 1.), 100, 50).transpose()); + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, 0.), 100, 50).transpose()); + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, M_PI/2.0), 100, 50).transpose()); + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, M_PI), 100, 50).transpose()); + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, -M_PI/2.0), 100, 50).transpose()); + + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, -M_PI/2.0, 0.), 100, 50).transpose()); + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, -M_PI/2.0), 100, 50).transpose()); + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, -M_PI/2.0, -M_PI), 100, 50).transpose()); + // PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, -2.5*M_PI), 100, 50).transpose()); + + std::cout << "Testing equirectangular coordinates.\n"; + + int i = 0; + int j = 0; + int width = 10; + int height = 5; + + if(1) { std::cout << "\nTesting equirectangular2sph.\n"; + for(i = -2*height ; i < 2*height ; i++) { + for(j = -2*width ; j < 2*width ; j++) { + Eigen::Vector3d p_sph = equirectangular2sph(i, j, width, height); + // std::cout << "i: " << i << ", j: " << j << ", p_sph: " << p_sph.transpose() << "\n"; + EXPECT_DOUBLE_EQ(p_sph[0], 1.0); + EXPECT_LT(p_sph[1], M_PI+TOL_EQ_TEST); + EXPECT_GE(p_sph[1], 0.0); + EXPECT_LT(p_sph[2], 2.0*M_PI); + EXPECT_GE(p_sph[2], 0.0); + } + } + } + + if(1) { std::cout << "\nTesting roundtrip.\n"; + for(i = 0 ; i < height ; i++) { + for(j = 0 ; j < width ; j++) { + Eigen::Vector3d p_sph = equirectangular2sph(i, j, width, height); + Eigen::Vector2d p_equirectangular = sph2equirectangular_d(p_sph, width, height); + // std::cout << "i: " << i << ", j: " << j << ", p_sph: " << p_sph.transpose() << ", p_equirectangular: " << p_equirectangular.transpose() << ((p_equirectangular[0] > height) ? "I OUT OF BOUNDS " : "") << ((p_equirectangular[1] > width) ? "J OUT OF BOUNDS " : "") << "\n"; + EXPECT_DOUBLE_EQ(p_equirectangular[0], i); + EXPECT_DOUBLE_EQ(p_equirectangular[1], j); + EXPECT_LT(p_equirectangular[0], height); + EXPECT_GE(p_equirectangular[0], 0.0); + EXPECT_LT(p_equirectangular[1], width); + EXPECT_GE(p_equirectangular[1], 0.0); + } + } + } + + if(1) { std::cout << "\nTesting sph2equirectangular_d out of bounds.\n"; + width = 3600; + height = 1801; + double theta = 0.0; + double phi = M_PI/4.0; + + std::cout << "\nTesting sph2equirectangular_d out of bounds in THETA.\n"; + for(theta = -4.0*M_PI ; theta < 4.0*M_PI ; theta += M_PI/10.0) { + auto p_equi = sph2equirectangular_d(Eigen::Vector3d(1.0, theta, phi), width, height).transpose(); + // std::cout << "theta: " << theta << ", phi: " << phi << ", p_equirectangular: " << p_equi << "\n"; + EXPECT_EQ(p_equi[0] < 0, false); + EXPECT_EQ(p_equi[0] >= height, false); + } + + std::cout << "\nTesting sph2equirectangular_d out of bounds in PHI.\n"; + theta = M_PI/2.0; + for(phi = -4.0*M_PI ; phi < 4.0*M_PI ; phi += M_PI/10.0) { + auto p_equi = sph2equirectangular_d(Eigen::Vector3d(1.0, theta, phi), width, height).transpose(); + // std::cout << "theta: " << theta << ", phi: " << phi << ",\tp_equirectangular: " << p_equi << "\n"; + EXPECT_EQ(p_equi[1] < 0, false); + EXPECT_EQ(p_equi[1] >= width, false); + } + } + + if(1) { std::cout << "\nTesting fmod.\n"; + for(double x = -4.0*M_PI ; x < 4.0*M_PI ; x += M_PI/10.0) { + // std::cout << x << "\t" << std::fmod(x, 2.0*M_PI) << "\t" << frame_conversions::fmod_pos(x, 2.0*M_PI) << "\n"; + EXPECT_EQ(frame_conversions::fmod_pos(x, 2.0*M_PI) >= 0.0, true); + EXPECT_EQ(frame_conversions::fmod_pos(x, 2.0*M_PI) < 2.0*M_PI, true); + } + } + } + if(0){// Camera std::cout << "Camera\n"; {// Constructor, accessors and setters @@ -315,20 +333,56 @@ int main() { } } - { - std::cout << "Coverage analysis\n"; - Slicer360ToPerspective slicer; + if(1){// Test Slicer360ToPerspective + if(0) { std::cout << "Coverage analysis\n"; + Slicer360ToPerspective slicer; - double fov = 90.0*DEG2RAD; - int width = 500; - slicer.cameras.push_back(Camera().SetFOV(fov).SetTarget(Eigen::Vector3d(1., 0., 0.))); - slicer.cameras.push_back(Camera().SetFOV(fov).SetTarget(Eigen::Vector3d(-1., -1., -1.))); - slicer.cameras.push_back(Camera().SetFOV(fov/2).SetTarget(Eigen::Vector3d(1., 0.2, -0.3))); - Image coverage_raw = slicer.ComputeCoverage(width, true); - Image coverage_col = slicer.ComputeCoverage(width, false); + double fov = 90.0*DEG2RAD; + int width = 500; + slicer.cameras.push_back(Camera().SetFOV(fov).SetTarget(Eigen::Vector3d(1., 0., 0.))); + slicer.cameras.push_back(Camera().SetFOV(fov).SetTarget(Eigen::Vector3d(-1., -1., -1.))); + slicer.cameras.push_back(Camera().SetFOV(fov/2).SetTarget(Eigen::Vector3d(1., 0.2, -0.3))); + Image coverage_raw = slicer.ComputeCoverage(width, true); + Image coverage_col = slicer.ComputeCoverage(width, false); - coverage_raw.Save("coverage_raw.png"); - coverage_col.Save("coverage_col.png"); + coverage_raw.Save("coverage_raw.png"); + coverage_col.Save("coverage_col.png"); + } + + if(1){ std::cout << "Perspective Projection\n"; + double Mpx = 1e6; // Image size in Mpx + double ratio = 16./9.; // Image ratio + int width = sqrt(Mpx)*sqrt(ratio); + int height = sqrt(Mpx)/sqrt(ratio); + + Slicer360ToPerspective slicer = Slicer360ToPerspective(Image("../venise.jpg")).SetOutputImageSize(width, height).SetInterpolationMethod(Image::InterpMethod::BILINEAR); + Camera camera = Camera().SetFOV(90.0*DEG2RAD).SetTarget(Eigen::Vector3d(1., 0., 0.)); + slicer.cameras.push_back(camera); + + slicer.ProjectToCamera(camera, true).Save("venise_proj_01.png"); + + // measure performance + double t0, t1; + t0 = omp_get_wtime(); + slicer.ProjectToCamera(camera, false); + t1 = omp_get_wtime(); + std::cout << "Projection time single-threaded BILINEAR : " << t1-t0 << " s\n"; + t0 = omp_get_wtime(); + slicer.ProjectToCamera(camera, true); + t1 = omp_get_wtime(); + std::cout << "Projection time multi-threaded BILINEAR : " << t1-t0 << " s\n"; + + slicer.SetInterpolationMethod(Image::InterpMethod::NEAREST); + + t0 = omp_get_wtime(); + slicer.ProjectToCamera(camera, false); + t1 = omp_get_wtime(); + std::cout << "Projection time single-threaded NEAREST : " << t1-t0 << " s\n"; + t0 = omp_get_wtime(); + slicer.ProjectToCamera(camera, true); + t1 = omp_get_wtime(); + std::cout << "Projection time multi-threaded NEAREST : " << t1-t0 << " s\n"; + } } PRINT_TESTS_SUMMARY();