Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RemoveDuplicateVertices Implementation for TriangleMesh. #6414

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

intelshashi
Copy link
Contributor

@intelshashi intelshashi commented Oct 6, 2023

Functionality to remove duplicate vertices and update other vertex attributes and triangle indices is implemented. The C++ and python tests are also written to test the code.

On branch sganjugu/remdup2
Changes to be committed:
modified: cpp/open3d/t/geometry/TriangleMesh.cpp
modified: cpp/open3d/t/geometry/TriangleMesh.h
modified: cpp/pybind/t/geometry/trianglemesh.cpp
modified: cpp/tests/t/geometry/TriangleMesh.cpp
modified: python/test/t/geometry/test_trianglemesh.py

Type

  • Bug fix (non-breaking change which fixes an issue): Fixes #
  • New feature (non-breaking change which adds functionality). Resolves #
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) Resolves #

Motivation and Context

Checklist:

  • I have run python util/check_style.py --apply to apply Open3D code style
    to my code.
  • This PR changes Open3D behavior or adds new functionality.
    • Both C++ (Doxygen) and Python (Sphinx / Google style) documentation is
      updated accordingly.
    • I have added or updated C++ and / or Python unit tests OR included test
      results
      (e.g. screenshots or numbers) here.
  • I will follow up and update the code if CI fails.
  • For fork PRs, I have selected Allow edits from maintainers.

Description


This change is Reviewable

Functionality to remove duplicate vertices and update
other vertex attributes and triangle indices is implemented.
The C++ and python tests are also written to test the code.

On branch sganjugu/remdup2
Changes to be committed:
modified:   cpp/open3d/t/geometry/TriangleMesh.cpp
modified:   cpp/open3d/t/geometry/TriangleMesh.h
modified:   cpp/pybind/t/geometry/trianglemesh.cpp
modified:   cpp/tests/t/geometry/TriangleMesh.cpp
modified:   python/test/t/geometry/test_trianglemesh.py
@update-docs
Copy link

update-docs bot commented Oct 6, 2023

Thanks for submitting this pull request! The maintainers of this repository would appreciate if you could update the CHANGELOG.md based on your changes.

@ssheorey ssheorey self-requested a review October 7, 2023 04:46
Copy link
Member

@ssheorey ssheorey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @intelshashi , this approach and implementation are sound and logical. Since Open3D needs to be performance portable on both CPU and GPU, we have added some optimized Tensor primitives to ease algorithm development. You can see some of these in TriangleMesh::SelectFacesByMask which solves a similar problem - keeping a subset of vertices. That approach ensures that these operations work on both CPU and GPU and are performant on the GPU. I would recommend switching to that approach before we merge this PR. Specifically:

  • create a vertex_mask of vertices to be kept and use Tensor::IndexGet to subset vertices and attributes.
  • Use utility::InclusivePrefixSum to remap vertex indices.

cpp/open3d/t/geometry/TriangleMesh.cpp Outdated Show resolved Hide resolved
cpp/open3d/t/geometry/TriangleMesh.cpp Outdated Show resolved Hide resolved
cpp/open3d/t/geometry/TriangleMesh.cpp Show resolved Hide resolved
cpp/open3d/t/geometry/TriangleMesh.cpp Outdated Show resolved Hide resolved
Previously, I was doing a manual copy and shrinking the vertices.
Instead in this checkin all vertex attributes, including the
coordinates are shrunk using IndexGet method using a vertex mask.

Further, the triangle index remapping is similar to what was done
earlier, with some simplications. One thing to note, is that
we cannot use utility::InclusivePrefixSum to remap vertex indices
because the duplicates can occur in any position and may be duplicates
of any earlier vertex.

For e.g., suppose there were 9 vertices to start with, and 8th (index 7, starting from 0),
was a duplicate of 2nd (index 1, starting from 0).

So, the vertex mask would look like this:
Vertex indices: [0, 1, 2, 3, 4, 5, 6, 7, 8]
Vertex mask:    [1, 1, 1, 1, 1, 1, 1, 0, 1]
Prefix sum:   [0, 1, 2, 3, 4, 5, 6, 7, 7, 8]
This gives an incorrect index map for 8th vertex, which is mapped to
index 7 instead of 1.

