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

Add RemoveDuplicatedTriangles feature using tensor API #6409

Closed

Conversation

PierreIntel
Copy link

@PierreIntel PierreIntel commented Oct 5, 2023

Type

  • Bug fix (non-breaking change which fixes an issue): Fixes #
  • [ x] 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

This PR is an implementation of the RemoveDuplicatedTriangles but using the tensor API to be able to run it either on CPU or GPU (only CPU for now in this PR).
This was also an opportunity to get familiar with the Open3D code.

Checklist:

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

Description

Here is the note of my PR for the RemoveDuplicatedTriangles feture using Tensor API.

The first problem of this feature is how to identify duplicated triangles.
I keep the same solution than the Eigen based API RemoveDuplicatedTriangles that is to use a unordered_map that has a complexity of search in O(1) due to the use of a hash function.

This unordered_map is implemented with a tuple as key instead of a tensor directly is because the hash function using a tensor is not implemented.
So we have to "convert" a vertice tensor of 3 points to a tuple to be able to keep track of the triangle.

The second problem is how to remove the duplicated triangles.
I choose to iterate on the index of the unique triangles and to copy them in a new tensor

I did this like that because we don't know the size of the new tensor of triangles until we have looped on all triangles.

I have tried to use :
tensor.reshape(), but it reshapes the tensor in a tensor with the same number of elements.
tensor.append(), but we need it doesn't increase the size of the vector where we append elements.

In the worst case where we have only one duplicated triangles we will need to iterate one time on the number of triangles and an other time on number of triangles - 1,
so the complexity is in O(n*(n-1)) and in the best case O(n) due to the test if before the second loop.

I have also treated the case if there are some normales triangles. I suppose that the number of triangles and normales triangles are the same and they are at a corresponding index.


This change is Reviewable

@update-docs
Copy link

update-docs bot commented Oct 5, 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 5, 2023 16:59
cpp/open3d/t/geometry/TriangleMesh.cpp Show resolved Hide resolved
.Item<int64_t>();
int64_t vertice_2 =
triangle_to_check.GetItem({core::TensorKey::Index(2)})
.Item<int64_t>();
Copy link
Member

Choose a reason for hiding this comment

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

We need to ensure this works for different dtypes for vertices (specifically int32_t and int64_t). This will output junk values for int32_t vertices.

minor:

  • operator[] is shorter: triangle_to_check[0].Item<dtype>()
  • Would using std::rotate to reorder elements in the tuple be cleaner than the multiple ifs ?
  • Can you comment on parallelization here? Is it possible and would it provide a useful speedup?

Copy link
Author

Choose a reason for hiding this comment

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

std::rotate can be used but it will need multiple ifs too to know which vertices to rotate. Example {2,1,0} will need 2 rotations to get {0,1,2}. What can be used is instantiate a std::vector<int64_t> of 3 elements, sort it with std::sort and next initialize the tuple for the unordered map.
Also elements of triangle_vec are int64_t like that it can get vertices in int32_t or int64_t.

        std::vector<int64_t> triangle_vec;
        if (triangles.GetDtype() == core::Int32)
        {
            auto vertice_0 = triangle_to_check[0].Item<int32_t>();
            auto vertice_1 = triangle_to_check[1].Item<int32_t>();
            auto vertice_2 = triangle_to_check[2].Item<int32_t>();
            triangle_vec = {vertice_0, vertice_1, vertice_2};
        }
        else if (triangles.GetDtype() == core::Int64)
        {
            auto vertice_0 = triangle_to_check[0].Item<int64_t>();
            auto vertice_1 = triangle_to_check[1].Item<int64_t>();
            auto vertice_2 = triangle_to_check[2].Item<int64_t>();
            triangle_vec = {vertice_0, vertice_1, vertice_2};
        }

        // We first need to find the minimum index. Because triangle (0-1-2)
        // and triangle (2-0-1) are the same.
        std::sort(triangle_vec.begin(), triangle_vec.end());
        triangle_in_tuple =
                        std::make_tuple(triangle_vec[0], triangle_vec[1], triangle_vec[2]);

The parallelization is possible but we need to be sure that the access to the unordered map is in a critical section to avoid multiple accesses adding the duplicate triangle in the unordered map.

Copy link
Author

Choose a reason for hiding this comment

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

done in c890dc8

if (triangle_to_old_index.find(triangle_in_tuple) ==
triangle_to_old_index.end()) {
triangle_to_old_index[triangle_in_tuple] = i;
index_to_keep.push_back(i);
Copy link
Member

Choose a reason for hiding this comment

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

Would using a boolean mask of triangles to keep, instead of a vector of indices be faster? Tensors support indexing with a Boolean mask, similar to numpy. See SelectFacesByMask().

Copy link
Author

Choose a reason for hiding this comment

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

Yes it is possible to use SelectFacesByMask() but this function doesn't select Normals Triangles faces ?
So if I need to loop over the normals triangles I can keep the actual loop instead of have both SleectFacesByMask() for triangles and the loop for normals triangles.

Copy link
Author

Choose a reason for hiding this comment

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

I succeed to manage it in 3aa93dd

core::Tensor triangles_normals, triangles_normals_without_dup;

// fill only is there are normals triangle to avoid errors
if (has_tri_normal) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you support other triangle attributes? (e.g. triangle colors, etc.) An important feature of the Tensor API data structures is support for generic triangle and vertex attributes.

Copy link
Author

Choose a reason for hiding this comment

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

Yes the triangle colors are manage in this : 3aa93dd

Copy link
Member

Choose a reason for hiding this comment

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

We want to support arbitrary user defined attributes. Examples are labels, covariance, opacity, etc. See SelectFacesByMask for how to support arbitrary attributes.


// We first need to find the minimum index. Because triangle (0-1-2)
// and triangle (2-0-1) are the same.
std::sort(triangle_vec.begin(), triangle_vec.end());
Copy link
Member

Choose a reason for hiding this comment

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

Triangles / faces are directional, i.e. triangle {0,1,2} is different from triangle {2,1,0} since their surface normals will face in opposite directions. Instead of sort, we can use std::rotate(triangle_vec.begin(), ptr_to_min, triangle_vec.end()) This ensures direction is preserved.

Copy link
Member

Choose a reason for hiding this comment

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

Also, repeated memory allocation as we create a new vector for each face should be avoided.

core::Tensor triangles_normals, triangles_normals_without_dup;

// fill only is there are normals triangle to avoid errors
if (has_tri_normal) {
Copy link
Member

Choose a reason for hiding this comment

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

We want to support arbitrary user defined attributes. Examples are labels, covariance, opacity, etc. See SelectFacesByMask for how to support arbitrary attributes.

@ssheorey ssheorey closed this Nov 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants