Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find the projection of a point on the convex hull with Scipy

From a set of points, I'm getting the convex hull with scipy.spatial, either with Delaunay or ConvexHull (from the qhull library). Now I would like to get the projection of a point outside this convex hull onto the hull (i.e. the point on the hull that is the smallest distance from the point outside).

This is the code I have so far:

from scipy.spatial import Delaunay, ConvexHull
import numpy as np

hu = np.random.rand(10, 2) ## the set of points to get the hull from
pt = np.array([1.1, 0.5]) ## a point outside
pt2 = np.array([0.4, 0.4]) ## a point inside
hull = ConvexHull(hu) ## get only the convex hull
#hull2 = Delaunay(hu) ## or get the full Delaunay triangulation

import matplotlib.pyplot as plt
plt.plot(hu[:,0], hu[:,1], "ro") ## plot all points
#plt.triplot(hu[:,0], hu[:,1], hull2.simplices.copy()) ## plot the Delaunay triangulation
## Plot the convexhull
for simplex in hull.simplices:
    plt.plot(hu[simplex,0], hu[simplex,1], "ro-")

## Plot the points inside and outside the convex hull 
plt.plot(pt[0], pt[1], "bs")
plt.plot(pt2[0], pt2[1], "bs")
plt.show()

With a picture it might be easier, I would like to obtain the x and y coordinates in green from the blue point outside the convex hull. The example is 2d but I would need to apply it in higher dimension as well. Thanks for the help.

Convex hull and point to obtain in green

EDIT: The problem is addressed here but I have trouble implementing it: https://mathoverflow.net/questions/118088/projection-of-a-point-to-a-convex-hull-in-d-dimensions

like image 737
Jean Avatar asked Mar 10 '23 20:03

Jean


1 Answers

I am answering to myself. As 0Tech pointed out, ConvexHull.equations gives you the plane equations for each plane (in 2d --- a line therefore) with the form : [A, B, C]. The plane is therefore defined by

A*x + B*y + C = 0

Projecting the point P=(x0, y0) on the plane is explained here. You want a point on the vector parallel to the plane vector (A, B) and passing through the point to project P, this line is parameterised by t:

P_proj = (x, y) = (x0 + A*t, y0 + B*t)

You then want your point to be on the plane and uses the full plane equation to do that:

A*(x0 + A*t) + B*(y0 + B*t) + C = 0
=> t=-(C + A*x0 + B*y0)/(A**2+B**2)

In (clumsy) python, it gives for any dimension:

from scipy.spatial import Delaunay, ConvexHull
import numpy as np

hu = np.random.rand(10, 2) ## the set of points to get the hull from
pt = np.array([1.1, 0.5]) ## a point outside
pt2 = np.array([0.4, 0.4]) ## a point inside
hull = ConvexHull(hu) ## get only the convex hull
#hull2 = Delaunay(hu) ## or get the full Delaunay triangulation

import matplotlib.pyplot as plt
plt.plot(hu[:,0], hu[:,1], "ro") ## plot all points
#plt.triplot(hu[:,0], hu[:,1], hull2.simplices.copy()) ## plot the Delaunay triangulation
## Plot the convexhull
for simplex in hull.simplices:
    plt.plot(hu[simplex,0], hu[simplex,1], "ro-")

## Plot the points inside and outside the convex hull 
plt.plot(pt[0], pt[1], "bs")
plt.plot(pt2[0], pt2[1], "bs")

for eq in hull.equations:
    t = -(eq[-1] + np.dot(eq[:-1], pt))/(np.sum(eq[:-1]**2))
    pt_proj = pt + eq[:-1]*t
    plt.plot(pt_proj[0], pt_proj[1], "gD-.")
plt.show()

Browsing stackoverflow, led me to another solution, that has the advantage of using segments instead of the lines, so the projection on one of the segment always lie on the segment:

def min_distance(pt1, pt2, p):
    """ return the projection of point p (and the distance) on the closest edge formed by the two points pt1 and pt2"""
    l = np.sum((pt2-pt1)**2) ## compute the squared distance between the 2 vertices
    t = np.max([0., np.min([1., np.dot(p-pt1, pt2-pt1) /l])]) # I let the answer of question 849211 explains this
    proj = pt1 + t*(pt2-pt1) ## project the point
    return proj, np.sum((proj-p)**2) ## return the projection and the point

Then we can browse each vertices and project the point:

for i in range(len(hull.vertices)):
    pt_proj, d = min_distance(hu[hull.vertices[i]], hu[hull.vertices[(i+1)%len(hull.vertices)]], pt)
    plt.plot([pt[0], pt_proj[0]], [pt[1], pt_proj[1]], "c<:")

And the picture, with the projection of the blue point on the right on each plane (line) in green for the first method and cyan for the second method:

Projected points

like image 145
Jean Avatar answered May 03 '23 19:05

Jean