On branch sganjugu/remdup2
Your branch is up to date with 'origin/sganjugu/remdup2'.
Changes to be committed:
modified:   ../cpp/open3d/t/geometry/TriangleMesh.cpp
modified:   ../python/test/t/geometry/test_trianglemesh.py
@intelshashi
Copy link
Contributor Author

Hi @intelshashi , this approach and implementation are sound and logical. Since Open3D needs to be performance portable on both CPU and GPU, we have added some optimized Tensor primitives to ease algorithm development. You can see some of these in TriangleMesh::SelectFacesByMask which solves a similar problem - keeping a subset of vertices. That approach ensures that these operations work on both CPU and GPU and are performant on the GPU. I would recommend switching to that approach before we merge this PR. Specifically:

  • create a vertex_mask of vertices to be kept and use Tensor::IndexGet to subset vertices and attributes.
  • Use utility::InclusivePrefixSum to remap vertex indices.

For e.g., suppose, you had 9 vertices, and 8th (index 7, starting from 0), was a duplicate of 2nd (index 1, starting from 0).
So, the vertex mask would look like this:
Vertex indices: [0, 1, 2, 3, 4, 5, 6, 7, 8]
Vertex mask: [1, 1, 1, 1, 1, 1, 1, 0, 1]
Prefix sum: [0, 1, 2, 3, 4, 5, 6, 7, 7, 8]
This gives an incorrect index map for 8th vertex.
In fact it could be a duplicate of any of the previous vertices.

@intelshashi intelshashi reopened this Nov 15, 2023
@intelshashi intelshashi requested a review from ssheorey November 15, 2023 14:43
(int)(orig_num_vertices - k));

return mesh;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this empty line is not needed.

auto orig_num_vertices = vertices.GetLength();
const auto vmask_type = core::Int32;
using vmask_itype = int32_t;
core::Tensor vertex_mask = core::Tensor::Zeros({orig_num_vertices}, vmask_type);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the mask is not used to store the remapped indices, I think we can have it as Bool right from the initialization.

auto triangles = GetTriangleIndices();
if (core::Int32 != triangles.GetDtype() && core::Int64 != triangles.GetDtype()) {
utility::LogError("Only Int32 or Int64 are supported for triangle indices");
return *this;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need to return after utility::LogError. (here and below)

Comment on lines +1335 to +1347
if(core::Float32 == vertices.GetDtype()) {
if (core::Int32 == triangles.GetDtype()) {
return RemoveDuplicateVerticesWorker<float, int>(*this);
} else {
return RemoveDuplicateVerticesWorker<float, int64_t>(*this);
}
} else {
if (core::Int32 == triangles.GetDtype()) {
return RemoveDuplicateVerticesWorker<double, int>(*this);
} else {
return RemoveDuplicateVerticesWorker<double, int64_t>(*this);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While working on my SelectByIndex method, I discovered a set of macros to do this in more compact way:
https://github.com/isl-org/Open3D/blob/main/cpp/open3d/core/Dispatch.h#L90
I think we could rewrite it as smt like:

Suggested change
if(core::Float32 == vertices.GetDtype()) {
if (core::Int32 == triangles.GetDtype()) {
return RemoveDuplicateVerticesWorker<float, int>(*this);
} else {
return RemoveDuplicateVerticesWorker<float, int64_t>(*this);
}
} else {
if (core::Int32 == triangles.GetDtype()) {
return RemoveDuplicateVerticesWorker<double, int>(*this);
} else {
return RemoveDuplicateVerticesWorker<double, int64_t>(*this);
}
}
DISPATCH_FLOAT_INT_DTYPE_TO_TEMPLATE(vertices.GetDtype(), triangles.GetDtype()), [&]() {
RemoveDuplicateVerticesWorker<scalar_t, int_t>(*this);
});

TriangleMesh& RemoveDuplicateVerticesWorker(TriangleMesh &mesh)
{
//Algorithm based on Eigen implementation
if(!mesh.HasVertexPositions()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please run python util/check_style.py --apply on your changes? There are a few accidental space misses.

//REM_DUP_VERT_STEP 1:
// Compute map from points to old indices, and use a counter
// to compute a map from old indices to new unique indices.
const Coord_t *vertices_ptr = vertices.GetDataPtr<Coord_t>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to move vertices tensor to CPU device, too.

return *this;
}
auto vertices = GetVertexPositions();
auto triangles = GetTriangleIndices();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be meshes w/o triangles tensor. In this case, the call to GetTrianglesIndices throws.
Could you please add safety checks for this case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants