Added CSV parser and Camera loading from decoded CSV.

This commit is contained in:
Jerome 2023-05-03 22:58:34 +02:00
parent e20c153f1e
commit 71a78e12e4
7 changed files with 154 additions and 2 deletions

View file

@ -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<double> const& yaw_pitch_roll_fov);
private:
Eigen::Vector3d fwd; //!< Forward vector in inertial frame

View file

@ -54,6 +54,16 @@ class Slicer360ToPerspective {
/// @return A vector of generated perspective images.
std::vector<Image> 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<Camera> cameras;//!< A vector of cameras that are used to convert the 360-degree image to perspective images.
private:

View file

@ -0,0 +1,25 @@
#ifndef H_CSVparser
#define H_CSVparser
#include <vector>
#include <string>
/**
* @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<std::vector<double>> parse_csv_file(const std::string& filename, char delimiter = ',');
#endif

View file

@ -1,4 +1,5 @@
#include <Camera.hpp>
#include <iostream>
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);
}
}
Camera Camera::FromYawPitchRollFOV(std::vector<double> 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);
}

View file

@ -1,5 +1,7 @@
#include <Slicer360ToPerspective.hpp>
#include <frame_conversions.hpp>
#include <csv_parser.hpp>
#include <iostream>
#ifndef DO_NOT_USE_OPENMP
#include <omp.h>
@ -99,3 +101,21 @@ std::vector<Image> Slicer360ToPerspective::ProjectToCameras(std::vector<Camera>
std::vector<Image> Slicer360ToPerspective::ProjectToAllCameras(bool parallel) const {
return ProjectToCameras(cameras, parallel);
}
Slicer360ToPerspective & Slicer360ToPerspective::LoadCamerasFromFile(std::string const& filename) {
std::vector<std::vector<double>> 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;
}

46
cpp/src/csv_parser.cpp Normal file
View file

@ -0,0 +1,46 @@
#include <csv_parser.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
std::vector<std::vector<double>> 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<std::vector<double>> data;
std::string line;
while (std::getline(file, line)) {
// Parse line into a vector of doubles
std::vector<double> 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;
}

View file

@ -3,7 +3,8 @@
#include <Image.hpp>
#include <Slicer360ToPerspective.hpp>
#include <frame_conversions.hpp>
#include "../include/parser.hpp"
#include <parser.hpp>
#include <csv_parser.hpp>
#include <test_framework.hpp>
@ -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<double> 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;