问题
Edit: I have written a more succinct version of this question here but I am keeping this post because it is a full explanation.
Given a 3D numpy array, marching cubes can form a 3D object around some threshold.
import numpy as np
from skimage import measure
A = np.zeros((12,12,12))
#A[A<1] = -1
for i in np.arange(1,2):
for j in np.arange(1,2):
for k in np.arange(1,2):
A[i,j,k] = 10
for i in np.arange(8,9):
for j in np.arange(8,9):
for k in np.arange(8,9):
A[i,j,k] = 10
verts, faces, normals, values = measure.marching_cubes_lewiner(A,1)
# which returns
verts = [[0.1, 1., 1. ] [1., 1., 0.1] [1., 0.1, 1. ] [1., 1., 1.9] [1., 1.9, 1. ]
[1.9, 1., 1. ] [7.1, 8., 8. ] [8., 8., 7.1] [8., 7.1, 8. ] [8., 8., 8.9]
[8., 8.9, 8. ] [8.9, 8., 8. ]]
faces = [[ 2, 1, 0] [ 0, 3, 2] [ 1, 4, 0] [ 0, 4, 3] [ 5, 1, 2] [ 3, 5, 2]
[ 5, 4, 1] [ 4, 5, 3] [ 8, 7, 6] [ 6, 9, 8] [ 7, 10, 6] [ 6, 10, 9]
[11, 7, 8] [ 9, 11, 8] [11, 10, 7] [10, 11, 9]]
This can be plotted:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from mpl_toolkits.mplot3d import Axes3D
mesh = Poly3DCollection(verts[faces])
mesh.set_edgecolor('k')
mesh.set_facecolor('b')
ax.set_xlim(0,10)
ax.set_ylim(0,10)
ax.set_zlim(0,12)
Returning this lovely 3D image:
I use an algorithm to separate these objects using my own code (see below) and get:
graph1 = {(1.0, 1.0, 0.10000000149011612), (1.899999976158142, 1.0, 1.0), (0.10000000149011612, 1.0, 1.0), (1.0, 1.899999976158142, 1.0), (1.0, 0.10000000149011612, 1.0), (1.0, 1.0, 1.899999976158142)}
graph2 = {(8.899999618530273, 8.0, 8.0), (8.0, 8.899999618530273, 8.0), (7.099999904632568, 8.0, 8.0), (8.0, 8.0, 7.099999904632568), (8.0, 7.099999904632568, 8.0), (8.0, 8.0, 8.899999618530273)}
Now, the problem is that, even though I have found the vertices composing each graph, I no longer have the an easy way to create separate 3D meshes for each object. Whereas before, verts[faces]
was used to create a mesh, it is not obvious how to relate each graph
to faces
to create triangular meshes. I've tried to solve this but have not been successful. For example:
verts1 = verts[0:6]
faces1 = faces[0:6]
mesh = Poly3DCollection(verts1[faces1])
This does not work. I think the key would be to find the faces that correspond to each object. If that were done, it might work. For example, our first graph includes only vertices 1 through 6. So we only need the faces
that refer to those vertices. As a demonstration, the first graph, graph1
can be reproduced (without graph2) using:
faces1 = faces[0:8]
mesh = Poly3DCollection(verts[faces1])
# and plot like above
If I could record which not only the vertices, but their index, then I might be able to sort faces
for those which refer to that object. I'll explain. First problem, I do not have the indices. This is my way of sorting objects. We first create a linelist (or edgelist), then we make tuples of them, and then use networkx to find connected components.
# create linelist
linelist = []
for idx, vert in enumerate(faces):
for i,x in enumerate(vert):
l = [np.ndarray.tolist(verts[faces[idx][i]]), np.ndarray.tolist(verts[faces[idx][(i+1)%len(vert)]])] # connect the verts of the triangle
linelist.append(l) # add to the line list
# Creates graph
tmp = [tuple(tuple(j) for j in i) for i in linelist]
graph = nx.Graph(tmp)
graphs = []
i=0
for idx, graph in enumerate(sorted(nx.connected_components(graph),key = len, reverse = True)):
graphs.append((graph))
print("Graph ",idx," corresponds to vertices: ",graph,'\n\n',file=open("output.txt","a"))
i+=1
I do not see how networkx could also record the index of each vertex.
Secondly, it's possible that the faces
referring to each object are disjoint, i.e. it may be faces[0:4] + faces[66] + faces[100:110]
. However, that can likely be overcome.
Assuming that we can generate a list of indices for each graph, the main problem is discovering an efficient way of discovering which faces refer to those vertices. My solution works for this set of objects, but not for more complicated arrangements (which I can provide). It is also extraordinarily slow. Still, here it is:
objects = []
obj = []
i = 0
for idx, face in enumerate(M):
if i == 0:
obj.append(face)
i = i + 1
else:
if np.isin(face,obj).any():
obj.append(face)
else:
objects.append(obj.copy())
obj = []
obj.append(face)
i = 0
if idx == len(M)-1:
objects.append(obj.copy())
If you have read this far, I am truly impressed with the community. I think there is an efficient way of doing this perhaps with networkx, but I have not found it.
Desired output: I want to sort the faces into connected components just as I sort the verts. graph1 = faces[x1] + faces[x2] + ... + faces[xn]
.
Edit: If someone could help me with the coding, I do have an idea (thanks in part to @Ehsan). After separating into connected components and finding the graphs, the vertices of each could be hashed to find original index. Then, one might be able to search for faces
which include at least one of those indices (since if it contains one vertex, it must be a face of the graph
). I'm not sure how efficient this would be. I'd love if there were a fast networkx workaround.
回答1:
@Paul Broderson answered this question https://stackoverflow.com/a/61590348/12919727
I will put it here just for aesthetics:
#!/usr/bin/env python
"""
Given a list of triangles, find the connected components.
https://stackoverflow.com/q/61584283/2912349
"""
import itertools
import networkx as nx
faces = [[ 2, 1, 0], [ 0, 3, 2], [ 1, 4, 0], [ 0, 4, 3], [ 5, 1, 2], [ 3, 5, 2],
[ 5, 4, 1], [ 4, 5, 3], [ 8, 7, 6], [ 6, 9, 8], [ 7, 10, 6], [ 6, 10, 9],
[11, 7, 8], [ 9, 11, 8], [11, 10, 7], [10, 11, 9]]
#create graph
edges = []
for face in faces:
edges.extend(list(itertools.combinations(face, 2)))
g = nx.from_edgelist(edges)
# compute connected components and print results
components = list(nx.algorithms.components.connected_components(g))
for component in components:
print(component)
# {0, 1, 2, 3, 4, 5}
# {6, 7, 8, 9, 10, 11}
# separate faces by component
component_to_faces = dict()
for component in components:
component_to_faces[tuple(component)] = [face for face in faces if set(face) <= component] # <= operator tests for subset relation
for component, component_faces in component_to_faces.items():
print(component, component_faces)
# (0, 1, 2, 3, 4, 5) [[2, 1, 0], [0, 3, 2], [1, 4, 0], [0, 4, 3], [5, 1, 2], [3, 5, 2], [5, 4, 1], [4, 5, 3]]
# (6, 7, 8, 9, 10, 11) [[8, 7, 6], [6, 9, 8], [7, 10, 6], [6, 10, 9], [11, 7, 8], [9, 11, 8], [11, 10, 7], [10, 11, 9]]
来源:https://stackoverflow.com/questions/61569196/given-a-set-of-triangle-vertices-and-faces-separate-objects-and-form-separate-m