2

My algorithm outputs the set of vertices describing objects in 3D space (x, y, z). In this case, there are two objects:

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. ]]

There are two tetrahedrons, one confined between centered on (1, 1, 1), the other on (8, 8, 8). My goal is to use breadth-first search to identify that the objects are separate, and then classify each. I have not been able to get the data in the correct form for my algorithm.

Instead, I intend to use the networkx module, specifically using the Graph class, which takes ndarrays as input. I have tried:

import networkx as nx
import numpy as np

graph = Graph(verts)
for idx, graph in enumerate(nx.connected_components(graph)):
    print("Graph ",idx, " in ", graph,'\n\n',file=open("output.txt","a"))     

However, I cannot create graph. Instead, I get the error:

"Input is not a correct numpy matrix or array.")
networkx.exception.NetworkXError: Input is not a correct numpy matrix or array.

This confuses me because type of verts = numpy.ndarray.

I am open to either using networkx for this task, or developing some other strategy. Additionally, please let me know if there are any edits that might make this post more clear.

Tetrahedrons

Edit: One thing that may help is another output, faces. These 'define triangular faces via referencing vertex indices from verts.' I believe these can be used to 'connect' or draw lines from vertex to vertex, eventually to create a dictionary.

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]]

A method has been proposed, and it works for this set of data. However, it does not work for all. This edit uploads a new set of data.

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. ]
 [3.1 1.  4. ]  [4.  1.  3.1]  [4.  0.1 4. ]  [4.  1.  4.9]  [4.  1.9 4. ]  [5.  1.  3.1]
 [5.  0.1 4. ]  [5.  1.  4.9]  [5.  1.9 4. ]  [5.9 1.  4. ]  [7.1 8.  8. ]
 [8.  8.  7.1]  [8.  7.1 8. ]  [8.  8.  8.9]  [8.  8.9 8. ]  [9.  8.  7.1]
 [9.  7.1 8. ]  [9.  8.  8.9]  [9.  8.9 8. ]  [9.9 8.  8. ]]

And it looks like this. tetra_3

McM
  • 471
  • 5
  • 21

2 Answers2

2

I was able to answer this by another approach. It is lengthy because I need to include extra pieces. As a general outlook, I solved this problem by utilizing faces, which defines each triangle with the indices of its vertices. faces tells me which vertices are connected. This allowed me to build a linelist, which contains all of the connections between vertices.

# using faces and verts in original post
linelist = []
for idx, vert in enumerate(faces):
    print(vert)
    for i,x in enumerate(vert):
        l = [np.ndarray.tolist(verts[faces[idx][i]]), np.ndarray.tolist(verts[faces[idx][(i+1)%len(vert)]])]
        linelist.append(l)

Which yields elements like:

[[1.0, 0.10000000149011612, 1.0], [1.0, 1.0, 0.10000000149011612]]

Edit: Discovered faster method:

tmp = [tuple(tuple(j) for j in i) for i in linelist]
graph = nx.Graph(tmp)
graphs = []
i=0
open('output.txt','w').close()
for idx, graph in enumerate(nx.connected_components(graph)):
    graphs.append(graph)
    print("Graph ",idx," corresponds to vertices: ",graph,'\n\n',file=open("output.txt","a"))         
    i+=1

These points are connected. Next, I used someone else's code to create a dictionary where each key is a vertex and each value is a connected vertex. And then I used breath-first-search on this dictionary. See the class below.

class MS_Graph():
    def __init__ (self, linelist=None, vertices=None):
        self.linelist = linelist if linelist is not None else None
        self.vertices = vertices if vertices is not None else None

    def getGraph(self):
        '''
        Takes self.linelist and converts to dict
        '''
        linelist = self.linelist
        # edge list usually reads v1 -> v2
        graph = {}
        # however these are lines so symmetry is assumed
        for l in linelist:
            v1, v2 = map(tuple, l)
            graph[v1] = graph.get(v1, ()) + (v2,)      
            graph[v2] = graph.get(v2, ()) + (v1,)
        return graph

    def BFS(self, graph):
        """
        Implement breadth-first search
        """
        # get nodes
        #nodes = list(graph.keys()) # changed 4/16/2020
        nodes = list(graph)
        graphs = []
        # check all nodes 
        while nodes:
            # initialize BFS
            toCheck = [nodes[0]]
            discovered = []
            # run bfs
            while toCheck:
                startNode = toCheck.pop()
                for neighbor in graph.get(startNode):
                    if neighbor not in discovered:
                        discovered.append(neighbor)
                        toCheck.append(neighbor)
                        nodes.remove(neighbor)
            # add discovered graphs
            graphs.append(discovered)
        self.graphs = graphs
        return graphs

