From 71a78e12e40e19c91c57cd3449faed3acb869cfb Mon Sep 17 00:00:00 2001 From: Jerome Date: Wed, 3 May 2023 22:58:34 +0200 Subject: [PATCH] Added CSV parser and Camera loading from decoded CSV. --- cpp/include/Camera.hpp | 7 ++++ cpp/include/Slicer360ToPerspective.hpp | 10 ++++++ cpp/include/csv_parser.hpp | 25 ++++++++++++++ cpp/src/Camera.cpp | 11 +++++- cpp/src/Slicer360ToPerspective.cpp | 20 +++++++++++ cpp/src/csv_parser.cpp | 46 ++++++++++++++++++++++++++ cpp/src/tests.cpp | 37 ++++++++++++++++++++- 7 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 cpp/include/csv_parser.hpp create mode 100644 cpp/src/csv_parser.cpp diff --git a/cpp/include/Camera.hpp b/cpp/include/Camera.hpp index 6c7fd49..58af01d 100644 --- a/cpp/include/Camera.hpp +++ b/cpp/include/Camera.hpp @@ -23,6 +23,8 @@ class Camera { Camera & SetForward(Eigen::Vector3d const& fwd_); Camera & SetUp(Eigen::Vector3d const& up_); Camera & SetR(Eigen::Matrix3d const& R_); + + /// @brief Sets the field of view of the camera, in radians. Camera & SetFOV(double fov); // Methods @@ -68,6 +70,11 @@ class Camera { // Static methods /// @brief Converts pixel coordinates to normalized coordinates. i is the row (y), j is the column (x). Normalized coordinates range from -1 to 1. static Eigen::Vector2d PixelToNormalizedCoordinates(unsigned int i, unsigned int j, unsigned int width, unsigned int height); + + /// @brief Creates a Camera object from a vector of yaw, pitch, roll and FOV, read from a CSV file. + /// @param yaw_pitch_roll_fov 4-D Vector containing yaw, pitch, roll and FOV as double-precision floating point numbers. + /// @return A Camera object with the specified parameters. + static Camera FromYawPitchRollFOV(std::vector const& yaw_pitch_roll_fov); private: Eigen::Vector3d fwd; //!< Forward vector in inertial frame diff --git a/cpp/include/Slicer360ToPerspective.hpp b/cpp/include/Slicer360ToPerspective.hpp index 71a0b4e..8f84abf 100644 --- a/cpp/include/Slicer360ToPerspective.hpp +++ b/cpp/include/Slicer360ToPerspective.hpp @@ -54,6 +54,16 @@ class Slicer360ToPerspective { /// @return A vector of generated perspective images. std::vector ProjectToAllCameras(bool parallel = true) const; + /// @brief Loads the cameras from a CSV file. + /// @details The CSV file should contain one camera per row and the columns should be in the following order: + /// - Yaw (degrees) + /// - Pitch (degrees) + /// - Roll (degrees) + /// - FOV (degrees) + /// @param filename File path of the CSV file. + /// @return A reference to the current object. + Slicer360ToPerspective & LoadCamerasFromFile(std::string const& filename); + std::vector cameras;//!< A vector of cameras that are used to convert the 360-degree image to perspective images. private: diff --git a/cpp/include/csv_parser.hpp b/cpp/include/csv_parser.hpp new file mode 100644 index 0000000..cd10db1 --- /dev/null +++ b/cpp/include/csv_parser.hpp @@ -0,0 +1,25 @@ +#ifndef H_CSVparser +#define H_CSVparser + +#include +#include + +/** + * @brief Parses a CSV file containing floating-point numbers. + * + * @param filename The name of the CSV file to parse. + * @return A vector of vectors of doubles containing the parsed data. + * Returns an empty vector if there was an error. + * + * This function reads a CSV file containing floating-point numbers and + * returns the data as a vector of vectors of doubles. The CSV file should + * use commas as delimiters and should not contain any quotes or other + * special characters. Each row in the CSV file should have the same number + * of columns. If there is an error opening the file or parsing the data, + * this function returns an empty vector and prints an error message to + * standard error. The error message will indicate the type of error and + * the name of the file where the error occurred. + */ +std::vector> parse_csv_file(const std::string& filename, char delimiter = ','); + +#endif diff --git a/cpp/src/Camera.cpp b/cpp/src/Camera.cpp index 79abee8..04f266d 100644 --- a/cpp/src/Camera.cpp +++ b/cpp/src/Camera.cpp @@ -1,4 +1,5 @@ #include +#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)) @@ -122,4 +123,12 @@ bool Camera::IsPointVisibleInertialFrame(Eigen::Vector3d const& p_inertial) cons Eigen::Vector2d Camera::PixelToNormalizedCoordinates(unsigned int i, unsigned int j, unsigned int width, unsigned int height) { return Eigen::Vector2d(2.0*((double)j)/(width - 1) - 1.0, -2.0*((double)i)/(height - 1) + 1.0); -} \ No newline at end of file +} + +Camera Camera::FromYawPitchRollFOV(std::vector const& yaw_pitch_roll_fov) { + if(yaw_pitch_roll_fov.size() != 4) { + std::cerr << "Error: Camera::FromYawPitchRollFOV: yaw_pitch_roll_fov must be a vector of size 4." << std::endl; + return Camera(); + } + return Camera().SetEulerAngles(yaw_pitch_roll_fov[0], yaw_pitch_roll_fov[1], yaw_pitch_roll_fov[2]).SetFOV(yaw_pitch_roll_fov[3]*DEG2RAD); +} diff --git a/cpp/src/Slicer360ToPerspective.cpp b/cpp/src/Slicer360ToPerspective.cpp index 768dca5..5b457af 100644 --- a/cpp/src/Slicer360ToPerspective.cpp +++ b/cpp/src/Slicer360ToPerspective.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #ifndef DO_NOT_USE_OPENMP #include @@ -99,3 +101,21 @@ std::vector Slicer360ToPerspective::ProjectToCameras(std::vector std::vector Slicer360ToPerspective::ProjectToAllCameras(bool parallel) const { return ProjectToCameras(cameras, parallel); } + +Slicer360ToPerspective & Slicer360ToPerspective::LoadCamerasFromFile(std::string const& filename) { + std::vector> camera_specs = parse_csv_file(filename); + if(camera_specs.size() == 0) { + std::cerr << "Error: no camera specification found in file " << filename << std::endl; + return *this; + } else if(camera_specs[0].size() < 4) { + std::cerr << "Error: camera specification in file " << filename << " must have 4 columns : yaw, pitch, roll, FOV (all in degrees)." << std::endl; + return *this; + } else if(camera_specs[0].size() > 4) { + std::cerr << "Warning: camera specification in file " << filename << " has more than 4 columns. Only the first 4 columns will be used." << std::endl; + } + // Load cameras from decoded CSV file + cameras.clear(); + for(auto& camera_spec : camera_specs) + cameras.push_back(Camera::FromYawPitchRollFOV(camera_spec)); + return *this; +} diff --git a/cpp/src/csv_parser.cpp b/cpp/src/csv_parser.cpp new file mode 100644 index 0000000..2a17b80 --- /dev/null +++ b/cpp/src/csv_parser.cpp @@ -0,0 +1,46 @@ +#include + +#include +#include +#include + +std::vector> parse_csv_file(const std::string& filename, char delimiter) +{ + // Open file for reading + std::ifstream file(filename); + if (!file.is_open()) { + std::cerr << "Error: could not open file " << filename << std::endl; + return {}; + } + + // Read file line by line + std::vector> data; + std::string line; + while (std::getline(file, line)) { + // Parse line into a vector of doubles + std::vector row; + std::istringstream iss(line); + std::string token; + while (std::getline(iss, token, delimiter)) { + try { + double value = std::stod(token); + row.push_back(value); + } catch (const std::invalid_argument&) { + std::cerr << "Error: invalid floating-point number \"" << token << "\" in file " << filename << std::endl; + return {}; + } + } + data.push_back(row); + } + + // Check that all rows have the same size + const size_t num_columns = data[0].size(); + for (const auto& row : data) { + if (row.size() != num_columns) { + std::cerr << "Error: inconsistent number of columns in file " << filename << std::endl; + return {}; + } + } + + return data; +} diff --git a/cpp/src/tests.cpp b/cpp/src/tests.cpp index 0bc6940..6174e36 100644 --- a/cpp/src/tests.cpp +++ b/cpp/src/tests.cpp @@ -3,7 +3,8 @@ #include #include #include -#include "../include/parser.hpp" +#include +#include #include @@ -254,6 +255,14 @@ int main(int argc, char** argv) { EXPECT_EQ(Camera().SetEulerAngles(45.0, 0.0, 0.0).GetR(), Camera().SetEulerAngles(45.0*DEG2RAD, 0.0, 0.0, true).GetR());// input in radians vs degrees } + {// Test Camera creation from CSV line + std::cout << "Creation from CSV line\n"; + std::vector yaw_pitch_roll_FOV = {45.0, 30.0, -15.0, 90.0}; + Camera camera = Camera::FromYawPitchRollFOV(yaw_pitch_roll_FOV); + + EXPECT_EQ(camera.GetFOV(), yaw_pitch_roll_FOV[3]*DEG2RAD); + EXPECT_EQ(camera.GetR(), Camera().SetEulerAngles(yaw_pitch_roll_FOV[0], yaw_pitch_roll_FOV[1], yaw_pitch_roll_FOV[2]).GetR()); + } } if(0){// Image @@ -455,6 +464,32 @@ int main(int argc, char** argv) { } } + if(1){// CSV parser + std::cout << "CSV parser\n"; + { std::cout << "Valid CSV file\n"; + auto data = parse_csv_file("../data_test_valid.csv"); + EXPECT_EQ(data.empty(), false); + for(auto& row : data) { + for(auto& cell : row) { + std::cout << cell << "\t"; + } + std::cout << std::endl; + } + } + { std::cout << "Invalid CSV file (missing column on one row)\n"; + auto data = parse_csv_file("../data_test_invalid.csv"); + EXPECT_EQ(data.empty(), true); + } + { std::cout << "Invalid CSV file (bad floating-point number)\n"; + auto data = parse_csv_file("../data_test_invalid_floating_point.csv"); + EXPECT_EQ(data.empty(), true); + } + { std::cout << "Non-existent CSV file\n"; + auto data = parse_csv_file("does_not_exist.csv"); + EXPECT_EQ(data.empty(), true); + } + } + PRINT_TESTS_SUMMARY(); return 0;