question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Detection of self intersection for pyvista.polydata type

See original GitHub issue

Hello everyone. Some time ago, I was generating new shapes (non-closed 3d surfaces) through a statistical shape model (SSM). Sometimes the SSM was giving shapes that self-intersected. By self-intersection, I mean when some part of the surface touches another part of the surface of the same pyvista.Polydata. I have attached an image to be as clear as possible.

image

I could not find any solution online to detect this kind of problem. The (not very robust) solution I came up with is to generate an inflated version of the same surface and then run the pyvista.Polydata.intersection() method to check if the two shapes intersect. This is the implementation:

def check_self_intersections(mesh_orig):
    mesh_inflated = mesh_orig.copy()
    mesh_inflated = mesh_inflated.compute_normals(
        consistent_normals=True,
        cell_normals=False,
        split_vertices=True
    )
    mesh_inflated.points = mesh_inflated.points + mesh_inflated['Normals']*1.
    intersection = mesh_orig.intersection(mesh_inflated)
    return intersection[0].n_points

The problems with this solution are multiple:

  1. VTK does not seem to like when intersection has zero points, i.e., the mesh does not intersect. In fact, some warning is always launched in the background;
  2. intersection can have a small number of points even if there are no intersections. In fact, I check for self-intersections by asserting that intersection.n_points < THRESHOLD_VALUE.

I would like to know if there are some better solutions to the problem and, in that case, create a pull request with this feature.

Thanks everyone!


Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:13 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
scarpmacommented, Mar 30, 2022

Ok, thank you very much. I will look into these in these days. As soon as I come up with something I will update this issue. Thanks !!

0reactions
scarpmacommented, Jul 14, 2022

yes, this is it

import numpy as np
import scipy.spatial

EPSILON = 1e-5

def seg_tria_intersection(P0, P1, V0, V1, V2):
    """
    P0 first point of the segment
    P1 second point of the segment
    V0, V1, V2 vertices of triangle
    D normalized direction of segment
    l length of the segment
    u,v barycentric coordinated of intersection (inside triangle
    if 0 < u < 1. and 0. < v < 1.)
    t coordinate along segment of the intersection (inside segment
    if 0. < t < l.)
    3d space
    """
    O = P0
    D = P1 - P0
    l = np.sqrt((D**2.).sum(-1))
    D = (D.T / l).T

    E1 = V1 - V0 # edge 1 of triangle
    E2 = V2 - V0 # edge 2 of triangle
    P = np.cross(D, E2)
    det = np.einsum('...i,...i',E1, P)
    # if det is near 0, ray lies in triangle's plane
    det_bool = np.abs(det) > EPSILON

    # EPSILON2 must be >0 because otherwise segments that start
    # or finish in one of the triangle vertices are counted as
    # intersections
    EPSILON2 = 0.001

    inv_det = 1 / det
    T = O - V0
    u = np.einsum('...i,...i', T, P) * inv_det
    u_bool = np.logical_and(u > 0.+EPSILON2, u < 1.-EPSILON2)

    Q = np.cross(T, E1)
    v = np.einsum('...i,...i', D, Q) * inv_det
    v_bool = np.logical_and(v > 0.+EPSILON2, u+v < 1.-EPSILON2)

    t = np.einsum('...i,...i', E2, Q) * inv_det
    t_bool = np.logical_and(t > 0., t < 1.)

    return det_bool * u_bool * v_bool * t_bool


def tria_tria_intersection(tria1_coo, tria2_coo):

    """
    detects if edges of tria1 intersect tria2. Note that this is different from
    detecting if the two triangles intersect. I can use this shorter version of the
    intersection function because I loop among all triangles, so if only triangle2'edges
    intersect triangle1, the intersection will be spotted later in the loop
    If not in a loop, given two triangles, they interset iff:
        or one edge on each intersect
        or two edges of one intersect the other one
    """

    tria1_coo = tria1_coo.astype(np.float32)
    tria2_coo = tria2_coo.astype(np.float32)

    if tria1_coo.ndim == 2:
        tria1_coo = tria1_coo[(None)]
    if tria2_coo.ndim == 2:
        tria2_coo = tria2_coo[(None)]

    i = 0
    i += seg_tria_intersection(
        tria1_coo[:,0], tria1_coo[:,1],
        tria2_coo[:,0], tria2_coo[:,1], tria2_coo[:,2],
    )
    i += seg_tria_intersection(
        tria1_coo[:,1], tria1_coo[:,2],
        tria2_coo[:,0], tria2_coo[:,1], tria2_coo[:,2],
    )
    i += seg_tria_intersection(
        tria1_coo[:,2], tria1_coo[:,0],
        tria2_coo[:,0], tria2_coo[:,1], tria2_coo[:,2],
    )

    return i>0


def check_intersections(mesh, r=None):

    cell_idxs = np.array(mesh.faces.reshape((-1,4))[:,1:])
    cell_coords = np.array(mesh.points[cell_idxs])
    cell_center_coords = np.array(mesh.cell_centers().points)
    kdtree = scipy.spatial.KDTree(cell_center_coords)

    if r is None:
        max_distance_between_center_and_vertices = np.sqrt((
            (cell_center_coords[:,None,:] - mesh.points[cell_idxs])**2
        ).sum(-1)).max()
        r = max_distance_between_center_and_vertices * 1.05

    near_cells = kdtree.query_ball_point(cell_center_coords, r, return_sorted=False, workers=-1)

    tria1_idxs_tot, tria2_idxs_tot = [], []
    for tria1_idx, tria2_idxs in enumerate(near_cells):
        #tria2_idxs_non_neig = list(remove_neig_cells(tria1_idx, cell_idxs, np.array(tria2_idxs)))
        tria2_idxs_non_neig = tria2_idxs
        tria1_idxs_tot += [tria1_idx] * len(tria2_idxs_non_neig)
        tria2_idxs_tot += tria2_idxs_non_neig
    tria1_idxs_tot = np.array(tria1_idxs_tot)
    tria2_idxs_tot = np.array(tria2_idxs_tot)

    tria1_coo = cell_coords[tria1_idxs_tot]
    tria2_coo = cell_coords[tria2_idxs_tot]

    o = tria_tria_intersection(tria1_coo, tria2_coo)

    return tria1_idxs_tot[o]

and it is used simply like this check_intersections(surfaceMesh) where surfaceMesh is a polydata object.

Probably the implementation is not very clear, so for any doubt ask me anything. I am also open to any suggestion / modification.

Thanks for your interest

Read more comments on GitHub >

github_iconTop Results From Across the Web

pyvista.PolyData — PyVista 0.37.0 documentation
Clip a dataset by a scalar. Clip any mesh type using a pyvista. PolyData surface mesh. Perform collision determination between two polyhedral surfaces....
Read more >
intersection — PyVista 0.37.0 documentation
Compute the intersection between two meshes. ... This method returns the surface intersection from two meshes (which often resolves as a line), whereas...
Read more >
pyvista.PolyDataFilters — PyVista 0.37.0 documentation
An internal class to manage filters/algorithms for polydata datasets. Methods ... Perform a boolean intersection operation on two meshes.
Read more >
Source code for pyvista.core.filters.poly_data
PolyData Mesh operating on the source mesh. tolerance : float, ... The intersection of two manifold meshes ``A`` and ``B`` is the mesh...
Read more >
Source code for pyvista.core.filters.data_set
SetInputDataObject(self) # Use the grid as the data we desire to cut alg. ... PolyData`: if a poly mesh is passed that represents...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found