And, bringing it altogether:

Graph = MS_Graph(linelist)
graph = Graph.getGraph()
graphs = Graph.BFS(graph)
print(len(graphs))
# output: 3
print(graphs)
# output:
[[(1.0, 1.0, 0.10000000149011612), (0.10000000149011612, 1.0, 1.0), (1.0, 1.0, 1.899999976158142), (1.899999976158142, 1.0, 1.0), (1.0, 0.10000000149011612, 1.0), (1.0, 1.899999976158142, 1.0)], 
[(4.0, 1.0, 3.0999999046325684), (3.0999999046325684, 1.0, 4.0), (4.0, 1.0, 4.900000095367432), (5.0, 1.0, 3.0999999046325684), (5.0, 0.10000000149011612, 4.0), (4.0, 0.10000000149011612, 4.0), (5.0, 1.0, 4.900000095367432), (5.900000095367432, 1.0, 4.0), (5.0, 1.899999976158142, 4.0), (4.0, 1.899999976158142, 4.0)], 
[(8.0, 8.0, 7.099999904632568), (7.099999904632568, 8.0, 8.0), (8.0, 8.0, 8.899999618530273), (8.899999618530273, 8.0, 8.0), (8.0, 7.099999904632568, 8.0), (8.0, 8.899999618530273, 8.0)]]

That said, I do wonder if there is a faster method.

Edit: There may be a faster way. Since faces contains the vertices of every single triangle, all triangles that belong to one object will have an unbroken chain. i.e. the set of vertices composing object 1 will be distinct from the set of vertices composing any other object.

For example the set of faces for each object:

object_1_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]
object_2_faces =
 [ 8  7  6]
 [ 6  9  8]
 [ 7 10  6]
 [ 6 10  9]
 [11  7  8]
 [ 9 11  8]
 [11 10  7]
 [10 11  9]
object_1_vertices = {0,1,2,3,4,5}
object_2_vertices = {6,7,8,9,10,11}

I imagine this means there is a faster way than finding all of the lines.

McM
  • 471
  • 5
  • 21
1

The problem is how you're constructing the graph. You should first create a new instance of a graph with g = nx.Graph(), and then use its methods to either add its nodes or edges. In this case, you want to add its paths from the nested list:

G = nx.Graph()
for path in verts:
    nx.add_path(G, path)

And then obtain the connected components:

cc = list(nx.connected_components(G))
# [{0.1, 1.0, 1.9}, {7.1, 8.0, 8.9}]

Now if you wanted to find which component each path belongs to, you could iterate over the paths and check with which of the components they intersect:

from collections import defaultdict

subgraphs = defaultdict(list)

for path in verts:
    for ix,c in enumerate(cc):
        if c.intersection(path):
            subgraphs[ix].append(path)

print(subgraphs)

defaultdict(list,
            {0: [[0.1, 1.0, 1.0],
              [1.0, 1.0, 0.1],
              [1.0, 0.1, 1.0],
              [1.0, 1.0, 1.9],
              [1.0, 1.9, 1.0],
              [1.9, 1.0, 1.0]],
             1: [[7.1, 8.0, 8.0],
              [8.0, 8.0, 7.1],
              [8.0, 7.1, 8.0],
              [8.0, 8.0, 8.9],
              [8.0, 8.9, 8.0],
              [8.9, 8.0, 8.0]]})
