Crash when `compute_normals`
Describe the bug, what's wrong, and what you expect:
When calculating normals, the interpreter silently crashes.. No error's or output, not even with the VTKErrorCatcher. can anyone please enlighten me what happens in this minimal example?
To Reproduce
Please include a code snippet to reproduce the bug in the block below:
import pyvista
import numpy as np
xyz = np.random.random(102*3).reshape(-1,3)
faces = np.arange(102*3)
np.random.shuffle(faces)
faces = faces.reshape(-1,3)
faces = np.column_stack((np.ones(len(faces),) * 3, faces)).astype(int)
mesh = pyvista.PolyData()
mesh.points = xyz
mesh.faces = faces
print(mesh.compute_normals(inplace=False))
System Information: Please run the following code wherever you are experiencing the bug and paste the output below. This report helps us track down bugs and it is critical to addressing your bug:
# Get system info
import pyvista as pv
print(pv.Report())
--------------------------------------------------------------------------------
Date: Wed Feb 02 16:36:41 2022 W. Europe Standard Time
OS : Windows
CPU(s) : 8
Machine : AMD64
Architecture : 64bit
RAM : 31.9 GiB
Environment : Python
File system : unknown
GPU Vendor : NVIDIA Corporation
GPU Renderer : Quadro M1000M/PCIe/SSE2
GPU Version : 4.5.0 NVIDIA 441.66
Python 3.9.0 (tags/v3.9.0:9cf6752, Oct 5 2020, 15:34:40) [MSC v.1927 64 bit
(AMD64)]
pyvista : 0.33.2
vtk : 9.1.0
numpy : 1.22.1
imageio : 2.14.1
appdirs : 1.4.4
scooby : 0.5.11
matplotlib : 3.5.1
pyvistaqt : 0.7.0
PyQt5 : 5.15.6
scipy : 1.7.3
--------------------------------------------------------------------------------
The vtkPolyDataNormals (the underlying VTK filter here) filter is specifically for polygonal meshes - for this specific example data, the cells are malformed (hence why you see nothing when trying to plot these data).
From https://vtk.org/doc/nightly/html/classvtkPolyDataNormals.html#details
Normals are computed only for polygons and triangle strips. Normals are not computed for lines or vertices. Triangle strips are broken up into triangle polygons. You may want to restrip the triangles.
Perhaps we should add a warning or error in compute_normals to check the cell types
Thanks for tour quick response!
However the faces are all triangles and thus a Polygonial mesh, aren't they? The filter should then.
there's only 102 verts but faces is indexing up to 305
The faces in this particular mesh (produced randomly) are not valid/illformed - @darikg is exactly right about the indexing
import pyvista
import numpy as np
xyz = np.random.random(102*3).reshape(-1,3)
faces = np.arange(102*3)
np.random.shuffle(faces)
faces = faces.reshape(-1,3)
faces = np.column_stack((np.ones(len(faces),) * 3, faces)).astype(int)
mesh = pyvista.PolyData()
mesh.points = xyz
mesh.faces = faces
print(mesh)
PolyData (0x7ff2e02eb440)
N Cells: 102
N Points: 102
X Bounds: -5.518e+127, 1.146e+248
Y Bounds: 0.000e+00, 1.711e+238
Z Bounds: 0.000e+00, 1.143e+243
N Arrays: 0
Let's look at the first face:
>>> faces[0,:]
array([ 3, 151, 221, 96])
Cell index 0 says that nodes 151, 221, and 96 should make that polygon. The mesh itself only has 102 points, thus that polygon cannot exist
Do you have some valid data where you are seeing this crash?
Sorry you guys are right, the example is invalid. I have some real data however the bug does not occur every time I run the program (due to some randomness). So it's hard to catch the dataset which raises the bug.
Tomorrow I will try, better, to catch the bug. Or make a valid example which raises the problem.
@banesullivan, assuming the bug is valid, is checking for 'is all triangles' a fix?
is checking for 'is all triangles' a fix?
That's what I'm thinking but it is hard to tell without seeing the problematic data. Perhaps also a clean().
FWIW, we have this check in PyVista with is_all_triangles
If the problem mesh is indeed all triangles, I'd like to do some other investigation
That function is indeed what I mentioned 😄
I will work on reproducing the dataset. I will be back when I have the dataset 😉
So I managed to reproduce the problematic data bug_merged.zip
When calling compute_normals on the dataset the interpreter crashes without errors just like I described. However giving the dataset a closer look I see that the the indices of the faces are out of bounds
import pyvista
mesh = pyvista.read("bug_merged.vtk")
print(mesh.faces.max())
# Returns 102
print(mesh.points.shape)
# Returns (102, 3)
So I can image this is problematic. To give some background information; the dataset is created by merging n datasets, every dataset should (should) contain 3 points and these point are connected by 1 face (namely faces=[3, 0, 1, 2]). I found a bug in my program that some datasets has only two points. So settings the faces to [3, 0, 1, 2] is obviously wrong on a dataset with n_points=2. However the error is raised pretty late, setting the faces, merging the datasets all go right and no function complains about the malformed faces array. Only at the point of computing the normals the program crashes...
I made a small example to recreate the malformed dataset:
import pyvista
import numpy as np
meshes = []
for i in range(3):
if i == 0:
xyz = np.random.random(2*3).reshape(-1, 3)
else:
xyz = np.random.random(3*3).reshape(-1, 3)
faces = np.array([[3, 0, 1, 2]])
mesh = pyvista.PolyData()
mesh.points = xyz
mesh.faces = faces
mesh.delaunay_2d()
meshes.append(mesh)
merged = pyvista.PolyData().merge(meshes, merge_points=False)
>>> merged
PolyData (0x18105e45880)
N Cells: 3
N Points: 9
X Bounds: 7.980e-02, 9.152e-01
Y Bounds: 2.071e-01, 8.445e-01
Z Bounds: 5.083e-02, 8.073e-01
N Arrays: 0
>>> merged.faces.reshape(-1, 4)
array([[3, 0, 1, 2],
[3, 2, 3, 4],
[3, 5, 6, 7]], dtype=int64)
But look at the faces attribute. Ofcourse the dataset is invalid, but merging should complain about this, or when setting the faces array a check should be done if self.faces.max() < self.n_points. This dataset is even plottable and normals can be calculated.
However if I place the malformed dataset to the end of the for loop:
meshes = []
for i in range(3):
if i == 2:
xyz = np.random.random(2*3).reshape(-1, 3)
else:
xyz = np.random.random(3*3).reshape(-1, 3)
faces = np.array([[3, 0, 1, 2]])
mesh = pyvista.PolyData()
mesh.points = xyz
mesh.faces = faces
mesh.delaunay_2d()
meshes.append(mesh)
merged = pyvista.PolyData().merge(meshes, merge_points=False)
>>> merged
PolyData (0x18105e7fe20)
N Cells: 3
N Points: 8
X Bounds: 3.444e-01, 7.444e-01
Y Bounds: 6.473e-02, 8.465e-01
Z Bounds: 3.494e-02, 9.298e-01
N Arrays: 0
>>> merged.faces.reshape(-1, 4)
array([[3, 0, 1, 2],
[3, 3, 4, 5],
[3, 6, 7, 8]], dtype=int64)
The faces matrix goes up to 8 which is of course out-of-bounds due to n_points=8.
So easiest I think is just add a check when setting faces? PS.: sorry for flagging this as bug, the problem was due to my own fault 😢
Thank you for this thorough investigative work, @Onandon11! I think the poor handling of this "malformed" mesh resulting in a crash is worthy of the "bug" label.
I think we should use this to add a check to the faces for compute_normals() or at least issue a warning. We could also add some sort of warning in the merge if we detect it is producing a "malformed" mesh
The mesh validation methods in #8164 can help address this. E.g. mesh validation with the original example:
import numpy as np
xyz = np.random.random(102*3).reshape(-1,3)
faces = np.arange(102*3)
np.random.shuffle(faces)
faces = faces.reshape(-1,3)
faces = np.column_stack((np.ones(len(faces),) * 3, faces)).astype(int)
mesh = pyvista.PolyData()
mesh.points = xyz
mesh.faces = faces
mesh.validate_mesh(action='error')
# pyvista.core.errors.InvalidMeshError: PolyData mesh is not valid due to the following problems:
# - Mesh has 97 cells with invalid point references. Invalid cell ids: [ 0 1 2 3 4 5 6 8 9 10 11 12 13 14 15 16 17 18
# 19 20 21 22 23 24 26 27 28 29 30 31 32 33 34 35 36 37
# 38 39 40 41 42 43 44 45 46 47 48 49 51 52 53 54 55 56
# 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
# 75 76 77 78 79 80 81 82 83 84 86 87 88 89 90 91 93 94
# 95 96 97 98 99 100 101]