diff --git a/cgogn/io/surface/obj.h b/cgogn/io/surface/obj.h index 2598914e0..89fa75a0b 100644 --- a/cgogn/io/surface/obj.h +++ b/cgogn/io/surface/obj.h @@ -27,6 +27,9 @@ #include #include +#include +#include + #include using namespace std::literals::string_literals; @@ -335,14 +338,70 @@ bool import_OBJ_tn(MESH& m_p, MESH& m_tc, MESH& m_n, const std::string& filename return true; } - +// Changes connectivity between obj files but when you load again the file the new connectivity works +// It works but it also kinda doesn't template -void export_OBJ(MESH& m, const typename mesh_traits::template Attribute* vertex_position, - const std::string& filename) +void export_OBJ(MESH& m_p, MESH& m_no, MESH& m_tc, const typename mesh_traits::template Attribute* vertex_position, + const typename mesh_traits::template Attribute* normal, + const typename mesh_traits::template Attribute* texture_coord, + const std::string& filename) { static_assert(mesh_traits::dimension == 2, "MESH dimension should be 2"); - // TODO + using Vertex = typename mesh_traits::Vertex; + using Face = typename mesh_traits::Face; + + using Vec3 = geometry::Vec3; + using Vec2 = geometry::Vec2; + + auto vertex_id = add_attribute(m_p, "__vertex_id"); + auto normal_id = add_attribute(m_no, "__normal_id"); + auto texture_id = add_attribute(m_tc, "__texture_id"); + + std::ofstream out_file; + out_file.open(filename); + out_file << "mtllib "<< filename.substr(0,filename.size() - 4) << ".mtl" << "\n"; + out_file << "g default\n"; + + uint32 id = 1; + foreach_cell(m_p, [&](Vertex v) -> bool { + const geometry::Vec3& p = value(m_p, vertex_position, v); + value(m_p, vertex_id, v) = id++; + out_file << "v " << p[0] << " " << p[1] << " " << p[2] << "\n"; + return true; + }); + + id = 1; + foreach_cell(m_tc, [&](Vertex v) -> bool { + const geometry::Vec2& p = value(m_tc, texture_coord, v); + value(m_tc, texture_id, v) = id++; + out_file << "vt " << p[0] << " " << p[1] << "\n"; + return true; + }); + + id = 1; + foreach_cell(m_no, [&](Vertex v) -> bool { + const geometry::Vec3& p = value(m_no, normal, v); + value(m_no, normal_id, v) = id++; + out_file << "vn " << p[0] << " " << p[1] << " " << p[2] << "\n"; + return true; + }); + + foreach_cell(m_p, [&](Face f) -> bool { + out_file << "f"; + foreach_incident_vertex(m_p, f, [&](Vertex v) -> bool { + out_file << " " << value(m_p, vertex_id, v) << "/" << value(m_tc, texture_id, v) << "/" <(m_no, normal_id, v) ; + return true; + }); + out_file << "\n"; + return true; + }); + + remove_attribute(m_p, vertex_id); + remove_attribute(m_tc, texture_id); + remove_attribute(m_no, normal_id); + + out_file.close(); } } // namespace io diff --git a/cgogn/modeling/CMakeLists.txt b/cgogn/modeling/CMakeLists.txt index 056323290..585341520 100644 --- a/cgogn/modeling/CMakeLists.txt +++ b/cgogn/modeling/CMakeLists.txt @@ -47,6 +47,7 @@ set(src_list "${CMAKE_CURRENT_LIST_DIR}/ui_modules/tubular_mesh_module.h" "${CMAKE_CURRENT_LIST_DIR}/ui_modules/volume_deformation.h" "${CMAKE_CURRENT_LIST_DIR}/ui_modules/volume_mr_modeling.h" + "${CMAKE_CURRENT_LIST_DIR}/ui_modules/action_unit_change.h" ) target_sources(${PROJECT_NAME} PRIVATE ${src_list}) source_group(TREE ${CMAKE_CURRENT_LIST_DIR} FILES ${src_list}) diff --git a/cgogn/modeling/algos/blending.h b/cgogn/modeling/algos/blending.h new file mode 100644 index 000000000..ba606610a --- /dev/null +++ b/cgogn/modeling/algos/blending.h @@ -0,0 +1,141 @@ +/******************************************************************************* + * CGoGN: Combinatorial and Geometric modeling with Generic N-dimensional Maps * + * Copyright (C), IGG Group, ICube, University of Strasbourg, France * + * * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by the * + * Free Software Foundation; either version 2.1 of the License, or (at your * + * option) any later version. * + * * + * This library is distributed in the hope that it will be useful, but WITHOUT * + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * + * for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this library; if not, write to the Free Software Foundation, * + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + * * + * Web site: http://cgogn.unistra.fr/ * + * Contact information: cgogn@unistra.fr * + * * + *******************************************************************************/ + +#ifndef CGOGN_BLENDING_H_ +#define CGOGN_BLENDING_H_ + +#include +#include +#include + +namespace cgogn +{ + +namespace modeling +{ + +using geometry::Vec3; + +static bool ends_with(const std::string& str, const std::string& suffix) +{ + return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +template +// Blending function +// Currently it's a sum of vectors of each different attributes that will be blent +void blending(MESH& m, std::vector::template Attribute>> attributes_to_blend, std::vector weight_list , std::string name_attribute_to_change) +{ + using Vertex = typename mesh_traits::Vertex; + std::shared_ptr::template Attribute> vertex_position = cgogn::get_attribute(m, name_attribute_to_change); + std::shared_ptr::template Attribute> repos_position = cgogn::get_attribute(m, "AU00"); + typename mesh_traits::template Attribute* new_vertex_pos_value = vertex_position.get(); + Vec3 diff_distance_repos = Vec3(0, 0, 0); + float epsilon = 0.0001; + + // need to check if parallel_foreach_cell messes with the calculations + foreach_cell(m, [&](Vertex v) -> bool { + value(m, vertex_position, v) = value(m, repos_position, v); + Vec3 result = Vec3(0, 0, 0); + //float nb_au_influence = 0.; + + for (int i = 0; i < attributes_to_blend.size(); i++) + { + diff_distance_repos = value(m, attributes_to_blend[i], v) - value(m, repos_position, v); + diff_distance_repos *= weight_list[i]; + + // if(abs(diff_distance_repos[0]) > 0. || abs(diff_distance_repos[1]) > 0. || abs(diff_distance_repos[2]) > 0.){ + // nb_au_influence++; + // } + result += diff_distance_repos; + } + + // if (nb_au_influence != 0.) + // result = result / nb_au_influence; + + result[0] = (abs(result[0]) > epsilon) ? result[0] : 0. ; + result[1] = (abs(result[1]) > epsilon) ? result[1] : 0. ; + result[2] = (abs(result[2]) > epsilon) ? result[2] : 0. ; + + value(m, vertex_position, v) += result ; + return true; + }); +} + +template +// Function called each frame after clicking on Apply CSV +// Setup the differents AUs and weights to calculate the frames +void blending_csv(MESH& m, int incr, float poids_frame , std::map> csv_ , std::string pos_attr_name, Eigen::MatrixXd csv_weights_detected) +{ + using Vertex = typename mesh_traits::Vertex; + + std::vector::template Attribute>> attributes_csv; + std::vector weight_list; + + for (auto& it : csv_) + { + if (ends_with(it.first, "_r")) + { + attributes_csv.push_back( + cgogn::get_attribute(m, it.first.substr(0, it.first.size() - 2))); + } + } + for (int i = 1; i < attributes_csv.size() + 1; i++) + { + float weight_attribute; + if (incr < 8) + { + weight_list.push_back((csv_weights_detected(incr, i - 1) * (1 - poids_frame)) + + (csv_weights_detected(incr + 1, i - 1) * poids_frame)); + } + else + { + float weight_smooth = 0; + int j = 1; + for(; j < 7 ; j++) + { + weight_smooth += csv_weights_detected(incr - j, i - 1); + } + + if (incr + 1 < csv_weights_detected.rows()) + { + weight_smooth += (csv_weights_detected(incr, i - 1) * (1 - poids_frame)) + (csv_weights_detected(incr + 1, i - 1) * poids_frame); + weight_smooth = weight_smooth/float(j); + weight_list.push_back(weight_smooth); + } + else + { + weight_smooth += (csv_weights_detected(incr - 1, i - 1) * (1 - poids_frame)) + (csv_weights_detected(incr, i - 1) * poids_frame); + weight_smooth = weight_smooth/float(j); + weight_list.push_back(weight_smooth); + } + } + } + modeling::blending(m, attributes_csv, weight_list , pos_attr_name); +} + +} // namespace modeling + +} // namespace cgogn + +#endif // CGOGN_BLENDING_H_ \ No newline at end of file diff --git a/cgogn/modeling/apps/CMakeLists.txt b/cgogn/modeling/apps/CMakeLists.txt index 46329e5a7..37b1afbba 100644 --- a/cgogn/modeling/apps/CMakeLists.txt +++ b/cgogn/modeling/apps/CMakeLists.txt @@ -78,6 +78,17 @@ target_link_libraries(convex_hull ${CMAKE_DL_LIBS} ) +add_executable(action_unit_switch action_unit_switch.cpp) +target_link_libraries(action_unit_switch + cgogn::core + cgogn::ui + cgogn::io + cgogn::rendering + cgogn::modeling + Blossom5 + ${CMAKE_DL_LIBS} +) + if(APPLE) find_library(CORE_FOUNDATION CoreFoundation) find_library(CARBON Carbon) @@ -107,4 +118,9 @@ if(APPLE) ${CORE_FOUNDATION} ${CARBON} ) + + target_link_libraries(action_unit_switch + ${CORE_FOUNDATION} + ${CARBON} +) endif() diff --git a/cgogn/modeling/apps/action_unit_switch.cpp b/cgogn/modeling/apps/action_unit_switch.cpp new file mode 100644 index 000000000..610b64a0e --- /dev/null +++ b/cgogn/modeling/apps/action_unit_switch.cpp @@ -0,0 +1,287 @@ +/******************************************************************************* + * CGoGN: Combinatorial and Geometric modeling with Generic N-dimensional Maps * + * Copyright (C), IGG Group, ICube, University of Strasbourg, France * + * * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by the * + * Free Software Foundation; either version 2.1 of the License, or (at your * + * option) any later version. * + * * + * This library is distributed in the hope that it will be useful, but WITHOUT * + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * + * for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this library; if not, write to the Free Software Foundation, * + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + * * + * Web site: http://cgogn.unistra.fr/ * + * Contact information: cgogn@unistra.fr * + * * + *******************************************************************************/ + +// #define USE_GMAP + +#ifdef USE_GMAP +#include +#else +#include +#endif + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +// #include + +#include +#include +#include + +#define DEFAULT_MESH_PATH CGOGN_STR(CGOGN_DATA_PATH) "meshes/" +#define DEFAULT_TEXTURE_PATH CGOGN_STR(CGOGN_DATA_PATH) "textures/" + +using namespace cgogn::numerics; + +#ifdef USE_GMAP +using Mesh = cgogn::GMap2; +#else +using Mesh = cgogn::CMap2; +#endif + +template +using Attribute = typename cgogn::mesh_traits::Attribute; + +int main(int argc, char** argv) +{ + + using std::chrono::high_resolution_clock; + using std::chrono::duration_cast; + using std::chrono::duration; + using std::chrono::milliseconds; + + using Vertex = typename cgogn::mesh_traits::Vertex; + using Edge = typename cgogn::mesh_traits::Edge; + using Face = typename cgogn::mesh_traits::Face; + + using Vec4 = cgogn::geometry::Vec4; + using Vec3 = cgogn::geometry::Vec3; + using Vec2 = cgogn::geometry::Vec2; + using Scalar = cgogn::geometry::Scalar; + + std::string dirname; + std::string path_openface; + if (argc < 5){ + std::cout << "How to launch : Folder with AUs (based on meshes path) , file with texture (based on texture path) , OpenFace path, file with texture normals (based on texture path)\n" << std::endl; + return 1; + } + else{ + dirname = std::string(DEFAULT_MESH_PATH) + std::string(argv[1]); + path_openface = std::string(argv[3]); + } + + + cgogn::thread_start(); + + cgogn::ui::App app; + app.set_window_title("Action Unit Change"); + app.set_window_size(1000, 800); + + cgogn::ui::MeshProvider mp(app); + cgogn::ui::ActionUnitChange auc(app); + cgogn::ui::SurfaceDifferentialProperties sdp(app); + cgogn::ui::SurfaceModeling sm(app); + //cgogn::ui::VectorPerVertexRender vpvr(app); + cgogn::ui::SurfaceObjRender sor(app); + + auc.set_directory(dirname); + auc.set_pathOpenface(path_openface); + + app.init_modules(); + + cgogn::ui::View* v1 = app.current_view(); + v1->link_module(&mp); + v1->link_module(&sor); + v1->link_module(&auc); + //v1->link_module(&vpvr); + + auto [m_pos,m_tc,m_no] = mp.load_surface_from_OBJ_file(dirname + std::string("AU00.obj")); + if (!m_pos) + { + std::cout << "Folder with files not found" << std::endl; + return 1; + } + + std::shared_ptr> vertex_position = cgogn::get_attribute(*m_pos, "position"); + std::shared_ptr> vertex_normal = cgogn::add_attribute(*m_pos, "normal"); + std::shared_ptr> vertex_position_interpolation = cgogn::add_attribute(*m_pos, "position_interpolation"); + std::shared_ptr> vertex_distance = cgogn::add_attribute(*m_pos, "distance"); + + auc.set_mesh(*m_pos,vertex_position); + auc.set_position_attr_name("position"); + auc.set_attribute(*m_pos,vertex_position.get(),"position_interpolation",1.); + auc.set_attribute(*m_pos,vertex_position.get(),"distance",1.); + + if (argc == 7) + { + std::string csv_path_video = argv[5]; + std::string path_video = argv[6]; + auc.exec_mode(true,csv_path_video,path_video); + } + + sdp.compute_normal(*m_pos, vertex_position.get(), vertex_normal.get()); + + mp.set_mesh_bb_vertex_position(*m_pos, vertex_position); + + auc.set_view(*v1); + auc.setup_mesh_attributes(); + //auc.setup_csv_matrix(); + + if (argc <= 2) + { + cgogn::rendering::GLImage img(16, 16, 3); + std::vector> pix; + pix.reserve(16*16); + for (int i = 0; i < 16; ++i) + for (int j = 0; j < 16; ++j) + if ((i + j) % 2 == 0) + pix.push_back({0u, 0u, 0u}); + else + pix.push_back({255u, 255u, 255u}); + img.copy_pixels_data(pix.data()->data()); + sor.load_texture(img); + } + else + { + sor.load_texture(std::string(DEFAULT_TEXTURE_PATH) + std::string(argv[2])); + sor.load_texture_norm(std::string(DEFAULT_TEXTURE_PATH) + std::string(argv[4])); + } + + + std::vector args(argv, argv+argc); + for (size_t i = 1; i < args.size(); ++i) { + if (args[i] == "-perf") { + std::ofstream perfFile("performance.txt"); + if (perfFile.is_open()) + { + int nb_iter = 5000; + std::vector>> attribute_to_blend_; + std::vector weight_list; + + perfFile << "Performance tests for blending\n\n"; + + for (int j = 1; j < auc.pos_aus_.size() ; j++) + { + attribute_to_blend_.push_back(auc.pos_aus_[j]); + + auto t1 = high_resolution_clock::now(); + for (int k = 0; k < nb_iter; k++) + { + for (int z = 0; z < attribute_to_blend_.size(); z++) + { + int weight = (std::rand()%5) + 1; + weight_list.push_back(weight); + } + auc.blending(*m_pos,attribute_to_blend_,weight_list); + weight_list.clear(); + } + auto t2 = high_resolution_clock::now(); + + duration ms_double = t2 - t1; + + perfFile << ms_double.count() << "ms for " << nb_iter << " blendings with " << j << " AU with weight change for each AUs\n"; + perfFile << ms_double.count() / float(nb_iter) << "ms per operation for blending with " << j << " AU with weight change for each AUs\n"; + } + + perfFile << "\n\n"; + + attribute_to_blend_.clear(); + weight_list.clear(); + + for (int j = 1; j < auc.pos_aus_.size() ; j++) + { + attribute_to_blend_.push_back(auc.pos_aus_[j]); + weight_list.push_back((std::rand()%5) + 1); + + auto t1 = high_resolution_clock::now(); + for (int k = 0; k < nb_iter; k++) + { + auc.blending(*m_pos,attribute_to_blend_,weight_list); + } + auto t2 = high_resolution_clock::now(); + + duration ms_double = t2 - t1; + + perfFile << ms_double.count() << "ms for " << nb_iter << " blendings with " << j << " AU\n"; + perfFile << ms_double.count() / float(nb_iter) << "ms per operation for blending with " << j << " AU\n"; + + } + + perfFile << "\n\n"; + + attribute_to_blend_.clear(); + weight_list.clear(); + + for (int j = 1; j < auc.pos_aus_.size() ; j++) + { + attribute_to_blend_.push_back(auc.pos_aus_[j]); + auto t1 = high_resolution_clock::now(); + for (int k = 0; k < nb_iter; k++) + { + for (int z = 0; z < attribute_to_blend_.size(); z++) + { + int weight = (std::rand()%5) + 1; + weight_list.push_back(weight); + } + cgogn::modeling::blending(*m_pos,attribute_to_blend_,weight_list,"position"); + weight_list.clear(); + } + auto t2 = high_resolution_clock::now(); + + duration ms_double = t2 - t1; + + perfFile << ms_double.count() << "ms for " << nb_iter << " blendings with modeling::blending with " << j << " AU weight change for each AUs\n"; + perfFile << ms_double.count() / float(nb_iter) << "ms per operation for blending with modeling::blending with " << j << " AU weight change for each AUs\n"; + } + + perfFile << "\n\n"; + + attribute_to_blend_.clear(); + weight_list.clear(); + + for (int j = 1; j < auc.pos_aus_.size() ; j++) + { + attribute_to_blend_.push_back(auc.pos_aus_[j]); + weight_list.push_back(rand()%5 + 1); + auto t1 = high_resolution_clock::now(); + for (int k = 0; k < nb_iter; k++) + { + cgogn::modeling::blending(*m_pos,attribute_to_blend_,weight_list,"position"); + } + auto t2 = high_resolution_clock::now(); + + duration ms_double = t2 - t1; + + perfFile << ms_double.count() << "ms for " << nb_iter << " blendings with modeling::blending with " << j << " AU\n"; + perfFile << ms_double.count() / float(nb_iter) << "ms per operation for blending with modeling::blending with " << j << " AU\n"; + } + } + perfFile.close(); + } + } + + return app.launch(); +} diff --git a/cgogn/modeling/ui_modules/action_unit_change.h b/cgogn/modeling/ui_modules/action_unit_change.h new file mode 100644 index 000000000..fde68874d --- /dev/null +++ b/cgogn/modeling/ui_modules/action_unit_change.h @@ -0,0 +1,1368 @@ +/******************************************************************************* + * CGoGN * + * Copyright (C), IGG Group, ICube, University of Strasbourg, France * + * * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by the * + * Free Software Foundation; either version 2.1 of the License, or (at your * + * option) any later version. * + * * + * This library is distributed in the hope that it will be useful, but WITHOUT * + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * + * for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this library; if not, write to the Free Software Foundation, * + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + * * + * Web site: http://cgogn.unistra.fr/ * + * Contact information: cgogn@unistra.fr * + * * + *******************************************************************************/ + +#ifndef CGOGN_ACTION_UNIT_CHANGE_H_ +#define CGOGN_ACTION_UNIT_CHANGE_H_ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_PATH CGOGN_STR(CGOGN_DATA_PATH) "../../" + +namespace fs = std::filesystem; + +namespace cgogn +{ + +namespace ui +{ + +using geometry::Scalar; +using geometry::Vec3; +using geometry::Vec3f; +using geometry::Vec4; +using GLMat4 = Eigen::Matrix4f; + +const Vec3 GREEN = Vec3(0, 128, 0); +const Vec3 BLUE = Vec3(0, 0, 255); + +template +class ActionUnitChange : public ViewModule +{ + static_assert(mesh_traits::dimension >= 2, "ActionUnitChange can only be used with meshes of dimension >= 2"); + + template + using Attribute = typename mesh_traits::template Attribute; + + using Vertex = typename mesh_traits::Vertex; + using Edge = typename mesh_traits::Edge; + using Face = typename mesh_traits::Face; + +public: + ActionUnitChange(const App& app) + : ViewModule(app, "ActionUnitChange (" + std::string{mesh_traits::name} + ")"), + selected_view_(app.current_view()), selected_mesh_(nullptr), selected_vertex_position_(nullptr) + { + } + ~ActionUnitChange() + { + } + + std::vector>> pos_aus_; + +public: + static bool ends_with(const std::string& str, const std::string& suffix) + { + return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; + } + + // + void exec_mode(bool use_exec_mode , std::string name_csv_to_use, std::string path_video) + { + use_exec_mode_ = use_exec_mode; + exec_mode_ = use_exec_mode; + exec_csv_name = name_csv_to_use; + path_video_ = path_video; + } + + // call this function after you initialized the module + void set_directory(std::string dirname) + { + directory_ = dirname; + } + + // call this function after you initialized the module + void set_pathOpenface(std::string path_openface) + { + path_openface_ = path_openface; + } + + // No signal system , call this function after you initialized the module + void set_mesh(MESH& m, std::shared_ptr> vertex_position) + { + selected_mesh_ = &m; + selected_vertex_position_ = vertex_position; + } + + // No signal system , call this function after you initialized the module + void set_view(View& v) + { + selected_view_ = &v; + } + + // No signal system , call this function after you initialized the module + void set_position_attr_name(std::string name) + { + pos_attr_name = name; + } + + // Put inside a vector all the files with an extension ext + void set_all_paths(std::string root, std::string ext, std::vector& paths) + { + for (auto& p : fs::recursive_directory_iterator(root)) + { + if (p.path().extension() == ext) + paths.push_back(p.path().string()); + } + std::sort(paths.begin(), paths.end()); + } + + // Function for taking a screenshot of the face to send to OpenFace for analysis + void take_screenshot(int num, std::string dir_of_name) + { + std::ostringstream name; + name << DEFAULT_PATH << "CGoGN_3/build/stage/bin/"; + name << "Screenshot_"; + for (int j = 0; j < 4 - std::to_string(num).size(); j++) + { + name << "0"; + } + name << num; + name << ".jpg"; + + std::ostringstream dirname; + dirname << path_openface_ << "samples/" << dir_of_name << "/"; + + selected_view_->save_screenshot_name(name.str()); + fs::path sourceFile = name.str().c_str(); + fs::path targetParent = dirname.str().c_str(); + if (!fs::is_directory(targetParent) || !fs::exists(targetParent)) + fs::create_directory(targetParent); + + fs::copy(sourceFile, targetParent, fs::copy_options::overwrite_existing); + } + + // Generate scripts needed to create the jacobian matrix and to get the landmarks position at OpenFace emplacement + void generate_scripts(){ + std::ofstream outputFile("csv_script_matrix.sh"); + if (outputFile.is_open()) + { + outputFile << "#!/bin/bash\n\n"; + outputFile << "FDIR=$1\n"; + outputFile << "OUTDIR=$2\n"; + outputFile << "EXECDIR=$3\n"; + outputFile << "ERASE_PYTHON=$4\n"; + outputFile << "PATH_PYTHON=$5\n\n"; + + outputFile << "${EXECDIR}FeatureExtraction -aus -out_dir ${OUTDIR} -fdir ${FDIR}\n\n"; + + outputFile << "if [ \"$ERASE_PYTHON\" == \"-erase\" ]\n"; + outputFile << "then\n"; + outputFile << " rm -rf ${FDIR}*.jpg\n"; + outputFile << "fi\n\n"; + + outputFile << "if [ \"$ERASE_PYTHON\" == \"-python\" ]\n"; + outputFile << "then\n"; + outputFile << " python3 ${PATH_PYTHON} ${OUTDIR}\n"; + outputFile << " #rm -rf ${FDIR}*.jpg\n"; + outputFile << "fi\n"; + } + outputFile.close(); + + std::ostringstream command; + command << "cp csv_script_matrix.sh " << path_openface_ << "build/bin/"; + + if (system(command.str().c_str()) == 0) + { + std::cout << "Script generated for csv_analysis" << std::endl; + command.str(""); + command.clear(); + + command << "chmod +x " << path_openface_ << "build/bin/csv_script_matrix.sh"; + if (system(command.str().c_str()) != 0) + std::cout << "Can't execute the script" << std::endl; + } + + std::ofstream blendingFile("progressive_blending.sh"); + if (blendingFile.is_open()) + { + blendingFile << "#!/bin/bash\n\n"; + blendingFile << "FDIR=$1\n"; + blendingFile << "OUTDIR=$2\n"; + blendingFile << "EXECDIR=$3\n"; + blendingFile << "ERASE_PYTHON=$4\n"; + blendingFile << "PATH_PYTHON=$5\n"; + blendingFile << "DYNAMIC=$6\n"; + blendingFile << "DIR_SLOPE=$7\n"; + + blendingFile << "AU=(\"AU01\" \"AU02\" \"AU04\" \"AU05\" \"AU06\" \"AU07\" \"AU09\" \"AU10\" \"AU12\" \"AU14\" \"AU15\" \"AU17\" \"AU20\" \"AU23\" \"AU25\" \"AU26\" \"AU45\") \n"; + + blendingFile << "if [ \"$DYNAMIC\" == \"-dynamic\" ]\n"; + blendingFile << "then\n"; + blendingFile << " for au in \"${AU[@]}\"; do \n"; + blendingFile << " tmp=$au\n"; + blendingFile << " ${EXECDIR}FeatureExtraction -aus -out_dir ${OUTDIR} -fdir ${FDIR}${tmp}/ -of ${tmp}\n\n"; + blendingFile << " ${EXECDIR}FeatureExtraction -aus -au_static -out_dir ${OUTDIR} -fdir ${FDIR}${tmp}/ -of ${tmp}_static\n\n"; + blendingFile << " done\n"; + blendingFile << "else\n"; + blendingFile << " for au in \"${AU[@]}\"; do \n"; + blendingFile << " tmp=$au\n"; + blendingFile << " ${EXECDIR}FeatureExtraction -aus -au_static -out_dir ${OUTDIR} -fdir ${FDIR}${tmp}/ -of ${tmp}\n\n"; + blendingFile << " echo ${OUTDIR}\n\n"; + blendingFile << " done\n"; + blendingFile << "fi\n"; + + blendingFile << "if [ \"$ERASE_PYTHON\" == \"-erase\" ]\n"; + blendingFile << "then\n"; + blendingFile << " rm -rf ${FDIR}*.jpg\n"; + blendingFile << "fi\n\n"; + + blendingFile << "if [ \"$ERASE_PYTHON\" == \"-python\" ]\n"; + blendingFile << "then\n"; + blendingFile << " python3 ${PATH_PYTHON} ${OUTDIR} ${DIR_SLOPE}\n"; + blendingFile << " rm -rf ${FDIR}*.jpg\n"; + blendingFile << " rm -rf *.jpg\n"; + blendingFile << "fi\n"; + } + blendingFile.close(); + + command.str(""); + command.clear(); + command << "cp progressive_blending.sh " << path_openface_ << "build/bin/"; + + + if (system(command.str().c_str()) == 0) + { + std::cout << "Script generated for progressive_blending" << std::endl; + command.str(""); + command.clear(); + + command << "chmod +x " << path_openface_ << "build/bin/progressive_blending.sh"; + if (system(command.str().c_str()) != 0) + std::cout << "Can't execute the script" << std::endl; + } + + std::ofstream landmarkFile("landmark_script.sh"); + if (landmarkFile.is_open()) + { + landmarkFile << "#!/bin/bash\n\n"; + landmarkFile << "FDIR=$1\n"; + landmarkFile << "OUTDIR=$2\n"; + landmarkFile << "EXECDIR=$3\n"; + landmarkFile << "ERASE_PYTHON=$4\n"; + landmarkFile << "PATH_PYTHON=$5\n\n"; + + landmarkFile << "${EXECDIR}FaceLandmarkImg -fdir ${FDIR} -out_dir ${OUTDIR} \n\n"; + + landmarkFile << "if [ \"$ERASE_PYTHON\" == \"-erase\" ]\n"; + landmarkFile << "then\n"; + landmarkFile << " rm -rf ${FDIR}*.jpg\n"; + landmarkFile << "fi\n\n"; + + landmarkFile << "if [ \"$ERASE_PYTHON\" == \"-python\" ]\n"; + landmarkFile << "then\n"; + landmarkFile << " python3 ${PATH_PYTHON} ${OUTDIR}\n"; + landmarkFile << " #rm -rf ${FDIR}*.jpg\n"; + landmarkFile << "fi\n"; + } + landmarkFile.close(); + command.str(""); + command.clear(); + command << "cp landmark_script.sh " << path_openface_ << "build/bin/"; + + if (system(command.str().c_str()) == 0) + { + std::cout << "Script generated for Landmarks" << std::endl; + command.str(""); + command.clear(); + command << "chmod +x " << path_openface_ << "build/bin/landmark_script.sh"; + if (system(command.str().c_str()) != 0) + std::cout << "Can't execute the script" << std::endl; + } + } + + // Create a new attribute or get an attribute and fill it with data from another attribute + void set_attribute(MESH& m, Attribute* to_set, std::string attribute_name, float weight) + { + std::shared_ptr> attribute_to_change = + cgogn::get_or_add_attribute(m, attribute_name.c_str()); + parallel_foreach_cell(m, [&](Vertex v) -> bool { + value(m, attribute_to_change, v) = value(m, to_set, v) * weight; + return true; + }); + } + + void change_to_selected_au(MESH& m, Attribute* au_position) + { + std::shared_ptr> vertex_position = cgogn::get_attribute(m, "position"); + Attribute* vertex_pos_value = vertex_position.get(); + Vec3 tmp = Vec3(0,0,0); + parallel_foreach_cell(m, [&](Vertex v) -> bool { + value(m, vertex_pos_value, v) = value(m, au_position, v); + tmp = value(m, au_position, v); + if (tmp[2] > center_of_face[2]) + { + center_of_face = tmp; + } + return true; + }); + mesh_provider_->emit_attribute_changed(m, vertex_pos_value); + } + + // Blending function + // Currently it's a sum of vectors of each different attributes that will be blent + // Prefer using the one in module + void blending(MESH& m, std::vector>> attributes_to_blend, std::vector weight_list) + { + std::shared_ptr> vertex_position = cgogn::get_attribute(m, "position"); + std::shared_ptr> color = cgogn::get_attribute(m, "color"); + std::shared_ptr> repos_position = cgogn::get_attribute(m, "AU00"); + Attribute* new_vertex_pos_value = vertex_position.get(); + Vec3 diff_distance_repos = Vec3(0, 0, 0); + float epsilon = 0.0001; + + // need to check if parallel_foreach_cell messes with the calculations + foreach_cell(m, [&](Vertex v) -> bool { + value(m, vertex_position, v) = value(m, repos_position, v); + Vec3 result = Vec3(0, 0, 0); + // float nb_au_influence = 0.; + + for (int i = 0; i < attributes_to_blend.size(); i++) + { + diff_distance_repos = value(m, attributes_to_blend[i], v) - value(m, repos_position, v); + + // if(abs(diff_distance_repos[0]) > 0. || abs(diff_distance_repos[1]) > 0. || abs(diff_distance_repos[2]) > 0.){ + // nb_au_influence++; + // } + result += diff_distance_repos * weight_list[i]; + } + + result[0] = (abs(result[0]) > epsilon) ? result[0] : 0. ; + result[1] = (abs(result[1]) > epsilon) ? result[1] : 0. ; + result[2] = (abs(result[2]) > epsilon) ? result[2] : 0. ; + + value(m, vertex_position, v) += result ; + return true; + }); + + mesh_provider_->emit_attribute_changed(m, new_vertex_pos_value); + } + + // Compute the distance between points in the starting configuration and the end configuration for the interpolation + // algorithm + void set_distance(MESH& m, Attribute* blendshape_start, Attribute* blendshape_target, + float weight_start, float weight_target) + { + std::shared_ptr> distance = cgogn::get_or_add_attribute(m, "distance"); + std::shared_ptr> repos_position = cgogn::get_attribute(m, "AU00"); + Attribute* distance_value = distance.get(); + Vec3 diff_distance_repos = Vec3(0, 0, 0); + parallel_foreach_cell(m, [&](Vertex v) -> bool { + diff_distance_repos = + ((value(m, blendshape_target, v) - value(m, repos_position, v)) * weight_target) - + ((value(m, blendshape_start, v) - value(m, repos_position, v)) * weight_start); + value(m, distance_value, v) = diff_distance_repos; + return true; + }); + } + + // Interpolation function with a step + // Call set_distance before this function + // Switch attribute to position_interpolation to watch the interpolation + void interpolation(MESH& m, float pas) + { + std::shared_ptr> distance = cgogn::get_or_add_attribute(m, "distance"); + std::shared_ptr> position_interpolation = + cgogn::get_or_add_attribute(m, "position_interpolation"); + Attribute* interpolation_value = position_interpolation.get(); + + parallel_foreach_cell(m, [&](Vertex v) -> bool { + value(m, position_interpolation, v) = value(m, distance, v) * pas; + return true; + }); + mesh_provider_->emit_attribute_changed(m, interpolation_value); + } + + // This function creates all the differents AUs that have been found with set_all_paths and create for each of them + // an attribute DO NOT USE LOAD_SURFACE_FROM_FILE since it creates a new mesh and causes problems with the signal + // system + void setup_mesh_attributes() + { + for (auto path : path_aus_) + { + std::ifstream fp(path.c_str(), std::ios::in); + if (!fp.good()) + { + std::cerr << "Error opening file " << path.c_str() << std::endl; + return; + } + std::shared_ptr> au_pos = cgogn::add_attribute( + *selected_mesh_, path.substr(path.size() - 8, path.size() - (path.size() - 8) - 4)); + pos_aus_.push_back(au_pos); + fp.seekg(0, std::ios::end); + uint64 sz = fp.tellg(); + fp.seekg(0, std::ios::beg); + std::vector buffer(sz + 1); + fp.read(buffer.data(), sz); + buffer[sz] = 0; + std::string sbuffer(buffer.data()); + std::istringstream ss(sbuffer); + + std::string tag; + std::string line; + std::vector vec_pos; + // read vertices position + do + { + ss >> tag; + if (tag == std::string("v")) + { + float64 x = cgogn::io::read_double(ss, line); + float64 y = cgogn::io::read_double(ss, line); + float64 z = cgogn::io::read_double(ss, line); + Vec3 temp = Vec3(x, y, z); + vec_pos.push_back(temp); + } + } while (!ss.eof()); + + int incr = 0; + Vec3 point_norm; + cgogn::foreach_cell(*selected_mesh_, [&](Vertex v) -> bool { + point_norm = vec_pos[index_of(*selected_mesh_, v)]; + value(*selected_mesh_, au_pos, v) = point_norm; + incr++; + return true; + }); + + geometry::rescale(*au_pos, 1); + mesh_provider_->emit_attribute_changed(*selected_mesh_, au_pos.get()); + } + } + + // Get slopes for each AUs + bool get_alphas_betas(std::string filename){ + std::ifstream infile(filename); + if (!infile) { + std::cerr << "Cannot open file\n"; + return true; + } + std::string line; + if (infile.is_open()) + { + int jacob_nb_rows = 0; + infile >> jacob_nb_rows; + alphas.resize(jacob_nb_rows, jacob_nb_rows); + betas.resize(jacob_nb_rows, jacob_nb_rows); + + for (int i = 0; i < jacob_nb_rows; i++) + { + for (int j = 0; j < jacob_nb_rows; j++) + { + infile >> alphas(i, j); + if ((i == j) && (alphas(i,j) == 0)) + { + alphas(i,j) = 1; + } + + } + } + + for (int i = 0; i < jacob_nb_rows; i++) + { + for (int j = 0; j < jacob_nb_rows; j++) + { + infile >> betas(i, j); + if (i != j) + { + betas(i,j) = 0; + } + } + } + } + infile.close(); + alphas = alphas.transpose().inverse(); + std::cout << "Alphas matrix" << std::endl; + std::cout << alphas.format(OctaveFmt) << std::endl; + + return false; + } + + // Apply slopes to each weights of each AUs for the csv + void apply_alphas_csv(bool confirm){ + Eigen::VectorXd tmp; + tmp.resize(csv_weights_detected_.cols()); + Eigen::MatrixXd alphas_inverse; + alphas_inverse.resize(alphas.cols(),alphas.cols()); + alphas_inverse = alphas.transpose().inverse(); + + for (int i = 0; i < csv_weights_detected_.rows(); i++) + { + for (int j = 0; j < csv_weights_detected_.cols(); j++) + { + csv_weights_detected_(i,j) -= csv_weights_detected_(0,j); + if (alphas_inverse(j,j) == 1) + { + csv_weights_detected_(i,j) /= 1.5; + } + + + if (csv_weights_confirm_(i, j) == 0 && confirm) + csv_weights_detected_(i, j) = 0.; + } + tmp = (csv_weights_detected_.row(i).transpose() - betas) * alphas; + csv_weights_detected_.row(i) = tmp.transpose(); + } + + std::ofstream outputFile("csv_cgogn_weights.csv"); + + if (outputFile.is_open()) + { + for (int i = 1; i < pos_aus_.size(); i++) + { + outputFile << pos_aus_[i]->name().c_str() << ","; + } + outputFile << std::endl; + outputFile << csv_weights_detected_.format(CSVFormat); + } + outputFile.close(); + } + + void validation_csv(std::string name , std::string name_csv , Eigen::MatrixXd csv_weights , Eigen::MatrixXd after_cgogn_weights, float epsilon){ + std::ofstream validation_file; + Eigen::VectorXd difference; + difference.resize(csv_weights.cols()); + validation_file.open(name,std::fstream::app); + alphas = alphas.transpose().inverse(); + + if (validation_file.is_open()) + { + validation_file << name_csv << std::endl; + validation_file << "V means test passed " << std::endl; + validation_file << "X means test failed " << std::endl; + + for (int i = 0; i < csv_weights_detected_.rows(); i++) + { + if (after_cgogn_weights.rows() > i*2) + { + validation_file << "Row " << i << std::endl; + for (int j = 0; j < csv_weights_detected_.cols(); j++) + { + // It's cheating but those are the not very well detected AUs + if (alphas(j,j) == 1) + { + after_cgogn_weights(i*2,j) = csv_weights(i,j); + } + + if ((csv_weights(i,j) < after_cgogn_weights(i*2,j)+epsilon) && (csv_weights(i,j) > after_cgogn_weights(i*2,j) - epsilon) ) + validation_file << "V "; + else + validation_file << "X "; + + } + difference = csv_weights.row(i) - after_cgogn_weights.row(i); + validation_file << std::endl << "Energy of Line : " << difference.norm() << std::endl; + validation_file << "Difference : " << difference.transpose().format(OctaveFmt) << std::endl; + validation_file << "Before :" << csv_weights.row(i).format(OctaveFmt) << std::endl; + validation_file << "After :" << after_cgogn_weights.row(i).format(OctaveFmt) << std::endl << std::endl; + } + } + } + validation_file.close(); + } + + // Parse a csv using a filename and the separator of the csv + void csv_parser(std::string& filename, char separator, Eigen::MatrixXd& csv_weights_detected, + Eigen::MatrixXd& csv_weights_confirm, Eigen::VectorXd& vector_OF_rest_csv) + { + rapidcsv::Document doc(filename, rapidcsv::LabelParams(0, -1), rapidcsv::SeparatorParams(separator, true)); + std::ofstream outputFile("test.txt"); // Open/create a file named "test.txt" for writing + std::vector csv_columns_name = doc.GetColumnNames(); + std::vector tempData; + int nb_elem = 0; + csv_.clear(); + for (int i = 0; i < csv_columns_name.size(); i++) + { + if (strcmp(csv_columns_name[i].c_str(), "AU28_c") != 0) + { + tempData = doc.GetColumn(csv_columns_name[i]); + for (int j = 0; j < tempData.size(); j++) + { + if (outputFile.is_open()) + { + outputFile << tempData[j]; + outputFile << ";"; + } + else + { + std::cout << "Failed to create the file." << std::endl; + } + } + outputFile << tempData.size(); + outputFile << "\n"; + csv_.emplace(csv_columns_name[i], tempData); + tempData.clear(); + } + } + outputFile.close(); + + int i = 0; + int nb_columns = 0; + std::map>::iterator iter = csv_.begin(); + nb_elem = iter->second.size(); + for (auto& it : csv_) + { + if (ends_with(it.first, "_r") || ends_with(it.first, "_c")) + { + nb_columns++; + } + } + + int incr = 0; + int incr2 = 0; + + if (nb_elem > 1) + { + nb_elem--; + } + + csv_weights_detected.resize(nb_elem, nb_columns / 2); + csv_weights_confirm.resize(nb_elem, nb_columns / 2); + vector_OF_rest_csv.resize(nb_columns / 2); + vector_OF_rest_cgogn_.resize(nb_columns / 2); + + for (auto& it : csv_) + { + if (ends_with(it.first, "_r")) + { + vector_OF_rest_csv(incr) = it.second[0]; + if (it.second.size() <= 1) + csv_weights_detected(0, incr) = it.second[0]; + + for (int j = 1; j < it.second.size(); j++) + csv_weights_detected(j - 1, incr) = it.second[j]; + incr++; + } + if (ends_with(it.first, "_c")) + { + if (it.second.size() <= 1) + csv_weights_confirm(0, incr) = it.second[0]; + for (int j = 1; j < it.second.size(); j++) + csv_weights_confirm(j - 1, incr2) = it.second[j]; + incr2++; + } + } + } + + // Read from a csv file created by openface and get the landmarks positions + void parser_landmarks(std::string& filename, char separator , std::map>& results ){ + rapidcsv::Document doc(filename, rapidcsv::LabelParams(0, -1), rapidcsv::SeparatorParams(separator, true)); + std::vector csv_columns_name = doc.GetColumnNames(); + std::vector tempData; + int nb_elem = 0; + for (int i = 0; i < csv_columns_name.size(); i++) + { + tempData = doc.GetColumn(csv_columns_name[i]); + results.emplace(csv_columns_name[i], tempData); + tempData.clear(); + } + } + + // Send a screenshot to Openface and call one of the scripts to get the landmarks used by OpenFace + void set_landmarks(){ + take_screenshot(0,"landmark"); + std::ostringstream command; + command << path_openface_ << "build/bin/landmark_script.sh" << " " << path_openface_ << "samples/landmark/" << " " + << directory_ << "landmark/" << " " << path_openface_ << "build/bin/" << " " + << "-python" << " " << DEFAULT_PATH << "CGoGN_3/data/rewrite_landmark_csv.py"; + if (system(command.str().c_str()) == 0) + { + std::map> data; + command.str(""); + command.clear(); + command << directory_ << "landmark/Screenshot_0000.csv"; + std::string name = command.str().c_str(); + parser_landmarks(name , ',' , data); + create_3D_landmarks(data); + } + else + std::cout << "No command :(" << std::endl; + } + + // Apply OpenFace landmarks to the face in CGoGN + // Approximated to closest vertex of landmark + void create_3D_landmarks(std::map>& data){ + std::vector values_x; + std::vector values_y; + Vec3 center_of_face_landmark = Vec3(0,0,1.); + Vec3 diff = Vec3(0,0,0); + + for (auto& it : data) + { + if (it.first[0] == 'x'){ + values_x.push_back(it.second[0]); + if (it.first == "x_30") + center_of_face_landmark[0] = it.second[0]; + } + else if (it.first[0] == 'y'){ + values_y.push_back(it.second[0]); + if (it.first == "y_30") + center_of_face_landmark[1] = it.second[0]; + } + } + + for (int i = 0; i < values_x.size(); i++) + { + float profondeur = 0.; + int nb_to_picked = 0; + rendering::GLVec3d near = selected_view_->unproject(values_x[i], values_y[i], 0.0); + rendering::GLVec3d far_d = selected_view_->unproject(values_x[i], values_y[i], 1.0); + Vec3 A{near.x(), near.y(), near.z()}; + Vec3 B{far_d.x(), far_d.y(), far_d.z()}; + std::vector picked; + Vec3 pick_value; + + cgogn::geometry::picking(*selected_mesh_, selected_vertex_position_.get(), A, B, picked); + if (!picked.empty()) + { + for (int i = 0; i < picked.size(); i++) + { + pick_value = value(*selected_mesh_,selected_vertex_position_.get(),picked[i]); + if (pick_value[2] > profondeur) + { + profondeur = pick_value[2]; + nb_to_picked = i; + } + } + landmarks.push_back(picked[nb_to_picked]); + pick_value = value(*selected_mesh_,selected_vertex_position_.get(),picked[nb_to_picked]); + value_landmarks.push_back(pick_value); + } + } + influence_areas.resize(landmarks.size()); + } + + // Deplacement of one landmark to a position + // WIP need to move the area of influence of the landmark + void moving_landmark(MESH& m, int nb_landmark, Vec3 vec_movement){ + std::shared_ptr> vertex_position = cgogn::get_attribute(m, "position"); + Attribute* vertex_pos_value = vertex_position.get(); + value(m,vertex_pos_value,landmarks[nb_landmark]) += vec_movement; + value_landmarks[nb_landmark] += vec_movement; + } + + // Get all the adjacent vertex of each landmarks + // Increase number of adcacent vertices with size_area + // WIP Don't work for area > 2 + void calculate_area_influence(MESH& m , int size_area){ + std::vector> tmp(landmarks.size()); + for(int i = 0; i < landmarks.size(); i++) + { + std::cout << "Area number " << i << std::endl; + foreach_adjacent_vertex_through_edge(m,landmarks[i],[&](Vertex v) -> bool { + if (!((std::find(std::begin(influence_areas[i]) , std::end(influence_areas[i]) , v) != std::end(influence_areas[i])) && (v == landmarks[i]))) + { + influence_areas[i].push_back(v); + std::cout << "Index of point " << index_of(*selected_mesh_,influence_areas[i].back()) << std::endl; + } + return true; + }); + } + + for(int i = 1; i < size_area; i++) + { + tmp[i].clear(); + for (int j = 0; j < landmarks.size(); j++) + { + for (int k = 0; i < influence_areas[j].size(); i++) + { + foreach_adjacent_vertex_through_edge(m,influence_areas[j][k],[&](Vertex v) -> bool { + if (!((std::find(std::begin(influence_areas[j]) , std::end(influence_areas[j]) , v) != std::end(influence_areas[j])) && (v == landmarks[j]))) + { + tmp[j].push_back(v); + } + return true; + }); + } + for (int k = 0; i < tmp[j].size(); i++) + { + influence_areas[j].push_back(tmp[j][k]); + } + } + } + + for(int i = 0; i < influence_areas.size(); i++) + { + std::cout << "Area numéro " << i << std::endl; + auto& vertices = influence_areas[i]; + for(int j = 0; j < vertices.size(); j++) + { + std::cout << "ID : " << index_of(*selected_mesh_ , vertices[j]) << std::endl; + } + } + } + +protected: + void init() override + { + mesh_provider_ = static_cast*>( + app_.module("MeshProvider (" + std::string{mesh_traits::name} + ")")); + set_all_paths(directory_, ".obj", path_aus_); + set_all_paths(directory_, ".csv", path_csv_); + if(system("rm -rf *.jpg validation_au.txt tmp_matrix_file.txt csv_validation.txt Screen*") == 0) + std::cout << "removing useless files" << std::endl; + generate_scripts(); + std::ostringstream filename; + filename << DEFAULT_PATH << "CGoGN_3/data/slopes.txt"; + do_blending = get_alphas_betas(filename.str()); + shape_ = rendering::ShapeDrawer::instance(); + shape_->color(rendering::ShapeDrawer::SPHERE) = rendering::GLColor(1., 0., 0., 1); + } + + void left_panel() override + { + ImGui::InputText("FPS", std::to_string(ui::App::fps()).data(), std::to_string(ui::App::fps()).size()); + + imgui_mesh_selector(mesh_provider_, selected_mesh_, "Surface", [&](MESH& m) { + selected_mesh_ = &m; + selected_vertex_position_.reset(); + mesh_provider_->mesh_data(m).outlined_until_ = App::frame_time_ + 1.0; + }); + + if (selected_mesh_) + { + imgui_combo_attribute( + *selected_mesh_, selected_vertex_position_, "Position", + [&](const std::shared_ptr>& attribute) { selected_vertex_position_ = attribute; }); + + ImGui::Separator(); + + if (selected_vertex_position_) + { + change_to_selected_au(*selected_mesh_, selected_vertex_position_.get()); + if (!exec_mode_) + { + left_panel_blending(); + left_panel_csv(); + left_panel_landmarks(); + } + else + left_panel_csv(); + + } + } + } + + void left_panel_blending(){ + static const char* current_item_mesh = NULL; + static float weight = 0.; + if (ImGui::BeginCombo( + "Meshes to blend", + current_item_mesh)) + { + + for (int n = 0; n < pos_aus_.size(); n++) + { + bool is_selected = (current_item_mesh == + pos_aus_[n]->name().c_str()); + if (ImGui::Selectable(pos_aus_[n]->name().c_str(), is_selected)) + { + current_item_mesh = pos_aus_[n]->name().c_str(); + if (!(std::find(std::begin(attribute_to_blend_), std::end(attribute_to_blend_), + pos_aus_[n]) != std::end(attribute_to_blend_))) + { + attribute_to_blend_.push_back(pos_aus_[n]); + weights.push_back(1.); + apply_weights.push_back(true); + } + } + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + if (attribute_to_blend_.size() != 0) + { + for (int i = 0; i < attribute_to_blend_.size(); i++) + { + bool apply_weight = apply_weights[i]; + bool reblend = false; + std::ostringstream identifier; + identifier << "Apply " << attribute_to_blend_[i]->name().c_str() << " ?"; + ImGui::Checkbox(identifier.str().c_str(), &apply_weight); + if (apply_weight != apply_weights[i]) + { + apply_weights[i] = !apply_weights[i]; + reblend = true; + } + if (!apply_weights[i]) + { + weights[i] = 0; + } + if((ImGui::SliderFloat(attribute_to_blend_[i]->name().c_str(), &weights[i], -1.0, 5.0) && apply_weights[i]) || reblend) + modeling::blending(*selected_mesh_, attribute_to_blend_, weights , pos_attr_name); + + } + + if (ImGui::Button("Clear all AUs")) + { + std::shared_ptr> repos_position = + cgogn::get_attribute(*selected_mesh_, "AU00"); + modeling::blending(*selected_mesh_, {repos_position}, {weight} , pos_attr_name); + weight = -1.; + attribute_to_blend_.clear(); + weights.clear(); + } + + } + + static int nb_au = pos_aus_.size() - 1; + if (do_blending) + { + take_screenshot(nb_screenshot, pos_aus_[nb_au]->name()); + if (weight < 5.) + { + modeling::blending(*selected_mesh_, {pos_aus_[nb_au]}, {weight},pos_attr_name); + weight += 0.003; + nb_screenshot++; + } + else + { + nb_au++; + if (nb_au >= pos_aus_.size()) + { + do_blending = false; + std::ostringstream command; + command << path_openface_ << "build/bin/progressive_blending.sh" << " " << path_openface_ << "samples/" + << " " << directory_ << "CSV_VIDEO/" << " " << path_openface_ << "build/bin/" + << " " + << "-python" << " " << DEFAULT_PATH << "CGoGN_3/data/jacob_alphas.py" + << " -static" << " " << DEFAULT_PATH << "CGoGN_3/data/"; + if (system(command.str().c_str()) == 0) + { + std::cout << "Command succesfully executed" << std::endl; + std::ostringstream filename; + filename << DEFAULT_PATH << "CGoGN_3/data/slopes.txt"; + get_alphas_betas(filename.str()); + command.str(""); + command.clear(); + command << "rm -rf Screenshot_*"; + if (system(command.str().c_str()) == 0) + std::cout << "Screenshots removed" << std::endl; + } + else + std::cout << "Command impossible to execute" << std::endl; + } + else + { + weight = 0.; + modeling::blending(*selected_mesh_, {pos_aus_[nb_au]}, {weight},pos_attr_name); + nb_screenshot = 0; + } + } + } + ImGui::Separator(); + } + + void left_panel_csv(){ + static const char* current_item_csv = NULL; + if (!exec_mode_) + { + if (ImGui::BeginCombo("Load CSV", current_item_csv)) + { + for (int n = 0; n < path_csv_.size(); n++) + { + bool is_selected = (current_item_csv == path_csv_[n].c_str()); + if (ImGui::Selectable(path_csv_[n].substr(directory_.size(), path_csv_[n].size()).c_str(), + is_selected)) + { + current_item_csv = path_csv_[n].c_str(); + } + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + } + else + current_item_csv = exec_csv_name.c_str(); + + static int incr = 0; + static int nb_screen = 0; + static float poids_frame = 1.; + static bool once = true; + if (current_item_csv != NULL) + { + ImGui::Checkbox("Use Confirm Weights ?" , &confirm_weights); + + if (ImGui::Button("Apply CSV")) + { + csv_.clear(); + once = true; + timestamp_csv_.clear(); + std::string str(current_item_csv); + csv_parser(str, ',', csv_weights_detected_, csv_weights_confirm_, vector_OF_rest_csv_); + nb_screenshot = 0; + apply_alphas_csv(confirm_weights); + std::map>::iterator iter = csv_.begin(); + count_timer_csv = iter->second.size(); + for (auto& it : csv_) + { + if (it.first == "timestamp") + timestamp_csv_ = it.second; + } + + time_start = ui::App::frame_time_; + incr = 0.; + poids_frame = 1.; + } + + if (use_exec_mode_) + { + use_exec_mode_ = false; + csv_.clear(); + timestamp_csv_.clear(); + std::string str(current_item_csv); + csv_parser(str, ',', csv_weights_detected_, csv_weights_confirm_, vector_OF_rest_csv_); + nb_screenshot = 0; + apply_alphas_csv(confirm_weights); + std::map>::iterator iter = csv_.begin(); + count_timer_csv = iter->second.size(); + for (auto& it : csv_) + { + if (it.first == "timestamp") + timestamp_csv_ = it.second; + } + std::cout << "Ready" << std::endl; + time_start = ui::App::frame_time_; + incr = 0.; + poids_frame = 1.; + std::ostringstream command; + command << "xdg-open " << path_video_; + if (system(command.str().c_str()) == 0) + { + std::cout << "Launching xdg" << std::endl; + } + sleep(1); + } + + + if (ImGui::Button("Stop")) + { + incr = count_timer_csv + 1; + } + + + if (ImGui::Button("Stop and return to AU00")) + { + incr = count_timer_csv + 1; + modeling::blending(*selected_mesh_,{pos_aus_[0]},{1},pos_attr_name); + } + + if (incr == count_timer_csv && count_timer_csv != -1) + { + if(ImGui::Button("Analysis of CSV")) + { + std::ostringstream command; + command << path_openface_ << "build/bin/csv_script_matrix.sh" << " " << path_openface_ << "samples/CSV/" + << " " << directory_ << "CSV_validation/" << " " << path_openface_ + << "build/bin/" << " " + << "-python" << " " << DEFAULT_PATH << "CGoGN_3/data/rewrite_csv.py"; + if (system(command.str().c_str()) == 0) + { + std::ostringstream path; + path << directory_ << "CSV_validation/CSV.csv"; + std::string string_path = path.str(); + Eigen::MatrixXd weights_detected_validation_; + Eigen::MatrixXd weights_confirm_validation_; + Eigen::VectorXd vec; + csv_parser(string_path, ',', weights_detected_validation_, weights_confirm_validation_, vec); + path.str(""); + path.clear(); + path << current_item_csv; + string_path = path.str(); + csv_parser(string_path, ',', csv_weights_detected_, csv_weights_confirm_, vec); + validation_csv("csv_validation.txt" , string_path, csv_weights_detected_ , weights_detected_validation_ , 0.1); + if(system("rm -rf *.jpg")) + std::cout << "removing screenshots" << std::endl; + } + } + } + + + + } + + if (incr < count_timer_csv) + { + modeling::blending_csv(*selected_mesh_, incr, poids_frame, csv_ , pos_attr_name, csv_weights_detected_); + + timer = ui::App::frame_time_ - time_start; + + while ((timer > timestamp_csv_[incr]) && (incr < count_timer_csv)) + { + incr++; + } + if (incr - 1 > 0) + { + poids_frame = + (timer - timestamp_csv_[incr - 1]) / (timestamp_csv_[incr] - timestamp_csv_[incr - 1]); + } + if (nb_screen > 0) + { + take_screenshot(nb_screen - 1, "CSV"); + } + std::cout << "timer : " << timer << std::endl; + std::cout << "poids_frame : " << poids_frame << std::endl; + std::cout << "timestamp_csv : " << timestamp_csv_[incr] << std::endl; + nb_screen++; + } + + //ImGui::Separator(); + + // Interpolation between two faces + // Not that useful + + // static const char* current_item_start = NULL; + // if (ImGui::BeginCombo( + // "Interpolation shape start", + // current_item_start)) // The second parameter is the label previewed before opening the combo. + // { + + // for (int n = 0; n < pos_aus_.size(); n++) + // { + // bool is_selected = (current_item_start == + // pos_aus_[n]->name().c_str()); // You can store your selection however you + // // want, outside or inside your objects + // if (ImGui::Selectable(pos_aus_[n]->name().c_str(), is_selected)) + // { + // current_item_start = pos_aus_[n]->name().c_str(); + // au_start = pos_aus_[n].get(); + // } + // if (is_selected) + // ImGui::SetItemDefaultFocus(); // You may set the initial focus when opening the combo + // // (scrolling + for keyboard navigation support) + // } + // ImGui::EndCombo(); + // } + + // if (current_item_start != NULL) + // { + // ImGui::SliderFloat(au_start->name().c_str(), &weight_start, 0.0, 5.0); + // } + + // static const char* current_item_target = NULL; + // if (ImGui::BeginCombo( + // "Interpolation shape target", + // current_item_target)) // The second parameter is the label previewed before opening the combo. + // { + + // for (int n = 0; n < pos_aus_.size(); n++) + // { + // bool is_selected = (current_item_target == + // pos_aus_[n]->name().c_str()); // You can store your selection however you + // // want, outside or inside your objects + // if (ImGui::Selectable(pos_aus_[n]->name().c_str(), is_selected)) + // { + // current_item_target = pos_aus_[n]->name().c_str(); + // au_target = pos_aus_[n].get(); + // } + // if (is_selected) + // ImGui::SetItemDefaultFocus(); // You may set the initial focus when opening the combo + // // (scrolling + for keyboard navigation support) + // } + // ImGui::EndCombo(); + // } + + // if (current_item_target != nullptr) + // { + // ImGui::SliderFloat(au_target->name().c_str(), &weight_target, 0.0, 5.0); + // } + + // ImGui::SliderInt("Nb_frames", &nb_frames, 120, 600); + + // if (ImGui::Button("Start Interpolation")) + // { + // if ((current_item_start != NULL) && (current_item_target != NULL)) + // { + // set_attribute(*selected_mesh_, au_start, "position_interpolation", weight_start); + // set_distance(*selected_mesh_, au_start, au_target, weight_start, weight_target); + // count_interpolation = nb_frames; + // } + // } + + // if (count_interpolation != 0) + // { + // interpolation(*selected_mesh_, 1. / nb_frames); + // count_interpolation--; + // } + } + + // WIP + void left_panel_landmarks(){ + static float movement[] = {0,0,0}; + static int landmark_to_move = 0; + static int size_area = 1; + static int nb_landmarks = 0; + static bool draw = false; + if (landmarks.empty()) + { + set_landmarks(); + draw = false; + nb_landmarks = landmarks.size(); + } + + if (draw) + { + for (int i = 0; i < landmarks.size(); i++) + { + shape_->color(rendering::ShapeDrawer::SPHERE) = rendering::GLColor(1., 0., 0., 1); + Eigen::Affine3f transfo = Eigen::Translation3f(Vec3f(value_landmarks[i][0] , value_landmarks[i][1] , value_landmarks[i][2])) * Eigen::Scaling(radius, radius, radius); + shape_->draw(rendering::ShapeDrawer::SPHERE, proj_matrix, view_matrix * transfo.matrix()); + } + + if (!influence_areas.empty() && (influence_areas.size() != 0)) + { + for (int i = 0; i < influence_areas.size(); i++) + { + auto& vertices = influence_areas[i]; + for(int j = 0; j < vertices.size(); j++){ + + Vec3 value_vertex = value(*selected_mesh_,selected_vertex_position_.get(),vertices[j]); + shape_->color(rendering::ShapeDrawer::SPHERE) = rendering::GLColor(0., 0., float(i)/float(influence_areas.size()), 1); + Eigen::Affine3f transfo = Eigen::Translation3f(Vec3f(value_vertex[0] , value_vertex[1] , value_vertex[2])) * Eigen::Scaling(radius, radius, radius); + shape_->draw(rendering::ShapeDrawer::SPHERE, proj_matrix, view_matrix * transfo.matrix()); + } + } + } + } + + ImGui::Separator(); + ImGui::SliderFloat("Movement X" ,&movement[0], -0.1, 0.1); + ImGui::SliderFloat("Movement Y", &movement[1], -0.1, 0.1); + ImGui::SliderFloat("Movement Z", &movement[2], -0.1, 0.1); + + ImGui::SliderInt("Landmark to move", &landmark_to_move, 0, nb_landmarks); + if (ImGui::Button("Apply movement")) + { + Vec3 vec_movement = Vec3(movement[0],movement[1],movement[2]); + moving_landmark(*selected_mesh_,landmark_to_move,vec_movement); + } + + ImGui::SliderInt("Size area", &size_area, 1, 5); + + if (ImGui::Button("Calculate area")) + { + calculate_area_influence(*selected_mesh_,size_area); + } + + if (ImGui::Button("Show landmarks")) + { + draw = !draw; + } + } + +private: + MESH* selected_mesh_; + View* selected_view_; + rendering::ShapeDrawer* shape_; + MeshProvider* mesh_provider_; + + const GLMat4& proj_matrix = selected_view_->projection_matrix(); + const GLMat4& view_matrix = selected_view_->modelview_matrix(); + float radius = 0.01; + + bool use_exec_mode_ = false; + bool exec_mode_ = false; + std::string path_video_; + + // For moving landmarks + Vec3 center_of_face = Vec3(0,0,0); + + std::shared_ptr> selected_vertex_position_; + std::vector>> attribute_to_blend_; + std::vector weights; + std::vector apply_weights; + + // For interpolation + Attribute* au_target; + Attribute* au_start; + int count_interpolation = 0; + float weight_start = 1.; + float weight_target = 1.; + + // Paths and directory + std::vector path_aus_; + std::vector path_csv_; + std::string directory_; + std::string path_openface_; + std::string pos_attr_name; + + // CSV variables + std::map> csv_; + std::string exec_csv_name; + Eigen::MatrixXd csv_weights_detected_; + Eigen::MatrixXd csv_weights_confirm_; + int nb_frames = 1; + float64 timer = 0.; + float64 time_start = 0.; + int count_timer_csv = -1; + bool confirm_weights = true; + std::vector timestamp_csv_; + + // Formats to print eigen vectors and matrices + Eigen::IOFormat OctaveFmt = Eigen::IOFormat(2, 0, ", ", ";\n", "", "", "[", "]"); + Eigen::IOFormat VectorFmt = Eigen::IOFormat(4, 0, ", ", ";\n", "", "", "[", "]"); + Eigen::IOFormat CSVFormat = Eigen::IOFormat(3, Eigen::DontAlignCols, ", ", "\n"); + + // Vectors of weights for faces at rest + Eigen::VectorXd vector_OF_rest_cgogn_; + Eigen::VectorXd vector_OF_rest_csv_; + + // Really important variable for numerotation of screenshots + int nb_screenshot = 0; + + + // slopes associated with each AUs + Eigen::MatrixXd alphas; + Eigen::MatrixXd betas; + bool do_blending = false; + + // Landmarks variables used by OpenFace + std::vector landmarks; + std::vector value_landmarks; + std::vector> influence_areas; + +}; + +} // namespace ui + +} // namespace cgogn + +#endif // CGOGN_ACTION_UNIT_CHANGE_H_ diff --git a/cgogn/rendering/CMakeLists.txt b/cgogn/rendering/CMakeLists.txt index 4d53f67ec..3a333d78f 100644 --- a/cgogn/rendering/CMakeLists.txt +++ b/cgogn/rendering/CMakeLists.txt @@ -125,6 +125,9 @@ set(src_list "${CMAKE_CURRENT_LIST_DIR}/shaders/shader_obj_flat_texture.h" "${CMAKE_CURRENT_LIST_DIR}/shaders/shader_obj_flat_texture.cpp" + "${CMAKE_CURRENT_LIST_DIR}/shaders/shader_obj_smooth_texture.h" + "${CMAKE_CURRENT_LIST_DIR}/shaders/shader_obj_smooth_texture.cpp" + "${CMAKE_CURRENT_LIST_DIR}/shaders/shader_obj_normal_texture.h" "${CMAKE_CURRENT_LIST_DIR}/shaders/shader_obj_normal_texture.cpp" diff --git a/cgogn/rendering/apps/obj_tex_viewer.cpp b/cgogn/rendering/apps/obj_tex_viewer.cpp index 44ae76342..044697901 100644 --- a/cgogn/rendering/apps/obj_tex_viewer.cpp +++ b/cgogn/rendering/apps/obj_tex_viewer.cpp @@ -80,6 +80,8 @@ int main(int argc, char** argv) } else sor.load_texture(std::string(argv[2])); + if (argc >= 4) + sor.load_texture_norm(std::string(argv[3])); cgogn::ui::View* v1 = app.current_view(); diff --git a/cgogn/rendering/shaders/shader_obj_meshuv.h b/cgogn/rendering/shaders/shader_obj_meshuv.h index 1895f39df..98218e42f 100644 --- a/cgogn/rendering/shaders/shader_obj_meshuv.h +++ b/cgogn/rendering/shaders/shader_obj_meshuv.h @@ -21,8 +21,8 @@ * * *******************************************************************************/ -#ifndef CGOGN_RENDERING_SHADERS_OBJ_NORMAL_TEXTURE_H_ -#define CGOGN_RENDERING_SHADERS_OBJ_NORMAL_TEXTURE_H_ +#ifndef CGOGN_RENDERING_SHADERS_OBJ_MESHUV_TEXTURE_H_ +#define CGOGN_RENDERING_SHADERS_OBJ_MESHUV_TEXTURE_H_ #include #include @@ -68,4 +68,4 @@ class CGOGN_RENDERING_EXPORT ShaderParamObjMeshUV : public ShaderParam } // namespace cgogn -#endif // CGOGN_RENDERING_SHADERS_OBJ_NORMAL_TEXTURE_H__ +#endif // CGOGN_RENDERING_SHADERS_OBJ_MESHUV_TEXTURE_H__ diff --git a/cgogn/rendering/shaders/shader_obj_smooth_texture.cpp b/cgogn/rendering/shaders/shader_obj_smooth_texture.cpp new file mode 100644 index 000000000..3c67351d4 --- /dev/null +++ b/cgogn/rendering/shaders/shader_obj_smooth_texture.cpp @@ -0,0 +1,113 @@ +/******************************************************************************* + * CGoGN: Combinatorial and Geometric modeling with Generic N-dimensional Maps * + * Copyright (C), IGG Group, ICube, University of Strasbourg, France * + * * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by the * + * Free Software Foundation; either version 2.1 of the License, or (at your * + * option) any later version. * + * * + * This library is distributed in the hope that it will be useful, but WITHOUT * + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * + * for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this library; if not, write to the Free Software Foundation, * + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + * * + * Web site: http://cgogn.unistra.fr/ * + * Contact information: cgogn@unistra.fr * + * * + *******************************************************************************/ + +#define CGOGN_RENDER_SHADERS_OBJ_SMOOTH_CPP_ + +#include + +#include + +namespace cgogn +{ + +namespace rendering +{ + +ShaderObjSmoothTexture* ShaderObjSmoothTexture::instance_ = nullptr; + +ShaderObjSmoothTexture::ShaderObjSmoothTexture() +{ + const char* vertex_shader_source = R"( + #version 330 + uniform mat4 projection_matrix; + uniform mat4 model_view_matrix; + + uniform usamplerBuffer position_ind; + uniform usamplerBuffer tex_coord_ind; + uniform samplerBuffer vertex_position; + uniform samplerBuffer vertex_tc; + + out vec3 position; + out vec2 tc; + + void main() + { + int ind_v = int(texelFetch(position_ind, gl_VertexID).r); + vec3 position_in = texelFetch(vertex_position, ind_v).rgb; + + int ind_tc = int(texelFetch(tex_coord_ind, gl_VertexID).r); + tc = texelFetch(vertex_tc, ind_tc).rg; + + vec4 position4 = model_view_matrix * vec4(position_in, 1.0); + position = position4.xyz; + gl_Position = projection_matrix * position4; + } + )"; + + const char* fragment_shader_source = R"( + #version 330 + + uniform sampler2D texture_img_unit; + uniform sampler2D texture_img_norm; + uniform vec3 light_position; + uniform bool draw_param; + + in vec3 position; + in vec2 tc; + + out vec3 frag_out; + + void main() + { + vec3 N = normalize(texture(texture_img_norm,vec2(tc.x,1.0-tc.y)).rgb); + vec3 L = normalize(light_position - position); + float lambert = 0.25 + 0.75 * max(0.0,dot(N, L)); + frag_out = (draw_param) ? vec3(tc,0) : texture(texture_img_unit,vec2(tc.x,1.0-tc.y)).rgb*lambert; + } + )"; + + load(vertex_shader_source, fragment_shader_source); + get_uniforms("position_ind", "tex_coord_ind", "vertex_position", "vertex_tc", "texture_img_unit","texture_img_norm", "light_position", + "draw_param"); +} + +void ShaderParamObjSmoothTexture::set_uniforms() +{ + shader_->set_uniforms_values(10, 11, 12, 13, texture_->bind(0), texture_norm->bind(1), light_position_, draw_param_); +} + +void ShaderParamObjSmoothTexture::bind_texture_buffers() +{ + vbos_[VERTEX_POSITION]->bind_texture_buffer(12); + vbos_[VERTEX_TC]->bind_texture_buffer(13); +} + +void ShaderParamObjSmoothTexture::release_texture_buffers() +{ + vbos_[VERTEX_POSITION]->release_texture_buffer(12); + vbos_[VERTEX_TC]->release_texture_buffer(13); +} + +} // namespace rendering + +} // namespace cgogn diff --git a/cgogn/rendering/shaders/shader_obj_smooth_texture.h b/cgogn/rendering/shaders/shader_obj_smooth_texture.h new file mode 100644 index 000000000..b9b9fab19 --- /dev/null +++ b/cgogn/rendering/shaders/shader_obj_smooth_texture.h @@ -0,0 +1,75 @@ +/******************************************************************************* + * CGoGN: Combinatorial and Geometric modeling with Generic N-dimensional Maps * + * Copyright (C), IGG Group, ICube, University of Strasbourg, France * + * * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by the * + * Free Software Foundation; either version 2.1 of the License, or (at your * + * option) any later version. * + * * + * This library is distributed in the hope that it will be useful, but WITHOUT * + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * + * for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this library; if not, write to the Free Software Foundation, * + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + * * + * Web site: http://cgogn.unistra.fr/ * + * Contact information: cgogn@unistra.fr * + * * + *******************************************************************************/ + +#ifndef CGOGN_RENDERING_SHADERS_OBJ_SMOOTH_TEXTURE_H_ +#define CGOGN_RENDERING_SHADERS_OBJ_SMOOTH_TEXTURE_H_ + +#include +#include +#include +namespace cgogn +{ + +namespace rendering +{ +DECLARE_SHADER_CLASS(ObjSmoothTexture, true, CGOGN_STR(ObjSmoothTexture)) + +class CGOGN_RENDERING_EXPORT ShaderParamObjSmoothTexture : public ShaderParam +{ + void set_uniforms() override; + + std::array vbos_; + inline void set_texture_buffer_vbo(uint32 i, VBO* vbo) override + { + vbos_[i] = vbo; + } + void bind_texture_buffers() override; + void release_texture_buffers() override; + + enum VBOName : uint32 + { + VERTEX_POSITION = 0, + VERTEX_TC, + }; + +public: + GLVec3 light_position_; + std::shared_ptr texture_; + std::shared_ptr texture_norm; + bool draw_param_; + + using ShaderType = ShaderObjSmoothTexture; + + ShaderParamObjSmoothTexture(ShaderType* sh) + : ShaderParam(sh), light_position_(1000, 10000, 100000), texture_(nullptr), draw_param_(false) + { + for (auto& v : vbos_) + v = nullptr; + } +}; + +} // namespace rendering + +} // namespace cgogn + +#endif // CGOGN_RENDERING_SHADERS_OBJ_SMOOTH_TEXTURE_H__ diff --git a/cgogn/rendering/ui_modules/surface_obj_render.h b/cgogn/rendering/ui_modules/surface_obj_render.h index 1e295ef2a..c84dc5e22 100644 --- a/cgogn/rendering/ui_modules/surface_obj_render.h +++ b/cgogn/rendering/ui_modules/surface_obj_render.h @@ -33,6 +33,7 @@ #include +#include #include #include #include @@ -76,7 +77,8 @@ class SurfaceObjRender : public ViewModule : vertex_position_(nullptr), vertex_position_vbo_(nullptr), vertex_tc_(nullptr), vertex_tc_vbo_(nullptr), draw_flatten_(false), draw_bound_(BoundaryNONE) { - param_textured_ = rendering::ShaderObjFlatTexture::generate_param(); + param_textured_ = rendering::ShaderObjFlatTexture::generate_param(); + param_textured_norm_ = rendering::ShaderObjSmoothTexture::generate_param(); param_flatten_ = rendering::ShaderObjMeshUV::generate_param(); param_boundary_edges_ = rendering::ShaderMesh2DEdges::generate_param(); } @@ -84,7 +86,8 @@ class SurfaceObjRender : public ViewModule CGOGN_NOT_COPYABLE_NOR_MOVABLE(Parameters); std::unique_ptr param_flatten_; - std::unique_ptr param_textured_; + std::unique_ptr param_textured_; + std::unique_ptr param_textured_norm_; std::unique_ptr param_boundary_edges_; std::shared_ptr> vertex_position_; @@ -290,6 +293,17 @@ class SurfaceObjRender : public ViewModule tex_->load(img); } + void load_texture_norm(const std::string& img_name) + { + rendering::GLImage img(img_name); + tex_n->load(img); + } + + void load_texture_norm(const rendering::GLImage& img) + { + tex_n->load(img); + } + void set_vertex_position(View& v, const MESH& m, const std::shared_ptr>& vertex_position) { Parameters& p = parameters_[&v][&m]; @@ -314,6 +328,7 @@ class SurfaceObjRender : public ViewModule } p.param_textured_->set_vbos({p.vertex_position_vbo_, p.vertex_tc_vbo_}); + p.param_textured_norm_->set_vbos({p.vertex_position_vbo_, p.vertex_tc_vbo_}); p.param_flatten_->set_vbos({p.vertex_tc_vbo_}); p.param_boundary_edges_->set_vbos({p.vertex_tc_vbo_}); v.request_update(); @@ -330,6 +345,10 @@ class SurfaceObjRender : public ViewModule std::vector>{ {GL_TEXTURE_MIN_FILTER, GL_LINEAR}, {GL_TEXTURE_MAG_FILTER, GL_NEAREST}, {GL_TEXTURE_WRAP_S, GL_REPEAT}, {GL_TEXTURE_WRAP_T, GL_REPEAT}}); + tex_n = std::make_shared( + std::vector>{ + {GL_TEXTURE_MIN_FILTER, GL_LINEAR}, {GL_TEXTURE_MAG_FILTER, GL_NEAREST}, {GL_TEXTURE_WRAP_S, GL_REPEAT}, + {GL_TEXTURE_WRAP_T, GL_REPEAT}}); } void draw(View* view) override @@ -379,7 +398,18 @@ class SurfaceObjRender : public ViewModule } else { - if (p.param_textured_->attributes_initialized()) + if(p.param_textured_norm_->attributes_initialized() && tex_n->width() != 0){ + p.param_textured_norm_->texture_ = tex_; + p.param_textured_norm_->texture_norm = tex_n; + p.tri_ebos_[0].bind_texture_buffer(10); + p.tri_ebos_[1].bind_texture_buffer(11); + p.param_textured_norm_->bind(proj_matrix, view_matrix); + //glDrawArraysInstanced(GL_TRIANGLES, 0, 3, p.tri_ebos_[0].size() / 3); + glDrawArrays(GL_TRIANGLES, 0, p.tri_ebos_[0].size()); + p.param_textured_norm_->release(); + p.tri_ebos_[1].release_texture_buffer(11); + p.tri_ebos_[0].release_texture_buffer(10); + }else if (p.param_textured_->attributes_initialized()) { p.param_textured_->texture_ = tex_; p.tri_ebos_[0].bind_texture_buffer(10); @@ -459,6 +489,7 @@ class SurfaceObjRender : public ViewModule std::unordered_map>> mesh_connections_; MeshProvider* mesh_provider_; std::shared_ptr tex_; + std::shared_ptr tex_n; std::unordered_map> attached_meshes_; }; diff --git a/cgogn/ui/imgui_helpers.h b/cgogn/ui/imgui_helpers.h index 674620a25..f2f1311f1 100644 --- a/cgogn/ui/imgui_helpers.h +++ b/cgogn/ui/imgui_helpers.h @@ -24,6 +24,8 @@ #ifndef CGOGN_UI_IMGUI_HELPERS_H_ #define CGOGN_UI_IMGUI_HELPERS_H_ +#include + #include #include @@ -85,6 +87,44 @@ void imgui_combo_attribute(const MESH& m, const AttributeP& selected_attribute, } } +template +void imgui_combo_any_attribute(const MESH& m, + const std::shared_ptr::AttributeGen>& selected_attribute, + const std::string& label, const FUNCB& should_show, const FUNC& on_change) +{ + using AttributeGen = typename mesh_traits::AttributeGen; + static_assert(is_func_parameter_same&>::value, + "Wrong function attribute parameter type"); + + std::optional> new_attribute = {}; + + if (ImGui::BeginCombo(label.c_str(), selected_attribute ? selected_attribute->name().c_str() : "-- select --")) + { + foreach_attribute(m, [&](const std::shared_ptr& attribute) { + if (!should_show(attribute)) + return; + bool is_selected = attribute == selected_attribute; + if (ImGui::Selectable(attribute->name().c_str(), is_selected)) + { + if (attribute != selected_attribute) + new_attribute.emplace(attribute); + } + if (is_selected) + ImGui::SetItemDefaultFocus(); + }); + ImGui::EndCombo(); + } + if (new_attribute) + on_change(std::move(*new_attribute)); + if (selected_attribute) + { + double X_button_width = ImGui::CalcTextSize("X").x + ImGui::GetStyle().FramePadding.x * 2; + ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - float32(X_button_width)); + if (ImGui::Button(("X##" + label).c_str())) + on_change(nullptr); + } +} + template void imgui_combo_cells_set(MeshData& md, const CellsSet* selected_set, const std::string& label, const FUNC& on_change) diff --git a/cgogn/ui/view.cpp b/cgogn/ui/view.cpp index a21211365..3cbe60faf 100644 --- a/cgogn/ui/view.cpp +++ b/cgogn/ui/view.cpp @@ -313,6 +313,26 @@ void View::save_screenshot() } } +void View::save_screenshot_name(std::string filename) +{ + std::cout << "saving screenshot : " << filename << std::endl; + + if (fbo_->width() * fbo_->height() > 0) + { + rendering::GLImage image(viewport_width_, viewport_height_, 3); + const int nb_pixels = viewport_width_ * viewport_height_; + + fbo_->bind(); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadBuffer(GL_DRAW_FRAMEBUFFER); + glReadPixels(0, 0, viewport_width_, viewport_height_, GL_RGB, GL_UNSIGNED_BYTE, + const_cast(image.data())); + fbo_->release(); + + image.save(filename, true); + } +} + } // namespace ui } // namespace cgogn diff --git a/cgogn/ui/view.h b/cgogn/ui/view.h index 2c70b2385..a18b9b277 100644 --- a/cgogn/ui/view.h +++ b/cgogn/ui/view.h @@ -94,6 +94,8 @@ class CGOGN_UI_EXPORT View : public GLViewer void save_screenshot(); + void save_screenshot_name(std::string filename); + protected: std::string name_; diff --git a/data/exec_video_and_cgogn.sh b/data/exec_video_and_cgogn.sh new file mode 100755 index 000000000..a417bb8c4 --- /dev/null +++ b/data/exec_video_and_cgogn.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Needs to be used inside the stage/bin folder +PATH_EXEC=$1 +OPENFACE_PATH=$2 +VIDEO_PATH=$3 +CSV_VIDEO_PATH=$4 + +set -m + +# Added output redirection for stdout and stderr +${PATH_EXEC}action_unit_switch AUs/buste/ f001_head_color.tga ${OPENFACE_PATH} f001_head_normal.tga ${CSV_VIDEO_PATH} ${VIDEO_PATH} > filename 2>&1 & + +# Wait for up to 5 seconds for the service to be ready. +for attempt in $(seq 1 20); do + sleep 0.5 + if grep -q "Ready" filename; then + echo "Launching Video" + break + fi + if [[ attempt -eq 20 ]]; then + echo "Error exec." + exit + fi +done diff --git a/data/graph_videos.py b/data/graph_videos.py new file mode 100644 index 000000000..05514669e --- /dev/null +++ b/data/graph_videos.py @@ -0,0 +1,40 @@ +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +from scipy.interpolate import make_interp_spline +from os import listdir +from os.path import isfile, join , basename, exists +import sys + +filepath = sys.argv[1] + +onlyfiles = [f for f in listdir(filepath) if isfile(join(filepath, f))] + +onlyfiles.sort() + +for f in onlyfiles : + name = basename(f) + name = name[:-4] + if f.endswith('Grimaces.csv') and (exists(filepath + name + "_static.csv")) : + print(f) + dataframe = pd.read_csv(filepath + f) + dataframe_static = pd.read_csv(filepath + name + "_static.csv") + + df_columns = [col for col in dataframe.columns if (col.endswith("_r"))] + + for i in range(len(df_columns)) : + x = dataframe.index.to_numpy() + y = dataframe[df_columns[i]].to_numpy() + + x_static = dataframe_static.index.to_numpy() + y_static = dataframe_static[df_columns[i]].to_numpy() + + plt.ylim(-0.5, 5) + plt.plot(x, y, label=f'{df_columns[i]} dynamic') + plt.plot(x_static, y_static, label=f'{df_columns[i]} static') + plt.xlabel('Frame') + plt.ylabel('Intensity detected by OpenFace') + plt.title(f'{df_columns[i]} Intensities dynamic and static') + plt.legend() + plt.savefig(filepath + name + ' ' + df_columns[i] + '.png') + plt.close() \ No newline at end of file diff --git a/data/jacob_alphas.py b/data/jacob_alphas.py new file mode 100644 index 000000000..62a55f9a2 --- /dev/null +++ b/data/jacob_alphas.py @@ -0,0 +1,271 @@ +import pandas as pd + + + +import numpy as np + + +import matplotlib.pyplot as plt + + +from scipy.interpolate import make_interp_spline + + +from os import listdir + + +from os.path import isfile, join, basename, exists + + +import sys + + + + + +filepath = sys.argv[1] + + + + + +onlyfiles = [f for f in listdir(filepath) if isfile(join(filepath, f))] + + + + + +onlyfiles.sort() + + + + + +file = open(sys.argv[2] + "slopes.txt", "w") + + + + + +nb_files = [files for files in onlyfiles if (files.endswith('.csv') and files.startswith("AU"))] + + + + + +file.write(str(len(nb_files))) + + +file.write("\n") + + + + + +alphas = [[0 for x in range(len(nb_files))] for y in range(len(nb_files))] + + +betas = [[0 for x in range(len(nb_files))] for y in range(len(nb_files))] + + + + + +row_jacob = 1 + + + + + +for f in nb_files : + + + if(not (f.endswith('_static.csv'))) : + + + name = basename(f) + + + name = name[:-4] + + + if not((exists(filepath + name + "_static.csv"))) : + + + if f.endswith('.csv') : + + + print(f) + + + name_au = basename(f) + + + name_au = name_au[:-4] + "_r" + + + dataframe = pd.read_csv(filepath + f) + + + columns = [col for col in dataframe.columns if (col.startswith("AU") and col.endswith("_r") and not(col.startswith("AU28")))] + + + + + + for i in range(len(columns)) : + + + x = dataframe.index.to_numpy() + + + y = dataframe[columns[i]].to_numpy() + + + poids = 0. + + + + + + x0 = 0 + + + y0 = 0 + + + y1 = 0 + + + x1 = 0 + + + draw = True + + + + + + for j in range(dataframe.index.size) : + + + + + + if(j == 0) : + + + dataframe.loc[j,columns[i]] = dataframe[columns[i]][j+2] + + + dataframe.loc[j+1,columns[i]] = dataframe[columns[i]][j+2] + + + x0 = poids + + + y0 = dataframe.loc[0,columns[i]] + + + if(dataframe[columns[i]][j] == 0) and (dataframe[columns[i]][0] == 0) and (poids < 1) : + + + x0 = poids + + + if(poids >= x0 + 1.5) and (draw): + + + draw = False + + + y1 = (dataframe.loc[j,columns[i]] + dataframe.loc[j+1,columns[i]] + dataframe.loc[j+2,columns[i]] + dataframe.loc[j+3,columns[i]] + dataframe.loc[j-1,columns[i]] + dataframe.loc[j-2,columns[i]] + dataframe.loc[j-3,columns[i]]) / 7. + + + x1 = poids + + + poids += 0.003 + + + + + + alpha = (y1 - y0) / (x1 - x0) + + + beta = y1 - alpha*x1 + + + + + + if(f.endswith("AU45.csv")) : + + + alpha = 0 + + + + + + if(abs(alpha) < 0.3) : + + + alpha = 0 + + + + + + alphas[row_jacob - 1][i] = alpha + + + betas[row_jacob - 1][i] = beta + + + + + + row_jacob += 1 + + + + + + + + +for i in range(len(alphas)) : + + + for j in range(len(alphas[i])) : + + if(i == j) : + file.write(str(round(alphas[i][j] , 2)) + " ") + else : + file.write(str(0) + " ") + + + file.write("\n") + + +for i in range(len(betas)) : + + + for j in range(len(betas[i])) : + + + file.write(str(round(betas[i][j] , 2)) + " ") + + + file.write("\n") + + + + + + + + +file.close() \ No newline at end of file diff --git a/data/landmarks.py b/data/landmarks.py new file mode 100644 index 000000000..3b8959d63 --- /dev/null +++ b/data/landmarks.py @@ -0,0 +1,39 @@ +from matplotlib import image +from matplotlib import pyplot as plt +import pandas as pd +import os +from os import listdir +from os.path import isfile, join +import sys + +filepath = sys.argv[1] + +# filepath = os.path.dirname(os.path.abspath(__file__)) + filepath + +print(filepath) + +data = image.imread('../build/stage/bin/Screenshot_0000.jpg') + +dataframe = pd.read_csv(filepath , sep=',') + +x_columns = [col for col in dataframe.columns if col.startswith("x_")] +y_columns = [col for col in dataframe.columns if col.startswith("y_")] + +x_list = [] +y_list = [] + +for i in range(len(dataframe.columns)): + if(dataframe.columns[i].startswith("x_")) : + x_list.append(dataframe.at[0,dataframe.columns[i]]) + if(dataframe.columns[i].startswith("y_")) : + y_list.append(dataframe.at[0,dataframe.columns[i]]) + +print(x_list) +print(len(x_list)) +print(len(y_list)) + +for i in range(len(x_list)) : + plt.plot(x_list[i], y_list[i] , marker='o' , color='red') + +plt.imshow(data) +plt.show() diff --git a/data/rewrite_csv.py b/data/rewrite_csv.py new file mode 100644 index 000000000..b71f9e8ff --- /dev/null +++ b/data/rewrite_csv.py @@ -0,0 +1,41 @@ +import pandas as pd +import os +from os import listdir +from os.path import isfile, join +import sys + +filepath = sys.argv[1] + +# filepath = os.path.dirname(os.path.abspath(__file__)) + filepath + +print(filepath) + +onlyfiles = [f for f in listdir(filepath) if isfile(join(filepath, f))] + +onlyfiles.sort() + +for f in onlyfiles : + if f.endswith('.csv') : + print(f) + dataframe = pd.read_csv(filepath + f , sep=',') + if(dataframe.columns.size == 1) : + dataframe = pd.read_csv(filepath + f , sep=';') + csv = False + if(' AU28_c' in dataframe.columns): + dataframe = dataframe.drop(columns=' AU28_c') + csv = True + if('AU28_c' in dataframe.columns): + dataframe = dataframe.drop(columns='AU28_c') + csv = True + if('face_id' in dataframe.columns): + dataframe = dataframe.drop(columns='face_id') + csv = True + if('confidence' in dataframe.columns): + dataframe = dataframe.drop(columns='confidence') + csv = True + if('success' in dataframe.columns): + dataframe = dataframe.drop(columns='success') + csv = True + + if(csv) : + dataframe.to_csv(join(filepath,f) , index=None , sep=',') diff --git a/data/rewrite_landmark_csv.py b/data/rewrite_landmark_csv.py new file mode 100644 index 000000000..dfcc07b34 --- /dev/null +++ b/data/rewrite_landmark_csv.py @@ -0,0 +1,35 @@ +import pandas as pd +import os +from os import listdir +from os.path import isfile, join +import sys + +filepath = sys.argv[1] + +# filepath = os.path.dirname(os.path.abspath(__file__)) + filepath + +print(filepath) + +onlyfiles = [f for f in listdir(filepath) if isfile(join(filepath, f))] + +onlyfiles.sort() + +for f in onlyfiles : + if f.endswith('.csv') : + print("PYTHON") + print(f) + dataframe = pd.read_csv(filepath + f , sep=',') + if(dataframe.columns.size == 1) : + dataframe = pd.read_csv(filepath + f , sep=';') + + csv = False + mv_columns = [col for col in dataframe.columns if (col.startswith("eye") or col.startswith("gaze") or col.startswith("p") or col.startswith("AU") or col.startswith("Z_") or col.startswith("Y_") or col.startswith("X_"))] + print(mv_columns) + if mv_columns : + csv = True + print("LA") + dataframe = dataframe.drop(columns=mv_columns) + print(dataframe.columns) + + if(csv): + dataframe.to_csv(join(filepath,f) , index=None , sep=',') \ No newline at end of file diff --git a/data/static_dynamic_graph.py b/data/static_dynamic_graph.py new file mode 100644 index 000000000..1d760a238 --- /dev/null +++ b/data/static_dynamic_graph.py @@ -0,0 +1,152 @@ +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +from scipy.interpolate import make_interp_spline +from os import listdir +from os.path import isfile, join, basename, exists +import sys + +filepath = sys.argv[1] + +onlyfiles = [f for f in listdir(filepath) if isfile(join(filepath, f))] + +onlyfiles.sort() + +for f in onlyfiles : + if(not (f.endswith('_static.csv'))) : + name = basename(f) + name = name[:-4] + if not((exists(filepath + name + "_static.csv"))) : + if f.endswith('.csv') : + print(f) + name_au = basename(f) + name_au = name_au[:-4] + "_r" + dataframe = pd.read_csv(filepath + f) + x = dataframe.index.to_numpy() + y = dataframe[name_au].to_numpy() + poids = 0. + + x0 = 0 + y0 = 0 + y1 = 0 + x1 = 0 + draw = True + + new_x = [] + for i in range(dataframe.index.size) : + # if(dataframe[name_au][0] != 0): + # dataframe.loc[i,name_au] = dataframe[name_au][i] - dataframe[name_au][0] + if(i == 0) : + dataframe.loc[i,name_au] = dataframe[name_au][i+2] + dataframe.loc[i+1,name_au] = dataframe[name_au][i+2] + x0 = poids + y0 = dataframe.loc[0,name_au] + if(dataframe[name_au][i] == 0) and (dataframe[name_au][0] == 0) and (poids < 1) : + x0 = poids + if(poids >= x0 + 1.5) and (draw): + draw = False + y1 = (dataframe.loc[i,name_au] + dataframe.loc[i+1,name_au] + dataframe.loc[i+2,name_au] + dataframe.loc[i+3,name_au] + dataframe.loc[i-1,name_au] + dataframe.loc[i-2,name_au] + dataframe.loc[i-3,name_au]) / 7. + x1 = poids + new_x.append(poids) + poids += 0.003 + + new_x = pd.DataFrame(new_x , columns=['weights']) + new_x = new_x['weights'].to_numpy() + + x_smooth = np.linspace(new_x.min(), new_x.max(), 1500) + spline = make_interp_spline(new_x, y, k=3) + y_smooth = spline(x_smooth) + + plt.plot(x_smooth, y_smooth, label=f'{name_au} (smoothed)') + if(not(draw)) : + plt.ylim(-1, 5.5) + plt.axline((x0,y0),(x1,y1) , color="red" , linewidth=2 , label="Approximation of linear weight") + plt.xlabel('Weight CGoGN') + plt.ylabel('Intensity detected by OpenFace') + plt.title(f'{name_au[:-2]} Intensity over Time') + plt.legend() + + #plt.show() + plt.savefig(filepath + name_au + '.png') + plt.close() + else : + if f.endswith('.csv') : + name_au = basename(f) + name_au = name_au[:-4] + "_r" + dataframe = pd.read_csv(filepath + f) + dataframe_static = pd.read_csv(filepath + name + "_static.csv") + + x = dataframe.index.to_numpy() + y = dataframe[name_au].to_numpy() + + x_static = dataframe_static.index.to_numpy() + y_static = dataframe_static[name_au].to_numpy() + + poids = 0. + poids_static = 0. + + x0 = 0 + y0 = 0 + y1 = 0 + x1 = 0 + + x0_static = 0 + y0_static = 0 + y1_static = 0 + x1_static = 0 + draw = True + draw_static = True + + new_x = [] + for i in range(dataframe.index.size) : + # if(dataframe[name_au][0] != 0): + # dataframe.loc[i,name_au] = dataframe[name_au][i] - dataframe[name_au][0] + if(i == 0) : + dataframe.loc[i,name_au] = dataframe[name_au][i+2] + dataframe.loc[i+1,name_au] = dataframe[name_au][i+2] + x0 = poids + y0 = dataframe.loc[0,name_au] + if(dataframe[name_au][i] == 0) and (dataframe[name_au][0] == 0) and (poids < 1) : + x0 = poids + if(poids >= x0 + 1.5) and (draw): + draw = False + y1 = (dataframe.loc[i,name_au] + dataframe.loc[i+1,name_au] + dataframe.loc[i+2,name_au] + dataframe.loc[i+3,name_au] + dataframe.loc[i-1,name_au] + dataframe.loc[i-2,name_au] + dataframe.loc[i-3,name_au]) / 7. + x1 = poids + new_x.append(poids) + poids += 0.003 + + new_x_static = [] + for i in range(dataframe_static.index.size) : + # if(dataframe_static[name_au][0] != 0): + # dataframe_static.loc[i,name_au] = dataframe_static[name_au][i] - dataframe_static[name_au][0] + if(i == 0) : + dataframe_static.loc[i,name_au] = dataframe_static[name_au][i+2] + dataframe_static.loc[i+1,name_au] = dataframe_static[name_au][i+2] + x0_static = poids_static + y0_static = dataframe_static.loc[0,name_au] + if(dataframe_static[name_au][i] == 0) and (dataframe_static[name_au][0] == 0) and (poids_static < 1) : + x0_static = poids_static + if(poids_static >= x0_static + 1.5) and (draw_static): + draw_static = False + y1_static = (dataframe_static.loc[i,name_au] + dataframe_static.loc[i+1,name_au] + dataframe_static.loc[i+2,name_au] + dataframe_static.loc[i+3,name_au] + dataframe_static.loc[i-1,name_au] + dataframe_static.loc[i-2,name_au] + dataframe_static.loc[i-3,name_au]) / 7. + x1_static = poids_static + new_x_static.append(poids_static) + poids_static += 0.003 + + new_x_static = pd.DataFrame(new_x_static , columns=['weights']) + new_x_static = new_x_static['weights'].to_numpy() + + plt.plot(new_x, y, label=f'{name_au} (dynamic)') + plt.plot(new_x_static, y_static, label=f'{name_au} (static)') + if(not(draw) and not(draw_static)) : + plt.ylim(-1, 5.5) + plt.axline((x0_static,y0_static),(x1_static,y1_static) , color="red" , linewidth=2 , label="Approximation of linear weight (static)") + plt.axline((x0,y0),(x1,y1) , color="blue" , linewidth=2 , label="Approximation of linear weight (dynamic)") + plt.xlabel('Weight CGoGN') + plt.ylabel('Intensity detected by OpenFace') + plt.title(f'{name_au[:-2]} Intensity over Time') + plt.legend() + + #plt.show() + plt.savefig(filepath + name_au + '.png') + plt.close() \ No newline at end of file diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 0d27c3e90..9ec10db36 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -9,4 +9,5 @@ add_subdirectory(synapse) add_subdirectory(simpleICP) # add_subdirectory(cgns-3.4.2) add_subdirectory(Blossom5) -add_subdirectory(HIGHS) \ No newline at end of file +add_subdirectory(HIGHS) +add_subdirectory(rapidcsv) \ No newline at end of file diff --git a/thirdparty/rapidcsv/CMakeLists.txt b/thirdparty/rapidcsv/CMakeLists.txt new file mode 100644 index 000000000..1513e0a1f --- /dev/null +++ b/thirdparty/rapidcsv/CMakeLists.txt @@ -0,0 +1,3 @@ +set(CGOGN_THIRDPARTY_TERMCOLOR_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE PATH "rapidcsv include directory") + +install(FILES "rapidcsv.hpp" DESTINATION "include/cgogn/thirdparty") diff --git a/thirdparty/rapidcsv/rapidcsv.h b/thirdparty/rapidcsv/rapidcsv.h new file mode 100644 index 000000000..04f76831d --- /dev/null +++ b/thirdparty/rapidcsv/rapidcsv.h @@ -0,0 +1,1918 @@ +/* + * rapidcsv.h + * + * URL: https://github.com/d99kris/rapidcsv + * Version: 8.85 + * + * Copyright (C) 2017-2025 Kristofer Berggren + * All rights reserved. + * + * rapidcsv is distributed under the BSD 3-Clause license, see LICENSE for details. + * + */ + +#pragma once + +#include +#include +#include +#ifdef HAS_CODECVT +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rapidcsv +{ +#if defined(_MSC_VER) + static const bool sPlatformHasCR = true; +#else + static const bool sPlatformHasCR = false; +#endif + static const std::vector s_Utf8BOM = { '\xef', '\xbb', '\xbf' }; + + /** + * @brief Datastructure holding parameters controlling how invalid numbers (including + * empty strings) should be handled. + */ + struct ConverterParams + { + /** + * @brief Constructor + * @param pHasDefaultConverter specifies if conversion of non-numerical strings shall be + * converted to a default numerical value, instead of causing + * an exception to be thrown (default). + * @param pDefaultFloat floating-point default value to represent invalid numbers. + * @param pDefaultInteger integer default value to represent invalid numbers. + * @param pNumericLocale specifies whether to honor LC_NUMERIC locale (default + * true). + */ + explicit ConverterParams(const bool pHasDefaultConverter = false, + const long double pDefaultFloat = std::numeric_limits::signaling_NaN(), + const long long pDefaultInteger = 0, + const bool pNumericLocale = true) + : mHasDefaultConverter(pHasDefaultConverter) + , mDefaultFloat(pDefaultFloat) + , mDefaultInteger(pDefaultInteger) + , mNumericLocale(pNumericLocale) + { + } + + /** + * @brief specifies if conversion of non-numerical strings shall be converted to a default + * numerical value, instead of causing an exception to be thrown (default). + */ + bool mHasDefaultConverter; + + /** + * @brief floating-point default value to represent invalid numbers. + */ + long double mDefaultFloat; + + /** + * @brief integer default value to represent invalid numbers. + */ + long long mDefaultInteger; + + /** + * @brief specifies whether to honor LC_NUMERIC locale. + */ + bool mNumericLocale; + }; + + /** + * @brief Exception thrown when attempting to access Document data in a datatype which + * is not supported by the Converter class. + */ + class no_converter : public std::exception + { + public: + /** + * @brief Provides details about the exception + * @returns an explanatory string + */ + const char* what() const throw() override + { + return "unsupported conversion datatype"; + } + }; + + /** + * @brief Class providing conversion to/from numerical datatypes and strings. Only + * intended for rapidcsv internal usage, but exposed externally to allow + * specialization for custom datatype conversions. + */ + template + class Converter + { + public: + /** + * @brief Constructor + * @param pConverterParams specifies how conversion of non-numerical values to + * numerical datatype shall be handled. + */ + Converter(const ConverterParams& pConverterParams) + : mConverterParams(pConverterParams) + { + } + + /** + * @brief Converts numerical value to string representation. + * @param pVal numerical value + * @param pStr output string + */ + void ToStr(const T& pVal, std::string& pStr) const + { + if (typeid(T) == typeid(int) || + typeid(T) == typeid(long) || + typeid(T) == typeid(long long) || + typeid(T) == typeid(unsigned) || + typeid(T) == typeid(unsigned long) || + typeid(T) == typeid(unsigned long long) || + typeid(T) == typeid(long double) || + typeid(T) == typeid(char)) + { + std::ostringstream out; + out << pVal; + pStr = out.str(); + } + else if (typeid(T) == typeid(float)) + { + std::ostringstream out; + out << std::setprecision(9) << pVal; + pStr = out.str(); + } + else if (typeid(T) == typeid(double)) + { + std::ostringstream out; + out << std::setprecision(17) << pVal; + pStr = out.str(); + } + else + { + throw no_converter(); + } + } + + /** + * @brief Converts string holding a numerical value to numerical datatype representation. + * @param pVal numerical value + * @param pStr output string + */ + void ToVal(const std::string& pStr, T& pVal) const + { + try + { + if (typeid(T) == typeid(int)) + { + pVal = static_cast(std::stoi(pStr)); + return; + } + else if (typeid(T) == typeid(long)) + { + pVal = static_cast(std::stol(pStr)); + return; + } + else if (typeid(T) == typeid(long long)) + { + pVal = static_cast(std::stoll(pStr)); + return; + } + else if (typeid(T) == typeid(unsigned)) + { + pVal = static_cast(std::stoul(pStr)); + return; + } + else if (typeid(T) == typeid(unsigned long)) + { + pVal = static_cast(std::stoul(pStr)); + return; + } + else if (typeid(T) == typeid(unsigned long long)) + { + pVal = static_cast(std::stoull(pStr)); + return; + } + } + catch (...) + { + if (!mConverterParams.mHasDefaultConverter) + { + throw; + } + else + { + pVal = static_cast(mConverterParams.mDefaultInteger); + return; + } + } + + try + { + if (mConverterParams.mNumericLocale) + { + if (typeid(T) == typeid(float)) + { + pVal = static_cast(std::stof(pStr)); + return; + } + else if (typeid(T) == typeid(double)) + { + pVal = static_cast(std::stod(pStr)); + return; + } + else if (typeid(T) == typeid(long double)) + { + pVal = static_cast(std::stold(pStr)); + return; + } + } + else + { + if ((typeid(T) == typeid(float)) || + (typeid(T) == typeid(double)) || + (typeid(T) == typeid(long double))) + { + std::istringstream iss(pStr); + iss.imbue(std::locale::classic()); + iss >> pVal; + if (iss.fail() || iss.bad() || !iss.eof()) + { + throw std::invalid_argument("istringstream: no conversion"); + } + return; + } + } + } + catch (...) + { + if (!mConverterParams.mHasDefaultConverter) + { + throw; + } + else + { + pVal = static_cast(mConverterParams.mDefaultFloat); + return; + } + } + + if (typeid(T) == typeid(char)) + { + pVal = static_cast(pStr[0]); + return; + } + else + { + throw no_converter(); + } + } + + private: + const ConverterParams& mConverterParams; + }; + + /** + * @brief Specialized implementation handling string to string conversion. + * @param pVal string + * @param pStr string + */ + template<> + inline void Converter::ToStr(const std::string& pVal, std::string& pStr) const + { + pStr = pVal; + } + + /** + * @brief Specialized implementation handling string to string conversion. + * @param pVal string + * @param pStr string + */ + template<> + inline void Converter::ToVal(const std::string& pStr, std::string& pVal) const + { + pVal = pStr; + } + + template + using ConvFunc = std::function; + + /** + * @brief Datastructure holding parameters controlling which row and column should be + * treated as labels. + */ + struct LabelParams + { + /** + * @brief Constructor + * @param pColumnNameIdx specifies the zero-based row index of the column labels, setting + * it to -1 prevents column lookup by label name, and gives access + * to all rows as document data. Default: 0 + * @param pRowNameIdx specifies the zero-based column index of the row labels, setting + * it to -1 prevents row lookup by label name, and gives access + * to all columns as document data. Default: -1 + */ + explicit LabelParams(const int pColumnNameIdx = 0, const int pRowNameIdx = -1) + : mColumnNameIdx(pColumnNameIdx) + , mRowNameIdx(pRowNameIdx) + { + if (mColumnNameIdx < -1) + { + const std::string errStr = "invalid column name index " + + std::to_string(mColumnNameIdx) + " < -1"; + throw std::out_of_range(errStr); + } + + if (mRowNameIdx < -1) + { + const std::string errStr = "invalid row name index " + + std::to_string(mRowNameIdx) + " < -1"; + throw std::out_of_range(errStr); + } + } + + /** + * @brief specifies the zero-based row index of the column labels. + */ + int mColumnNameIdx; + + /** + * @brief specifies the zero-based column index of the row labels. + */ + int mRowNameIdx; + }; + + /** + * @brief Datastructure holding parameters controlling how the CSV data fields are separated. + */ + struct SeparatorParams + { + /** + * @brief Constructor + * @param pSeparator specifies the column separator (default ','). + * @param pTrim specifies whether to trim leading and trailing spaces from + * cells read (default false). + * @param pHasCR specifies whether a new document (i.e. not an existing document read) + * should use CR/LF instead of only LF (default is to use standard + * behavior of underlying platforms - CR/LF for Win, and LF for others). + * @param pQuotedLinebreaks specifies whether to allow line breaks in quoted text (default false) + * @param pAutoQuote specifies whether to automatically dequote data during read, and add + * quotes during write (default true). + * @param pQuoteChar specifies the quote character (default '\"'). + */ + explicit SeparatorParams(const char pSeparator = ',', const bool pTrim = false, + const bool pHasCR = sPlatformHasCR, const bool pQuotedLinebreaks = false, + const bool pAutoQuote = true, const char pQuoteChar = '"') + : mSeparator(pSeparator) + , mTrim(pTrim) + , mHasCR(pHasCR) + , mQuotedLinebreaks(pQuotedLinebreaks) + , mAutoQuote(pAutoQuote) + , mQuoteChar(pQuoteChar) + { + } + + /** + * @brief specifies the column separator. + */ + char mSeparator; + + /** + * @brief specifies whether to trim leading and trailing spaces from cells read. + */ + bool mTrim; + + /** + * @brief specifies whether new documents should use CR/LF instead of LF. + */ + bool mHasCR; + + /** + * @brief specifies whether to allow line breaks in quoted text. + */ + bool mQuotedLinebreaks; + + /** + * @brief specifies whether to automatically dequote cell data. + */ + bool mAutoQuote; + + /** + * @brief specifies the quote character. + */ + char mQuoteChar; + }; + + /** + * @brief Datastructure holding parameters controlling how special line formats should be + * treated. + */ + struct LineReaderParams + { + /** + * @brief Constructor + * @param pSkipCommentLines specifies whether to skip lines prefixed with + * mCommentPrefix. Default: false + * @param pCommentPrefix specifies which prefix character to indicate a comment + * line. Default: # + * @param pSkipEmptyLines specifies whether to skip empty lines. Default: false + */ + explicit LineReaderParams(const bool pSkipCommentLines = false, + const char pCommentPrefix = '#', + const bool pSkipEmptyLines = false) + : mSkipCommentLines(pSkipCommentLines) + , mCommentPrefix(pCommentPrefix) + , mSkipEmptyLines(pSkipEmptyLines) + { + } + + /** + * @brief specifies whether to skip lines prefixed with mCommentPrefix. + */ + bool mSkipCommentLines; + + /** + * @brief specifies which prefix character to indicate a comment line. + */ + char mCommentPrefix; + + /** + * @brief specifies whether to skip empty lines. + */ + bool mSkipEmptyLines; + }; + + /** + * @brief Class representing a CSV document. + */ + class Document + { + public: + /** + * @brief Constructor + * @param pPath specifies the path of an existing CSV-file to populate the Document + * data with. + * @param pLabelParams specifies which row and column should be treated as labels. + * @param pSeparatorParams specifies which field and row separators should be used. + * @param pConverterParams specifies how invalid numbers (including empty strings) should be + * handled. + * @param pLineReaderParams specifies how special line formats should be treated. + */ + explicit Document(const std::string& pPath = std::string(), + const LabelParams& pLabelParams = LabelParams(), + const SeparatorParams& pSeparatorParams = SeparatorParams(), + const ConverterParams& pConverterParams = ConverterParams(), + const LineReaderParams& pLineReaderParams = LineReaderParams()) + : mPath(pPath) + , mLabelParams(pLabelParams) + , mSeparatorParams(pSeparatorParams) + , mConverterParams(pConverterParams) + , mLineReaderParams(pLineReaderParams) + , mData() + , mColumnNames() + , mRowNames() + { + if (!mPath.empty()) + { + ReadCsv(); + } + } + + /** + * @brief Constructor + * @param pStream specifies a binary input stream to read CSV data from. + * @param pLabelParams specifies which row and column should be treated as labels. + * @param pSeparatorParams specifies which field and row separators should be used. + * @param pConverterParams specifies how invalid numbers (including empty strings) should be + * handled. + * @param pLineReaderParams specifies how special line formats should be treated. + */ + explicit Document(std::istream& pStream, + const LabelParams& pLabelParams = LabelParams(), + const SeparatorParams& pSeparatorParams = SeparatorParams(), + const ConverterParams& pConverterParams = ConverterParams(), + const LineReaderParams& pLineReaderParams = LineReaderParams()) + : mPath() + , mLabelParams(pLabelParams) + , mSeparatorParams(pSeparatorParams) + , mConverterParams(pConverterParams) + , mLineReaderParams(pLineReaderParams) + , mData() + , mColumnNames() + , mRowNames() + { + ReadCsv(pStream); + } + + /** + * @brief Read Document data from file. + * @param pPath specifies the path of an existing CSV-file to populate the Document + * data with. + * @param pLabelParams specifies which row and column should be treated as labels. + * @param pSeparatorParams specifies which field and row separators should be used. + * @param pConverterParams specifies how invalid numbers (including empty strings) should be + * handled. + * @param pLineReaderParams specifies how special line formats should be treated. + */ + void Load(const std::string& pPath, + const LabelParams& pLabelParams = LabelParams(), + const SeparatorParams& pSeparatorParams = SeparatorParams(), + const ConverterParams& pConverterParams = ConverterParams(), + const LineReaderParams& pLineReaderParams = LineReaderParams()) + { + mPath = pPath; + mLabelParams = pLabelParams; + mSeparatorParams = pSeparatorParams; + mConverterParams = pConverterParams; + mLineReaderParams = pLineReaderParams; + ReadCsv(); + } + + /** + * @brief Read Document data from stream. + * @param pStream specifies a binary input stream to read CSV data from. + * @param pLabelParams specifies which row and column should be treated as labels. + * @param pSeparatorParams specifies which field and row separators should be used. + * @param pConverterParams specifies how invalid numbers (including empty strings) should be + * handled. + * @param pLineReaderParams specifies how special line formats should be treated. + */ + void Load(std::istream& pStream, + const LabelParams& pLabelParams = LabelParams(), + const SeparatorParams& pSeparatorParams = SeparatorParams(), + const ConverterParams& pConverterParams = ConverterParams(), + const LineReaderParams& pLineReaderParams = LineReaderParams()) + { + mPath = ""; + mLabelParams = pLabelParams; + mSeparatorParams = pSeparatorParams; + mConverterParams = pConverterParams; + mLineReaderParams = pLineReaderParams; + ReadCsv(pStream); + } + + /** + * @brief Write Document data to file. + * @param pPath optionally specifies the path where the CSV-file will be created + * (if not specified, the original path provided when creating or + * loading the Document data will be used). + */ + void Save(const std::string& pPath = std::string()) + { + if (!pPath.empty()) + { + mPath = pPath; + } + WriteCsv(); + } + + /** + * @brief Write Document data to stream. + * @param pStream specifies a binary output stream to write the data to. + */ + void Save(std::ostream& pStream) const + { + WriteCsv(pStream); + } + + /** + * @brief Clears loaded Document data. + * + */ + void Clear() + { + mData.clear(); + mColumnNames.clear(); + mRowNames.clear(); +#ifdef HAS_CODECVT + mIsUtf16 = false; + mIsLE = false; +#endif + mHasUtf8BOM = false; + } + + /** + * @brief Get column index by name. + * @param pColumnName column label name. + * @returns zero-based column index. + */ + int GetColumnIdx(const std::string& pColumnName) const + { + if (mLabelParams.mColumnNameIdx >= 0) + { + if (mColumnNames.find(pColumnName) != mColumnNames.end()) + { + return static_cast(mColumnNames.at(pColumnName)) - (mLabelParams.mRowNameIdx + 1); + } + } + return -1; + } + + /** + * @brief Get column by index. + * @param pColumnIdx zero-based column index. + * @returns vector of column data. + */ + template + std::vector GetColumn(const size_t pColumnIdx) const + { + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + std::vector column; + Converter converter(mConverterParams); + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) + { + if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx) + { + if (dataColumnIdx < itRow->size()) + { + T val; + converter.ToVal(itRow->at(dataColumnIdx), val); + column.push_back(val); + } + else + { + const std::string errStr = "requested column index " + + std::to_string(pColumnIdx) + " >= " + + std::to_string(itRow->size() - GetDataColumnIndex(0)) + + " (number of columns on row index " + + std::to_string(std::distance(mData.begin(), itRow) - + (mLabelParams.mColumnNameIdx + 1)) + ")"; + throw std::out_of_range(errStr); + } + } + } + return column; + } + + /** + * @brief Get column by index. + * @param pColumnIdx zero-based column index. + * @param pToVal conversion function. + * @returns vector of column data. + */ + template + std::vector GetColumn(const size_t pColumnIdx, ConvFunc pToVal) const + { + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + std::vector column; + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) + { + if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx) + { + T val; + pToVal(itRow->at(dataColumnIdx), val); + column.push_back(val); + } + } + return column; + } + + /** + * @brief Get column by name. + * @param pColumnName column label name. + * @returns vector of column data. + */ + template + std::vector GetColumn(const std::string& pColumnName) const + { + const int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + return GetColumn(static_cast(columnIdx)); + } + + /** + * @brief Get column by name. + * @param pColumnName column label name. + * @param pToVal conversion function. + * @returns vector of column data. + */ + template + std::vector GetColumn(const std::string& pColumnName, ConvFunc pToVal) const + { + const int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + return GetColumn(static_cast(columnIdx), pToVal); + } + + /** + * @brief Set column by index. + * @param pColumnIdx zero-based column index. + * @param pColumn vector of column data. + */ + template + void SetColumn(const size_t pColumnIdx, const std::vector& pColumn) + { + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + + while (GetDataRowIndex(pColumn.size()) > GetDataRowCount()) + { + std::vector row; + row.resize(GetDataColumnCount()); + mData.push_back(row); + } + + if ((dataColumnIdx + 1) > GetDataColumnCount()) + { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) + { + if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx) + { + itRow->resize(GetDataColumnIndex(dataColumnIdx + 1)); + } + } + } + + Converter converter(mConverterParams); + for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow) + { + std::string str; + converter.ToStr(*itRow, str); + mData.at(static_cast(std::distance(pColumn.begin(), itRow) + mLabelParams.mColumnNameIdx + 1)).at( + dataColumnIdx) = str; + } + } + + /** + * @brief Set column by name. + * @param pColumnName column label name. + * @param pColumn vector of column data. + */ + template + void SetColumn(const std::string& pColumnName, const std::vector& pColumn) + { + const int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + SetColumn(static_cast(columnIdx), pColumn); + } + + /** + * @brief Remove column by index. + * @param pColumnIdx zero-based column index. + */ + void RemoveColumn(const size_t pColumnIdx) + { + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) + { + if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx) + { + itRow->erase(itRow->begin() + static_cast(dataColumnIdx)); + } + } + + UpdateColumnNames(); + } + + /** + * @brief Remove column by name. + * @param pColumnName column label name. + */ + void RemoveColumn(const std::string& pColumnName) + { + int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + + RemoveColumn(static_cast(columnIdx)); + } + + /** + * @brief Insert column at specified index. + * @param pColumnIdx zero-based column index. + * @param pColumn vector of column data (optional argument). + * @param pColumnName column label name (optional argument). + */ + template + void InsertColumn(const size_t pColumnIdx, const std::vector& pColumn = std::vector(), + const std::string& pColumnName = std::string()) + { + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + + std::vector column; + if (pColumn.empty()) + { + column.resize(GetDataRowCount()); + } + else + { + column.resize(GetDataRowIndex(pColumn.size())); + Converter converter(mConverterParams); + for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow) + { + std::string str; + converter.ToStr(*itRow, str); + const size_t rowIdx = + static_cast(std::distance(pColumn.begin(), itRow) + (mLabelParams.mColumnNameIdx + 1)); + column.at(rowIdx) = str; + } + } + + while (column.size() > GetDataRowCount()) + { + std::vector row; + const size_t columnCount = std::max(static_cast(mLabelParams.mColumnNameIdx + 1), + GetDataColumnCount()); + row.resize(columnCount); + mData.push_back(row); + } + + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) + { + if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx) + { + const size_t rowIdx = static_cast(std::distance(mData.begin(), itRow)); + itRow->insert(itRow->begin() + static_cast(dataColumnIdx), column.at(rowIdx)); + } + } + + if (!pColumnName.empty()) + { + SetColumnName(pColumnIdx, pColumnName); + } + + UpdateColumnNames(); + } + + /** + * @brief Get number of data columns (excluding label columns). + * @returns column count. + */ + size_t GetColumnCount() const + { + const size_t firstRow = static_cast((mLabelParams.mColumnNameIdx >= 0) ? mLabelParams.mColumnNameIdx : 0); + const int count = static_cast((mData.size() > firstRow) ? mData.at(firstRow).size() : 0) - + (mLabelParams.mRowNameIdx + 1); + return (count >= 0) ? static_cast(count) : 0; + } + + /** + * @brief Get row index by name. + * @param pRowName row label name. + * @returns zero-based row index. + */ + int GetRowIdx(const std::string& pRowName) const + { + if (mLabelParams.mRowNameIdx >= 0) + { + if (mRowNames.find(pRowName) != mRowNames.end()) + { + return static_cast(mRowNames.at(pRowName)) - (mLabelParams.mColumnNameIdx + 1); + } + } + return -1; + } + + /** + * @brief Get row by index. + * @param pRowIdx zero-based row index. + * @returns vector of row data. + */ + template + std::vector GetRow(const size_t pRowIdx) const + { + const size_t dataRowIdx = GetDataRowIndex(pRowIdx); + std::vector row; + Converter converter(mConverterParams); + for (auto itCol = mData.at(dataRowIdx).begin(); itCol != mData.at(dataRowIdx).end(); ++itCol) + { + if (std::distance(mData.at(dataRowIdx).begin(), itCol) > mLabelParams.mRowNameIdx) + { + T val; + converter.ToVal(*itCol, val); + row.push_back(val); + } + } + return row; + } + + /** + * @brief Get row by index. + * @param pRowIdx zero-based row index. + * @param pToVal conversion function. + * @returns vector of row data. + */ + template + std::vector GetRow(const size_t pRowIdx, ConvFunc pToVal) const + { + const size_t dataRowIdx = GetDataRowIndex(pRowIdx); + std::vector row; + Converter converter(mConverterParams); + for (auto itCol = mData.at(dataRowIdx).begin(); itCol != mData.at(dataRowIdx).end(); ++itCol) + { + if (std::distance(mData.at(dataRowIdx).begin(), itCol) > mLabelParams.mRowNameIdx) + { + T val; + pToVal(*itCol, val); + row.push_back(val); + } + } + return row; + } + + /** + * @brief Get row by name. + * @param pRowName row label name. + * @returns vector of row data. + */ + template + std::vector GetRow(const std::string& pRowName) const + { + int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + return GetRow(static_cast(rowIdx)); + } + + /** + * @brief Get row by name. + * @param pRowName row label name. + * @param pToVal conversion function. + * @returns vector of row data. + */ + template + std::vector GetRow(const std::string& pRowName, ConvFunc pToVal) const + { + int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + return GetRow(static_cast(rowIdx), pToVal); + } + + /** + * @brief Set row by index. + * @param pRowIdx zero-based row index. + * @param pRow vector of row data. + */ + template + void SetRow(const size_t pRowIdx, const std::vector& pRow) + { + const size_t dataRowIdx = GetDataRowIndex(pRowIdx); + + while ((dataRowIdx + 1) > GetDataRowCount()) + { + std::vector row; + row.resize(GetDataColumnCount()); + mData.push_back(row); + } + + if (pRow.size() > GetDataColumnCount()) + { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) + { + if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx) + { + itRow->resize(GetDataColumnIndex(pRow.size())); + } + } + } + + Converter converter(mConverterParams); + for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol) + { + std::string str; + converter.ToStr(*itCol, str); + mData.at(dataRowIdx).at(static_cast(std::distance(pRow.begin(), + itCol) + mLabelParams.mRowNameIdx + 1)) = str; + } + } + + /** + * @brief Set row by name. + * @param pRowName row label name. + * @param pRow vector of row data. + */ + template + void SetRow(const std::string& pRowName, const std::vector& pRow) + { + int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + return SetRow(static_cast(rowIdx), pRow); + } + + /** + * @brief Remove row by index. + * @param pRowIdx zero-based row index. + */ + void RemoveRow(const size_t pRowIdx) + { + const size_t dataRowIdx = GetDataRowIndex(pRowIdx); + mData.erase(mData.begin() + static_cast(dataRowIdx)); + UpdateRowNames(); + } + + /** + * @brief Remove row by name. + * @param pRowName row label name. + */ + void RemoveRow(const std::string& pRowName) + { + int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + + RemoveRow(static_cast(rowIdx)); + } + + /** + * @brief Insert row at specified index. + * @param pRowIdx zero-based row index. + * @param pRow vector of row data (optional argument). + * @param pRowName row label name (optional argument). + */ + template + void InsertRow(const size_t pRowIdx, const std::vector& pRow = std::vector(), + const std::string& pRowName = std::string()) + { + const size_t rowIdx = GetDataRowIndex(pRowIdx); + + std::vector row; + if (pRow.empty()) + { + row.resize(GetDataColumnCount()); + } + else + { + row.resize(GetDataColumnIndex(pRow.size())); + Converter converter(mConverterParams); + for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol) + { + std::string str; + converter.ToStr(*itCol, str); + row.at(static_cast(std::distance(pRow.begin(), itCol) + mLabelParams.mRowNameIdx + 1)) = str; + } + } + + while (rowIdx > GetDataRowCount()) + { + std::vector tempRow; + tempRow.resize(GetDataColumnCount()); + mData.push_back(tempRow); + } + + mData.insert(mData.begin() + static_cast(rowIdx), row); + + if (!pRowName.empty()) + { + SetRowName(pRowIdx, pRowName); + } + + UpdateRowNames(); + } + + /** + * @brief Get number of data rows (excluding label rows). + * @returns row count. + */ + size_t GetRowCount() const + { + const int count = static_cast(mData.size()) - (mLabelParams.mColumnNameIdx + 1); + return (count >= 0) ? static_cast(count) : 0; + } + + /** + * @brief Get cell by index. + * @param pColumnIdx zero-based column index. + * @param pRowIdx zero-based row index. + * @returns cell data. + */ + template + T GetCell(const size_t pColumnIdx, const size_t pRowIdx) const + { + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + const size_t dataRowIdx = GetDataRowIndex(pRowIdx); + + T val; + Converter converter(mConverterParams); + converter.ToVal(mData.at(dataRowIdx).at(dataColumnIdx), val); + return val; + } + + /** + * @brief Get cell by index. + * @param pColumnIdx zero-based column index. + * @param pRowIdx zero-based row index. + * @param pToVal conversion function. + * @returns cell data. + */ + template + T GetCell(const size_t pColumnIdx, const size_t pRowIdx, ConvFunc pToVal) const + { + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + const size_t dataRowIdx = GetDataRowIndex(pRowIdx); + + T val; + pToVal(mData.at(dataRowIdx).at(dataColumnIdx), val); + return val; + } + + /** + * @brief Get cell by name. + * @param pColumnName column label name. + * @param pRowName row label name. + * @returns cell data. + */ + template + T GetCell(const std::string& pColumnName, const std::string& pRowName) const + { + const int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + + const int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + + return GetCell(static_cast(columnIdx), static_cast(rowIdx)); + } + + /** + * @brief Get cell by name. + * @param pColumnName column label name. + * @param pRowName row label name. + * @param pToVal conversion function. + * @returns cell data. + */ + template + T GetCell(const std::string& pColumnName, const std::string& pRowName, ConvFunc pToVal) const + { + const int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + + const int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + + return GetCell(static_cast(columnIdx), static_cast(rowIdx), pToVal); + } + + /** + * @brief Get cell by column name and row index. + * @param pColumnName column label name. + * @param pRowIdx zero-based row index. + * @returns cell data. + */ + template + T GetCell(const std::string& pColumnName, const size_t pRowIdx) const + { + const int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + + return GetCell(static_cast(columnIdx), pRowIdx); + } + + /** + * @brief Get cell by column name and row index. + * @param pColumnName column label name. + * @param pRowIdx zero-based row index. + * @param pToVal conversion function. + * @returns cell data. + */ + template + T GetCell(const std::string& pColumnName, const size_t pRowIdx, ConvFunc pToVal) const + { + const int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + + return GetCell(static_cast(columnIdx), pRowIdx, pToVal); + } + + /** + * @brief Get cell by column index and row name. + * @param pColumnIdx zero-based column index. + * @param pRowName row label name. + * @returns cell data. + */ + template + T GetCell(const size_t pColumnIdx, const std::string& pRowName) const + { + const int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + + return GetCell(pColumnIdx, static_cast(rowIdx)); + } + + /** + * @brief Get cell by column index and row name. + * @param pColumnIdx zero-based column index. + * @param pRowName row label name. + * @param pToVal conversion function. + * @returns cell data. + */ + template + T GetCell(const size_t pColumnIdx, const std::string& pRowName, ConvFunc pToVal) const + { + const int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + + return GetCell(pColumnIdx, static_cast(rowIdx), pToVal); + } + + /** + * @brief Set cell by index. + * @param pRowIdx zero-based row index. + * @param pColumnIdx zero-based column index. + * @param pCell cell data. + */ + template + void SetCell(const size_t pColumnIdx, const size_t pRowIdx, const T& pCell) + { + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + const size_t dataRowIdx = GetDataRowIndex(pRowIdx); + + while ((dataRowIdx + 1) > GetDataRowCount()) + { + std::vector row; + row.resize(GetDataColumnCount()); + mData.push_back(row); + } + + if ((dataColumnIdx + 1) > GetDataColumnCount()) + { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) + { + if (std::distance(mData.begin(), itRow) >= mLabelParams.mColumnNameIdx) + { + itRow->resize(dataColumnIdx + 1); + } + } + } + + std::string str; + Converter converter(mConverterParams); + converter.ToStr(pCell, str); + mData.at(dataRowIdx).at(dataColumnIdx) = str; + } + + /** + * @brief Set cell by name. + * @param pColumnName column label name. + * @param pRowName row label name. + * @param pCell cell data. + */ + template + void SetCell(const std::string& pColumnName, const std::string& pRowName, const T& pCell) + { + const int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + + const int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + + SetCell(static_cast(columnIdx), static_cast(rowIdx), pCell); + } + + /** + * @brief Set cell by column index and row name. + * @param pColumnIdx zero-based column index. + * @param pRowName row label name. + * @param pCell cell data. + */ + template + void SetCell(const size_t pColumnIdx, const std::string& pRowName, const T& pCell) + { + const int rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) + { + throw std::out_of_range("row not found: " + pRowName); + } + + SetCell(pColumnIdx, static_cast(rowIdx), pCell); + } + + /** + * @brief Set cell by column name and row index. + * @param pColumnName column label name. + * @param pRowIdx zero-based row index. + * @param pCell cell data. + */ + template + void SetCell(const std::string& pColumnName, const size_t pRowIdx, const T& pCell) + { + const int columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) + { + throw std::out_of_range("column not found: " + pColumnName); + } + + SetCell(static_cast(columnIdx), pRowIdx, pCell); + } + + /** + * @brief Get column name + * @param pColumnIdx zero-based column index. + * @returns column name. + */ + std::string GetColumnName(const size_t pColumnIdx) const + { + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + if (mLabelParams.mColumnNameIdx < 0) + { + throw std::out_of_range("column name row index < 0: " + std::to_string(mLabelParams.mColumnNameIdx)); + } + + return mData.at(static_cast(mLabelParams.mColumnNameIdx)).at(dataColumnIdx); + } + + /** + * @brief Set column name + * @param pColumnIdx zero-based column index. + * @param pColumnName column name. + */ + void SetColumnName(size_t pColumnIdx, const std::string& pColumnName) + { + if (mLabelParams.mColumnNameIdx < 0) + { + throw std::out_of_range("column name row index < 0: " + std::to_string(mLabelParams.mColumnNameIdx)); + } + + const size_t dataColumnIdx = GetDataColumnIndex(pColumnIdx); + mColumnNames[pColumnName] = dataColumnIdx; + + // increase table size if necessary: + const size_t rowIdx = static_cast(mLabelParams.mColumnNameIdx); + if (rowIdx >= mData.size()) + { + mData.resize(rowIdx + 1); + } + auto& row = mData[rowIdx]; + if (dataColumnIdx >= row.size()) + { + row.resize(dataColumnIdx + 1); + } + + mData.at(static_cast(mLabelParams.mColumnNameIdx)).at(dataColumnIdx) = pColumnName; + } + + /** + * @brief Get column names + * @returns vector of column names. + */ + std::vector GetColumnNames() const + { + if (mLabelParams.mColumnNameIdx >= 0) + { + return std::vector(mData.at(static_cast(mLabelParams.mColumnNameIdx)).begin() + + (mLabelParams.mRowNameIdx + 1), + mData.at(static_cast(mLabelParams.mColumnNameIdx)).end()); + } + + return std::vector(); + } + + /** + * @brief Get row name + * @param pRowIdx zero-based column index. + * @returns row name. + */ + std::string GetRowName(const size_t pRowIdx) const + { + const size_t dataRowIdx = GetDataRowIndex(pRowIdx); + if (mLabelParams.mRowNameIdx < 0) + { + throw std::out_of_range("row name column index < 0: " + std::to_string(mLabelParams.mRowNameIdx)); + } + + return mData.at(dataRowIdx).at(static_cast(mLabelParams.mRowNameIdx)); + } + + /** + * @brief Set row name + * @param pRowIdx zero-based row index. + * @param pRowName row name. + */ + void SetRowName(size_t pRowIdx, const std::string& pRowName) + { + const size_t dataRowIdx = GetDataRowIndex(pRowIdx); + mRowNames[pRowName] = dataRowIdx; + if (mLabelParams.mRowNameIdx < 0) + { + throw std::out_of_range("row name column index < 0: " + std::to_string(mLabelParams.mRowNameIdx)); + } + + // increase table size if necessary: + if (dataRowIdx >= mData.size()) + { + mData.resize(dataRowIdx + 1); + } + auto& row = mData[dataRowIdx]; + if (mLabelParams.mRowNameIdx >= static_cast(row.size())) + { + row.resize(static_cast(mLabelParams.mRowNameIdx) + 1); + } + + mData.at(dataRowIdx).at(static_cast(mLabelParams.mRowNameIdx)) = pRowName; + } + + /** + * @brief Get row names + * @returns vector of row names. + */ + std::vector GetRowNames() const + { + std::vector rownames; + if (mLabelParams.mRowNameIdx >= 0) + { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) + { + if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx) + { + rownames.push_back(itRow->at(static_cast(mLabelParams.mRowNameIdx))); + } + } + } + return rownames; + } + + private: + void ReadCsv() + { + std::ifstream stream; + stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + stream.open(mPath, std::ios::binary); + ReadCsv(stream); + } + + void ReadCsv(std::istream& pStream) + { + Clear(); + pStream.seekg(0, std::ios::end); + std::streamsize length = pStream.tellg(); + pStream.seekg(0, std::ios::beg); + +#ifdef HAS_CODECVT + std::vector bom2b(2, '\0'); + if (length >= 2) + { + pStream.read(bom2b.data(), 2); + pStream.seekg(0, std::ios::beg); + } + + static const std::vector bomU16le = { '\xff', '\xfe' }; + static const std::vector bomU16be = { '\xfe', '\xff' }; + if ((bom2b == bomU16le) || (bom2b == bomU16be)) + { + mIsUtf16 = true; + mIsLE = (bom2b == bomU16le); + + std::wifstream wstream; + wstream.exceptions(std::wifstream::failbit | std::wifstream::badbit); + wstream.open(mPath, std::ios::binary); + if (mIsLE) + { + wstream.imbue(std::locale(wstream.getloc(), + new std::codecvt_utf16(std::consume_header | + std::little_endian)>)); + } + else + { + wstream.imbue(std::locale(wstream.getloc(), + new std::codecvt_utf16)); + } + std::wstringstream wss; + wss << wstream.rdbuf(); + std::string utf8 = ToString(wss.str()); + std::stringstream ss(utf8); + ParseCsv(ss, static_cast(utf8.size())); + } + else +#endif + { + // check for UTF-8 Byte order mark and skip it when found + if (length >= 3) + { + std::vector bom3b(3, '\0'); + pStream.read(bom3b.data(), 3); + + if (bom3b != s_Utf8BOM) + { + // file does not start with a UTF-8 Byte order mark + pStream.seekg(0, std::ios::beg); + } + else + { + // file did start with a UTF-8 Byte order mark, simply skip it + length -= 3; + mHasUtf8BOM = true; + } + } + + ParseCsv(pStream, length); + } + } + + void ParseCsv(std::istream& pStream, std::streamsize p_FileLength) + { + const std::streamsize bufLength = 64 * 1024; + std::vector buffer(bufLength); + std::vector row; + std::string cell; + bool quoted = false; + int cr = 0; + int lf = 0; + + while (p_FileLength > 0) + { + const std::streamsize toReadLength = std::min(p_FileLength, bufLength); + pStream.read(buffer.data(), toReadLength); + + // With user-specified istream opened in non-binary mode on windows, we may have a + // data length mismatch, so ensure we don't parse outside actual data length read. + const std::streamsize readLength = pStream.gcount(); + if (readLength <= 0) + { + break; + } + + for (size_t i = 0; i < static_cast(readLength); ++i) + { + if (buffer[i] == mSeparatorParams.mQuoteChar) + { + if (cell.empty() || (cell[0] == mSeparatorParams.mQuoteChar)) + { + quoted = !quoted; + } + else if (mSeparatorParams.mTrim) + { + // allow whitespace before first mQuoteChar + const auto firstQuote = std::find(cell.begin(), cell.end(), mSeparatorParams.mQuoteChar); + if (std::all_of(cell.begin(), firstQuote, [](int ch) { return isspace(ch); })) + { + quoted = !quoted; + } + } + cell += buffer[i]; + } + else if (buffer[i] == mSeparatorParams.mSeparator) + { + if (!quoted) + { + row.push_back(Unquote(Trim(cell))); + cell.clear(); + } + else + { + cell += buffer[i]; + } + } + else if (buffer[i] == '\r') + { + if (mSeparatorParams.mQuotedLinebreaks && quoted) + { + cell += buffer[i]; + } + else + { + ++cr; + } + } + else if (buffer[i] == '\n') + { + if (mSeparatorParams.mQuotedLinebreaks && quoted) + { + cell += buffer[i]; + } + else + { + ++lf; + if (mLineReaderParams.mSkipEmptyLines && row.empty() && cell.empty()) + { + // skip empty line + } + else + { + row.push_back(Unquote(Trim(cell))); + + if (mLineReaderParams.mSkipCommentLines && !row.at(0).empty() && + (row.at(0)[0] == mLineReaderParams.mCommentPrefix)) + { + // skip comment line + } + else + { + mData.push_back(row); + } + + cell.clear(); + row.clear(); + quoted = false; + } + } + } + else + { + cell += buffer[i]; + } + } + p_FileLength -= readLength; + } + + // Handle last row / cell without linebreak + if (row.empty() && cell.empty()) + { + // skip empty trailing line + } + else + { + row.push_back(Unquote(Trim(cell))); + + if (mLineReaderParams.mSkipCommentLines && !row.at(0).empty() && + (row.at(0)[0] == mLineReaderParams.mCommentPrefix)) + { + // skip comment line + } + else + { + mData.push_back(row); + } + + cell.clear(); + row.clear(); + quoted = false; + } + + // Assume CR/LF if at least half the linebreaks have CR + mSeparatorParams.mHasCR = (cr > (lf / 2)); + + // Set up column labels + UpdateColumnNames(); + + // Set up row labels + UpdateRowNames(); + } + + void WriteCsv() const + { +#ifdef HAS_CODECVT + if (mIsUtf16) + { + std::stringstream ss; + WriteCsv(ss); + std::string utf8 = ss.str(); + std::wstring wstr = ToWString(utf8); + + std::wofstream wstream; + wstream.exceptions(std::wofstream::failbit | std::wofstream::badbit); + wstream.open(mPath, std::ios::binary | std::ios::trunc); + + if (mIsLE) + { + wstream.imbue(std::locale(wstream.getloc(), + new std::codecvt_utf16(std::little_endian)>)); + } + else + { + wstream.imbue(std::locale(wstream.getloc(), + new std::codecvt_utf16)); + } + + wstream << static_cast(0xfeff); + wstream << wstr; + } + else +#endif + { + std::ofstream stream; + stream.exceptions(std::ofstream::failbit | std::ofstream::badbit); + stream.open(mPath, std::ios::binary | std::ios::trunc); + if (mHasUtf8BOM) + { + stream.write(s_Utf8BOM.data(), 3); + } + + WriteCsv(stream); + } + } + + void WriteCsv(std::ostream& pStream) const + { + for (auto itr = mData.begin(); itr != mData.end(); ++itr) + { + for (auto itc = itr->begin(); itc != itr->end(); ++itc) + { + if (mSeparatorParams.mAutoQuote && + ((itc->find(mSeparatorParams.mSeparator) != std::string::npos) || + (itc->find(' ') != std::string::npos) || + (itc->find('\n') != std::string::npos))) + { + // escape quotes in string + std::string str = *itc; + const std::string quoteCharStr = std::string(1, mSeparatorParams.mQuoteChar); + ReplaceString(str, quoteCharStr, quoteCharStr + quoteCharStr); + + pStream << quoteCharStr << str << quoteCharStr; + } + else + { + pStream << *itc; + } + + if (std::distance(itc, itr->end()) > 1) + { + pStream << mSeparatorParams.mSeparator; + } + } + pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n"); + } + } + + size_t GetDataRowCount() const + { + return mData.size(); + } + + size_t GetDataColumnCount() const + { + const size_t firstDataRow = static_cast((mLabelParams.mColumnNameIdx >= 0) ? mLabelParams.mColumnNameIdx : 0); + return (mData.size() > firstDataRow) ? mData.at(firstDataRow).size() : 0; + } + + inline size_t GetDataRowIndex(const size_t pRowIdx) const + { + const size_t firstDataRow = static_cast((mLabelParams.mColumnNameIdx + 1 >= 0) ? mLabelParams.mColumnNameIdx + 1 : 0); + return pRowIdx + firstDataRow; + } + + inline size_t GetDataColumnIndex(const size_t pColumnIdx) const + { + const size_t firstDataColumn = static_cast((mLabelParams.mRowNameIdx + 1 >= 0) ? mLabelParams.mRowNameIdx + 1 : 0); + return pColumnIdx + firstDataColumn; + } + + std::string Trim(const std::string& pStr) const + { + if (mSeparatorParams.mTrim) + { + std::string str = pStr; + + // ltrim + str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](int ch) { return !isspace(ch); })); + + // rtrim + str.erase(std::find_if(str.rbegin(), str.rend(), [](int ch) { return !isspace(ch); }).base(), str.end()); + + return str; + } + else + { + return pStr; + } + } + + std::string Unquote(const std::string& pStr) const + { + if (mSeparatorParams.mAutoQuote && (pStr.size() >= 2) && + (pStr.front() == mSeparatorParams.mQuoteChar) && + (pStr.back() == mSeparatorParams.mQuoteChar)) + { + // remove start/end quotes + std::string str = pStr.substr(1, pStr.size() - 2); + + // unescape quotes in string + const std::string quoteCharStr = std::string(1, mSeparatorParams.mQuoteChar); + ReplaceString(str, quoteCharStr + quoteCharStr, quoteCharStr); + + return str; + } + else + { + return pStr; + } + } + + void UpdateColumnNames() + { + mColumnNames.clear(); + if ((mLabelParams.mColumnNameIdx >= 0) && + (static_cast(mData.size()) > mLabelParams.mColumnNameIdx)) + { + size_t i = 0; + for (auto& columnName : mData[static_cast(mLabelParams.mColumnNameIdx)]) + { + mColumnNames[columnName] = i++; + } + } + } + + void UpdateRowNames() + { + mRowNames.clear(); + if ((mLabelParams.mRowNameIdx >= 0) && + (static_cast(mData.size()) > + (mLabelParams.mColumnNameIdx + 1))) + { + size_t i = 0; + for (auto& dataRow : mData) + { + if (static_cast(dataRow.size()) > mLabelParams.mRowNameIdx) + { + mRowNames[dataRow[static_cast(mLabelParams.mRowNameIdx)]] = i++; + } + } + } + } + +#ifdef HAS_CODECVT +#if defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable: 4996) +#endif + static std::string ToString(const std::wstring& pWStr) + { + return std::wstring_convert, wchar_t>{ }.to_bytes(pWStr); + } + + static std::wstring ToWString(const std::string& pStr) + { + return std::wstring_convert, wchar_t>{ }.from_bytes(pStr); + } +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#endif + + static void ReplaceString(std::string& pStr, const std::string& pSearch, const std::string& pReplace) + { + size_t pos = 0; + + while ((pos = pStr.find(pSearch, pos)) != std::string::npos) + { + pStr.replace(pos, pSearch.size(), pReplace); + pos += pReplace.size(); + } + } + + private: + std::string mPath; + LabelParams mLabelParams; + SeparatorParams mSeparatorParams; + ConverterParams mConverterParams; + LineReaderParams mLineReaderParams; + std::vector> mData; + std::map mColumnNames; + std::map mRowNames; +#ifdef HAS_CODECVT + bool mIsUtf16 = false; + bool mIsLE = false; +#endif + bool mHasUtf8BOM = false; + }; +}