yatu
  • 86,083
  • 12
  • 84
  • 139
  • Two questions: 1) What does the output of list(nx.connected_components(G)) mean? I was expecting to find two separate graphs, not two points.. Each graph has 6 vertices, so I was expecting it to return 6 points. 2) Is this method as fast as any other method that might be used? – McM Apr 17 '20 at 21:29
  • Well not too sure what you mean really by `identify that the objects are separate`. These components are the nodes in the graph that are connected, that are linked to each other. It looks like you might want to see if they are within some boundaries? @mmont – yatu Apr 17 '20 at 21:32
  • Right, I think I know how to go about this. Give me a few mins @mmont – yatu Apr 17 '20 at 21:37
  • Well wait, what output do you expect exactly? @mmont – yatu Apr 17 '20 at 21:39
  • Yea, thanks. Just to clarify, there are two objects there. I want to output something like: Graph 1 - ( (0,0,1), (1,0,0) ... etc), Graph 2 - ( (8,7,7), (7,7,8), ... etc ). Basically, it would identify, in this example, 2 graphs, and output the points composing each. – McM Apr 17 '20 at 21:39
  • Okay so the members of each subgraph – yatu Apr 17 '20 at 21:40
  • Just spitballing, but it might be that the input of vertices is wrong. I think if I had a set of lines linking each vertex to another, and input that into Graph, it might work. – McM Apr 17 '20 at 21:44
  • Updated @mmont hope this helps – yatu Apr 17 '20 at 21:55
  • it comes very close! However, if I add another object (graph), and it shares an axis with some graph, then it will group the two together as one subgraph. I've thought of another method, I don't want to take up too much of your time. Just thought I'd fill you in. It has helped! – McM Apr 17 '20 at 23:02
  • Me too :) I've accepted it, as it is the best current answer. I've uploaded another dataset if you get bored. There's also a secondary dataset that might help, which includes the faces of each triangle. This will likely be a long post! @yatu – McM Apr 17 '20 at 23:09
  • Hm, that's really cool @yatu, and it could be done in 3D: https://stackoverflow.com/questions/36917944/label-3d-numpy-array-with-scipy-ndimage-label. Currently I am using marching cubes, which also performs interpolation and outputs some other useful things. https://kite.com/python/docs/skimage.measure.marching_cubes_lewiner But that may be worth a try, hmmm. – McM Apr 17 '20 at 23:23
  • Never actually tried it in 3d. Perhaps add a follow up question or even a post [answering your own question](https://stackoverflow.com/help/self-answer) if you get this solved, it a very interesting problem :) Ping me if you do @mmont – yatu Apr 17 '20 at 23:27
  • hey @yatu, I got it! :) I ended up using the faces to connect the vertices, giving me a list of lines (edges) or linelist. Then I used someone else's code to create a dictionary, ran breadth first search, and voila! it worked. lol. kind of surprised but there it is.Looking back, it seems fairly obvious that I would need to use ```faces```. Anyway, thanks for all your help! – McM Apr 18 '20 at 01:05
  • hey @yatu, gotta question but not sure if it's big enough for a post. Now that I have these subgraphs, is there a way of finding the volume contained within each subgraph? – McM Apr 23 '20 at 01:38
  • it seems a few of our comments were deleted. I remember you mentioning a python method, called label, which would search the array for arrangements that meet the a given pattern. For example, label = [[0, 1, 0], [1,1,1],[0,1,1]], which looks like a plus sign. I can't remember the specifics, but I am trying to use that function to do a rough estimate of volume for each 3D object. Do you remember anything like this? – McM May 03 '20 at 03:16
  • Yes I mentioned you might find scipy's component labeling functions useful. Find it [here](https://docs.scipy.org/doc/scipy-0.16.0/reference/generated/scipy.ndimage.measurements.label.html) @mmont – yatu May 03 '20 at 10:57
  • It looks like you want image data connected component labeling, rather than from a graph theory perspective, in the sense that, only components that are next to each other are labeled as the same component. You may also check [fill-bounding-boxes](https://stackoverflow.com/questions/54638817/fill-bounding-boxes-in-2d-array/54675526#54675526) @mmont – yatu May 03 '20 at 10:59