18

I need to plot a 3D point cloud (number of points: N), then a convex hull (actually a polyhedron with N vertices) from the points. I made a script in python with scipy.spatial ConvexHull for plot 8 points and plot a cube, the plot of the point cloud is ok, but the cube is not ok, because the code puts two lines going across the diagonal face of the cube in addition to the edge lines. I don't understand why plot lines across faces.

The script:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull  

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

points= np.array([[0,0,0],
            [4,0,0],
            [4,4,0],
            [0,4,0],
            [0,0,4],
            [4,0,4],
            [4,4,4],
            [0,4,4]])

hull=ConvexHull(points)

edges= zip(*points)

for i in hull.simplices:
    plt.plot(points[i,0], points[i,1], points[i,2], 'r-')

ax.plot(edges[0],edges[1],edges[2],'bo') 

ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')

ax.set_xlim3d(-5,5)
ax.set_ylim3d(-5,5)
ax.set_zlim3d(-5,5)

plt.show()

Result of the script:

enter image description here

Feri
  • 181
  • 1
  • 1
  • 5

2 Answers2

25

I know this is old, but I came here from Google so I think others might too.

The problem is only in the plotting method you use. One simplex is a nD triangle defined by 3 points. But the plotting function must cycle back to the last point, otherwise only 2 of 3 simplex edges are drawn.

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial import ConvexHull


# 8 points defining the cube corners
pts = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
                [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1], ])

hull = ConvexHull(pts)

fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")

# Plot defining corner points
ax.plot(pts.T[0], pts.T[1], pts.T[2], "ko")

# 12 = 2 * 6 faces are the simplices (2 simplices per square face)
for s in hull.simplices:
    s = np.append(s, s[0])  # Here we cycle back to the first coordinate
    ax.plot(pts[s, 0], pts[s, 1], pts[s, 2], "r-")

# Make axis label
for i in ["x", "y", "z"]:
    eval("ax.set_{:s}label('{:s}')".format(i, i))

plt.show()

image

Anders Kaseorg
  • 3,657
  • 22
  • 35
Thorben Menne
  • 251
  • 3
  • 5
2

As I understand, you want the edges only (so I don't understand Thorben's answer).

To get a convex hull and its edges, use pycddlib (the cdd library):

# -*- coding: utf-8 -*-
import numpy as np
import cdd as pcdd
import matplotlib.pyplot as plt

points= np.array([
    [0,0,0],
    [4,0,0],
    [4,4,0],
    [0,4,0],
    [0,0,4],
    [4,0,4],
    [4,4,4],
    [0,4,4]
])

# to get the convex hull with cdd, one has to prepend a column of ones
vertices = np.hstack((np.ones((8,1)), points))

# do the polyhedron
mat = pcdd.Matrix(vertices, linear=False, number_type="fraction") 
mat.rep_type = pcdd.RepType.GENERATOR
poly = pcdd.Polyhedron(mat)

# get the adjacent vertices of each vertex
adjacencies = [list(x) for x in poly.get_input_adjacency()]

# store the edges in a matrix (giving the indices of the points)
edges = [None]*(8-1)
for i,indices in enumerate(adjacencies[:-1]):
    indices = list(filter(lambda x: x>i, indices))
    l = len(indices)
    col1 = np.full((l, 1), i)
    indices = np.reshape(indices, (l, 1))
    edges[i] = np.hstack((col1, indices))
Edges = np.vstack(tuple(edges))

# plot
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")

start = points[Edges[:,0]]
end = points[Edges[:,1]]

for i in range(12):
    ax.plot(
        [start[i,0], end[i,0]], 
        [start[i,1], end[i,1]], 
        [start[i,2], end[i,2]],
        "blue"
    )

ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")

ax.set_xlim3d(-1,5)
ax.set_ylim3d(-1,5)
ax.set_zlim3d(-1,5)

plt.show()

enter image description here

Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225