From fdc9687d86682c9b4fda3e26bf66d1ea83aef15c Mon Sep 17 00:00:00 2001 From: Bertrand Kerautret Date: Mon, 6 May 2024 23:48:55 +0200 Subject: [PATCH 1/4] new tool polyMeshColorize --- ChangeLog.md | 3 +- README.md | 5 +- visualisation/CMakeLists.txt | 3 +- visualisation/polyMeshColorize.cpp | 418 +++++++++++++++++++++++++++++ 4 files changed, 425 insertions(+), 4 deletions(-) create mode 100644 visualisation/polyMeshColorize.cpp diff --git a/ChangeLog.md b/ChangeLog.md index 0f57d40..35bc762 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,7 +30,8 @@ - *visualisation* - polyMeshEdit: tool to edit a mesh (add local noise, remove selected faces). (Bertrand Kerautret [#78](https://github.com/DGtal-team/DGtalTools-contrib/pull/78)) - + - polyMeshColorize: tool to colorize a mesh. + (Bertrand Kerautret [#83](https://github.com/DGtal-team/DGtalTools-contrib/pull/83)) # DGtalTools-contrib 1.3 diff --git a/README.md b/README.md index 25bf72b..7251633 100644 --- a/README.md +++ b/README.md @@ -93,12 +93,13 @@ This section, can contain all tools related to visualisation: - meshViewerEdit: tool to visualize a mesh and to apply simple edits (face removal, color edits...). - graphViewer: tool to display graphs from a list of edges, a list of vertex and an optionnal list of radius for each edge. - polyMeshEdit: tool to edit a mesh (add local noise, remove selected faces). - + - polyMeshColorize: tool to colorize a mesh. + @@ -109,6 +110,6 @@ This section, can contain all tools related to visualisation: - +
displayTgtCoverAlphaTS
polyMeshEditpolyMeshEditpolyMeshColorize
diff --git a/visualisation/CMakeLists.txt b/visualisation/CMakeLists.txt index c04b7f4..a73516d 100644 --- a/visualisation/CMakeLists.txt +++ b/visualisation/CMakeLists.txt @@ -5,7 +5,8 @@ SET(DGTAL_TOOLS_CONTRIB ) SET(DGTAL_TOOLS_CONTRIB_POLY - polyMeshEdit + polyMeshEdit + polyMeshColorize ) diff --git a/visualisation/polyMeshColorize.cpp b/visualisation/polyMeshColorize.cpp new file mode 100644 index 0000000..ae3b0a6 --- /dev/null +++ b/visualisation/polyMeshColorize.cpp @@ -0,0 +1,418 @@ +/** + * This program 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 3 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + **/ + +/** + * @file + * @ingroup visualisation + * @author Bertrand Kerautret (\c bertrand.kerautret@univ-lyon2.fr ) + * + * + * @date 2023/11/17 + * + * Source file of the tool polyMeshColorize + * + * This file is part of the DGtal library/DGtalTools-contrib Project. + */ + +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include "DGtal/io/readers/MeshReader.h" +#include "DGtal/io/writers/MeshWriter.h" + +#include +#include +#include +#include + +#include + +#include "CLI11.hpp" + +/////////////////////////////////////////////////////////////////////////////// +using namespace std; +using namespace DGtal; +/////////////////////////////////////////////////////////////////////////////// + + +/** + @page polyMeshColorize polyMeshColorize + + @brief polyMeshColorize tool to colorize a mesh (faces). Note that the rendering is based on index color and the real colors are exported from the associated RGB colors. Note also that the process relies on the halfedge data structure that can fail if the input is not topologically consistant. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). + + + @b Usage: polyMeshColorize [OPTIONS] 1 [2] + + + @b Allowed @b options @b are : + + @code + + Positionals: + 1 TEXT:FILE REQUIRED an input mesh file in .obj or .off format. + 2 TEXT:FILE=result.obj an output mesh file in .obj or .off format. + + + Options: + -h,--help Print this help message and exit + -i,--input TEXT:FILE REQUIRED an input mesh file in .obj or .off format. + -o,--output TEXT:FILE=result.obj an output mesh file in .obj or .off format. + @endcode + + @b Example: + + @code + polyMeshColorize $DGtal/examples/samples/bunnyhead.obj bunnyEdited.obj + @endcode + + @image html respolyMeshColorize.png "Example of result. " + + @see + @ref polyMeshColorize.cpp + + */ + + +typedef PolygonalSurface PolySurface; + +static PolySurface currentPolysurf; + +static float minPaintRad = 1.0; +static float maxPaintRad = 100.0; +static float paintRad = 1.0; + +static float color1Selected[] = {0.0,0.0,1.0}; +static float color2Selected[] = {0.0,1.0,0.0}; +static float color3Selected[] = {0.0,1.0,1.0}; + +static std::map indexColoring; +static DGtal::Mesh srcInputMesh (true); +static DGtal::Mesh inputMesh (true); + +std::vector vectSel; +std::vector> fColor; + +static std::string outputFileName {"result.obj"}; + + +void updateColorDisplay(){ + polyscope::removeStructure("fColor"); + auto digsurf = polyscope::getSurfaceMesh("InputMesh"); + digsurf->addFaceColorQuantity("fColor", fColor); + digsurf->setAllQuantitiesEnabled(true); +} + + +void importMeshColor(const DGtal::Mesh &aMEsh){ + fColor.resize(aMEsh.nbFaces()); + for ( unsigned int i = 0; i< aMEsh.nbFaces(); i++ ) + { + auto c = aMEsh.getFaceColor(i); + fColor[i] = {c.red()/255.0, c.green()/255.0, c.blue()/255.0}; + } +} + + +void addSurfaceInPolyscope(PolySurface &psurf){ + std::vector> faces; + for(auto &face: psurf.allFaces()) + { + faces.push_back(psurf.verticesAroundFace( face )); + } + auto digsurf = polyscope::registerSurfaceMesh("InputMesh", + psurf.positions(), faces); + digsurf->addFaceColorQuantity("fColor", fColor); + digsurf->setAllQuantitiesEnabled(true); + +} + +Z3i::RealPoint +getFaceBarycenter(const PolySurface &polysurff, const PolySurface::Face &aFace) { + Z3i::RealPoint res(0.0, 0.0, 0.0); + for(auto const &v: polysurff.verticesAroundFace(aFace)){ + res += polysurff.position(v); + } + return res/polysurff.verticesAroundFace(aFace).size(); +} + +// Helper function +std::vector faceAround(const PolySurface &polysurff, + PolySurface::Face faceId, + double radius){ + std::vector result; + std::queue q; + std::map fVisited; + std::map vVisited; + + for (auto const &v : polysurff.verticesAroundFace(faceId) ) + { + q.push(v); + } + fVisited[faceId] = true; + bool addNewFaces = true; + while (!q.empty() ) + { + PolySurface::Vertex v = q.front(); q.pop(); + auto listFace = polysurff.facesAroundVertex(v); + for (auto const & f : listFace) + { + if (fVisited.count(f)==0) + { + if((getFaceBarycenter(polysurff, f) - + getFaceBarycenter(polysurff, faceId)).norm() < radius) + { + fVisited[f]=true; + result.push_back(f); + for (auto const & v : polysurff.verticesAroundFace(f)) + { + if (vVisited.count(v)==0) + { + vVisited[v]=true; + q.push(v); + } + } + } + } + } + } + return result; +} + + +std::map +getIndexColors(const DGtal::Mesh &aMEsh){ + std::map indexCol; + for ( unsigned int i = 0; i< aMEsh.nbFaces(); i++ ) + { + auto c = aMEsh.getFaceColor(i); + if(! indexCol.count(c)) + { + indexCol[c] += 1; + } + } + unsigned int max = 0; + for (auto it = indexCol.begin(); it != indexCol.end(); it++) + { + if ((*it).second > max) + { + max = (*it).second; + } + } + return indexCol; +} + + + +void +drawSelection() { + for(auto i: vectSel){ + auto c = inputMesh.getFaceColor(i); + fColor[i] = {c.red()/255.0-0.3, c.green()/255.0-0.3, c.blue()/255.0-0.3}; + } +} + +void +resetSelection() { + for ( unsigned int i = 0; i< vectSel.size(); i++ ) + { + auto c = inputMesh.getFaceColor(vectSel[i]); + fColor[vectSel[i]] = {c.red()/255.0, c.green()/255.0, c.blue()/255.0}; + } + vectSel.clear(); +} + +void +colorizeSelectedFaces(const DGtal::Color &c) { + for(auto i: vectSel){ + inputMesh.setFaceColor(i, c); + fColor[i] = {c.red()/255.0, c.green()/255.0, c.blue()/255.0}; + } + vectSel.clear(); +} + +void callbackFaceID() { + srand((unsigned) time(NULL)); + ImGui::Begin("Editing tools"); + ImGui::Text("Setting selection size:"); + ImGui::SliderFloat("radius values", &paintRad, minPaintRad, maxPaintRad, "size = %.3f"); + ImGui::Separator(); + + ImGui::Text("Action:"); + if (ImGui::Button("Clear selection")) + { + resetSelection(); + } + ImGui::SameLine(); + + ImGui::Text("Color selection "); + ImGui::ColorEdit3("color 1", color1Selected); + if (ImGui::Button("colorize 1")) + { + colorizeSelectedFaces(DGtal::Color(color1Selected[0]*255, + color1Selected[1]*255, + color1Selected[2]*255) ); + updateColorDisplay(); + + } + ImGui::ColorEdit3("color 2", color2Selected); + if (ImGui::Button("colorize 2")) + { + colorizeSelectedFaces(DGtal::Color(color2Selected[0]*255, + color2Selected[1]*255, + color2Selected[2]*255) ); + updateColorDisplay(); + + } + + ImGui::ColorEdit3("color 3", color3Selected); + if (ImGui::Button("colorize 3")) + { + colorizeSelectedFaces(DGtal::Color(color3Selected[0]*255, + color3Selected[1]*255, + color3Selected[2]*255) ); + updateColorDisplay(); + + } + + ImGui::Separator(); + ImGui::Text("IO"); + + if (ImGui::Button("save in .obj")) + { + inputMesh >> outputFileName; + } + ImGui::SameLine(); + + if (ImGui::Button("reload src")) + { + inputMesh = srcInputMesh; + importMeshColor(inputMesh); + } + ImGui::Separator(); + ImGui::Text("Polyscope interface:"); + + if (ImGui::Button("show ")) + { + polyscope::options::buildGui=true; + } + ImGui::SameLine(); + if (ImGui::Button("hide")) + { + polyscope::options::buildGui=false; + } + + ImGuiIO& io = ImGui::GetIO(); + if (io.MouseDoubleClicked[0]) + { + unsigned long indexSelect = polyscope::pick::getSelection().second; + unsigned long nb = 0; + // face selected + if (indexSelect >= currentPolysurf.nbVertices()) + { + nb = (unsigned long) polyscope::pick::getSelection().second - currentPolysurf.nbVertices(); + } + else + { + // vertex selected (selecting a face connected to it) + if(currentPolysurf.facesAroundVertex(polyscope::pick::getSelection().second) + .size()> 0) + { + nb = currentPolysurf.facesAroundVertex(polyscope::pick::getSelection().second)[0]; + } + } + + if (nb > 0 && nb < fColor.size()) + { + auto fVois = faceAround(currentPolysurf, nb, paintRad); + vectSel.push_back(nb); + for (auto f: fVois) + { + vectSel.push_back(f); + } + drawSelection(); + updateColorDisplay(); + } + } + ImGui::End(); +} + + + + + +int main(int argc, char** argv) +{ + std::string inputFileName {""}; + + // parse command line using CLI ---------------------------------------------- + CLI::App app; + app.description("polyMeshColorize tool to colorize a mesh (faces). Note that the rendering is based on index color and the real colors are exported from the associated RGB colors. Note also that the process relies on the halfedge data structure that can fail if the input is not topologically consistant. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). \n" + " polyMeshColorize $DGtal/examples/samples/bunnyhead.obj bunnyColored.obj \n"); + app.add_option("-i,--input,1", inputFileName, "an input mesh file in .obj or .off format." ) + ->required() + ->check(CLI::ExistingFile); + app.add_option("-o,--output,2", outputFileName, "an output mesh file in .obj or .off format.", true ); + + + app.get_formatter()->column_width(40); + CLI11_PARSE(app, argc, argv); + polyscope::options::programName = "polyMeshColorize - (DGtalToolsContrib)"; + polyscope::init(); + polyscope::options::buildGui=false; + // read input mesh + inputMesh << inputFileName; + inputMesh.removeIsolatedVertices(); + srcInputMesh = inputMesh; + + auto bb = inputMesh.getBoundingBox(); + indexColoring = getIndexColors(inputMesh); + auto it = indexColoring.begin(); + if (it!= indexColoring.end()) { + color1Selected[0] = (*it).first.red()/255.0; + color1Selected[1] = (*it).first.green()/255.0; + color1Selected[2] = (*it).first.blue()/255.0; + it++; + } + if (it!= indexColoring.end()) { + color2Selected[0] = (*it).first.red()/255.0; + color2Selected[1] = (*it).first.green()/255.0; + color2Selected[2] = (*it).first.blue()/255.0; + it++; + if (it!= indexColoring.end()) { + color3Selected[0] = (*it).first.red()/255.0; + color3Selected[1] = (*it).first.green()/255.0; + color3Selected[2] = (*it).first.blue()/255.0; + } + } + DGtal::trace.info() << "color index recovered with:" << indexColoring.size() << std::endl; + // Setting scale mesh dependant parameters + minPaintRad = (bb.second - bb.first).norm()/1000.0; + maxPaintRad = (bb.second - bb.first).norm()/2.0; + paintRad = (bb.second - bb.first).norm()/50.0; + + DGtal::MeshHelpers::mesh2PolygonalSurface(inputMesh, currentPolysurf); + importMeshColor(inputMesh); + polyscope::state::userCallback = callbackFaceID; + addSurfaceInPolyscope(currentPolysurf); + + polyscope::show(); + return 0; + +} From d2a8aa64328070967e7b5cff5b6e9ff366f18c44 Mon Sep 17 00:00:00 2001 From: Bertrand Kerautret Date: Tue, 7 May 2024 23:41:48 +0200 Subject: [PATCH 2/4] descripption update --- visualisation/polyMeshColorize.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/visualisation/polyMeshColorize.cpp b/visualisation/polyMeshColorize.cpp index ae3b0a6..44ab744 100644 --- a/visualisation/polyMeshColorize.cpp +++ b/visualisation/polyMeshColorize.cpp @@ -55,7 +55,7 @@ using namespace DGtal; /** @page polyMeshColorize polyMeshColorize - @brief polyMeshColorize tool to colorize a mesh (faces). Note that the rendering is based on index color and the real colors are exported from the associated RGB colors. Note also that the process relies on the halfedge data structure that can fail if the input is not topologically consistant. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). + @brief polyMeshColorize tool to colorize a mesh (faces). Note that the process relies on the halfedge data structure that can fail if the input is not topologically consistant. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). @b Usage: polyMeshColorize [OPTIONS] 1 [2] @@ -363,7 +363,7 @@ int main(int argc, char** argv) // parse command line using CLI ---------------------------------------------- CLI::App app; - app.description("polyMeshColorize tool to colorize a mesh (faces). Note that the rendering is based on index color and the real colors are exported from the associated RGB colors. Note also that the process relies on the halfedge data structure that can fail if the input is not topologically consistant. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). \n" + app.description("polyMeshColorize tool to colorize a mesh (faces). Note that the process relies on the halfedge data structure that can fail if the input is not topologically consistant. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). \n" " polyMeshColorize $DGtal/examples/samples/bunnyhead.obj bunnyColored.obj \n"); app.add_option("-i,--input,1", inputFileName, "an input mesh file in .obj or .off format." ) ->required() @@ -401,7 +401,6 @@ int main(int argc, char** argv) color3Selected[2] = (*it).first.blue()/255.0; } } - DGtal::trace.info() << "color index recovered with:" << indexColoring.size() << std::endl; // Setting scale mesh dependant parameters minPaintRad = (bb.second - bb.first).norm()/1000.0; maxPaintRad = (bb.second - bb.first).norm()/2.0; From 742d02f0e89408b57c081cb1a8a2e53168195a4f Mon Sep 17 00:00:00 2001 From: Bertrand Kerautret Date: Sat, 25 May 2024 23:35:33 +0200 Subject: [PATCH 3/4] Update visualisation/polyMeshColorize.cpp Co-authored-by: David Coeurjolly --- visualisation/polyMeshColorize.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/visualisation/polyMeshColorize.cpp b/visualisation/polyMeshColorize.cpp index 44ab744..917ddee 100644 --- a/visualisation/polyMeshColorize.cpp +++ b/visualisation/polyMeshColorize.cpp @@ -55,7 +55,7 @@ using namespace DGtal; /** @page polyMeshColorize polyMeshColorize - @brief polyMeshColorize tool to colorize a mesh (faces). Note that the process relies on the halfedge data structure that can fail if the input is not topologically consistant. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). + @brief polyMeshColorize tool to colorize a mesh (faces). Note that the process relies on the halfedge data structure that can fail if the input is not topologically consistent. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). @b Usage: polyMeshColorize [OPTIONS] 1 [2] From f9cd63c8f218904ff4a23b0f8b726047e240c7db Mon Sep 17 00:00:00 2001 From: Bertrand Kerautret Date: Sat, 25 May 2024 23:35:56 +0200 Subject: [PATCH 4/4] Update visualisation/polyMeshColorize.cpp Co-authored-by: David Coeurjolly --- visualisation/polyMeshColorize.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/visualisation/polyMeshColorize.cpp b/visualisation/polyMeshColorize.cpp index 917ddee..d8cc1c7 100644 --- a/visualisation/polyMeshColorize.cpp +++ b/visualisation/polyMeshColorize.cpp @@ -363,7 +363,7 @@ int main(int argc, char** argv) // parse command line using CLI ---------------------------------------------- CLI::App app; - app.description("polyMeshColorize tool to colorize a mesh (faces). Note that the process relies on the halfedge data structure that can fail if the input is not topologically consistant. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). \n" + app.description("polyMeshColorize tool to colorize a mesh (faces). Note that the process relies on the halfedge data structure that can fail if the input is not topologically consistent. If you want use other type of mesh, you can use meshViewerEdit that is based on the simple soup of triangles process (slower selection process). \n" " polyMeshColorize $DGtal/examples/samples/bunnyhead.obj bunnyColored.obj \n"); app.add_option("-i,--input,1", inputFileName, "an input mesh file in .obj or .off format." ) ->required()