70

I have a point cloud of coordinates in numpy. For a high number of points, I want to find out if the points lie in the convex hull of the point cloud.

I tried pyhull but I cant figure out how to check if a point is in the ConvexHull:

hull = ConvexHull(np.array([(1, 2), (3, 4), (3, 6)]))
for s in hull.simplices:
    s.in_simplex(np.array([2, 3]))

raises LinAlgError: Array must be square.

Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
AME
  • 2,499
  • 5
  • 29
  • 45

12 Answers12

111

Here is an easy solution that requires only scipy:

def in_hull(p, hull):
    """
    Test if points in `p` are in `hull`

    `p` should be a `NxK` coordinates of `N` points in `K` dimensions
    `hull` is either a scipy.spatial.Delaunay object or the `MxK` array of the 
    coordinates of `M` points in `K`dimensions for which Delaunay triangulation
    will be computed
    """
    from scipy.spatial import Delaunay
    if not isinstance(hull,Delaunay):
        hull = Delaunay(hull)

    return hull.find_simplex(p)>=0

It returns a boolean array where True values indicate points that lie in the given convex hull. It can be used like this:

tested = np.random.rand(20,3)
cloud  = np.random.rand(50,3)

print in_hull(tested,cloud)

If you have matplotlib installed, you can also use the following function that calls the first one and plots the results. For 2D data only, given by Nx2 arrays:

def plot_in_hull(p, hull):
    """
    plot relative to `in_hull` for 2d data
    """
    import matplotlib.pyplot as plt
    from matplotlib.collections import PolyCollection, LineCollection

    from scipy.spatial import Delaunay
    if not isinstance(hull,Delaunay):
        hull = Delaunay(hull)

    # plot triangulation
    poly = PolyCollection(hull.points[hull.vertices], facecolors='w', edgecolors='b')
    plt.clf()
    plt.title('in hull')
    plt.gca().add_collection(poly)
    plt.plot(hull.points[:,0], hull.points[:,1], 'o', hold=1)


    # plot the convex hull
    edges = set()
    edge_points = []

    def add_edge(i, j):
        """Add a line between the i-th and j-th points, if not in the list already"""
        if (i, j) in edges or (j, i) in edges:
            # already added
            return
        edges.add( (i, j) )
        edge_points.append(hull.points[ [i, j] ])

    for ia, ib in hull.convex_hull:
        add_edge(ia, ib)

    lines = LineCollection(edge_points, color='g')
    plt.gca().add_collection(lines)
    plt.show()    

    # plot tested points `p` - black are inside hull, red outside
    inside = in_hull(p,hull)
    plt.plot(p[ inside,0],p[ inside,1],'.k')
    plt.plot(p[-inside,0],p[-inside,1],'.r')
Sildoreth
  • 1,883
  • 1
  • 25
  • 38
Juh_
  • 14,628
  • 8
  • 59
  • 92
  • 1
    Is it lso possible to find the outer points of the convex hull of a point cloud? Because I want to remove those points from a distance calculation that are form the outer triangles and often have high distances – Liwellyen Apr 10 '18 at 17:08
  • 1
    It's quite simple actually: let `cloud` be a NxK arrays of N points in K dimension, `ConvexHull(cloud).vertices` (from scipy.spatial) gives the indices of the points on the convex hull, i.e. the "outer points" – Juh_ Apr 12 '18 at 07:32
  • @Juh_ Are there any alternatives to Delaunay triangulation when it comes to computing a hull? Can one safely assume that that it's a reliable method? I'm only asking because, given my limited knowledge in linear algebra, your function `in_hull()` seems like a black box. E.g., I couldn't find any documentation motivating why `hull.find_simplex(p)>=0` is tantamount to membership in the hull. – Tfovid May 03 '19 at 09:32
  • 1
    You can safely assume it is a reliable method, as it is explained in the doc of `Delaunay.find_simplex` which returns -1 for point outside of the hull. Now, if you want more control, or want a faster algorithm, I recommend the solution of @nils below. It's more complex but compute only what is needed (I didn't test it, but it looks like it is) – Juh_ May 06 '19 at 15:23
  • @Juh_ So at the end of the day, `scipy.spatial.ConvexHull()` isn't really needed since `Delaunay()` does the job? I would have assumed the former to be the go-to function when it comes to computing hulls. Doesn't `ConvexHull()` have its own implementation of containment in the hull akin to `Delaunay`'s `find_simplex()`? – Tfovid May 07 '19 at 14:34
  • 1
    Yes: ConvexHull doesn't provide the suitable api. Here I propose to use a method that does more than required but is easy to implement. Note that I stop using scipy couple of years ago, so it could evolve. – Juh_ May 07 '19 at 15:21
  • 1
    'TypeError: float() argument must be a string or a number' on line `hull = Delaunay(hull)`. Any ideas? – Gulzar Nov 27 '19 at 16:51
  • Not really. What is the type of your hull input ? Does it fit the requirements given in the function doc? – Juh_ Nov 27 '19 at 17:02
  • `self._hull = spatial.Delaunay(spatial.ConvexHull(np.random.uniform(-5, 5, (20, 3))))` gives `File "qhull.pyx", line 1815, in scipy.spatial.qhull.Delaunay.__init__ File "C:\code\Noam\convex_volume_filler\venv\lib\site-packages\numpy\core\numeric.py", line 632, in ascontiguousarray return array(a, dtype, copy=False, order='C', ndmin=1) TypeError: float() argument must be a string or a number` did I not fit some requirement? It works up to the conversion to Delaunay – Gulzar Nov 28 '19 at 09:15
  • @Juh_ I have the same error as @Gulzar . If the hull is constructed using `convex_hull = scipy.spatial.ConvexHull(points)` and then I run `in_hull(points, convex_hull)`, I receive the error `{TypeError}float() argument must be a string or a number, not 'ConvexHull'` – Rylan Schaeffer Apr 30 '20 at 20:34
  • 1
    Solution for 'TypeError: float() argument must be a string or a number, not 'ConvexHull'': `hull = Delaunay(hull.vertices)` – thepenguin77 Jun 30 '20 at 14:22
  • As a note, the docs for `Delaunay.convex_hull (ndarray)` mentions that it may be less numerically stable than using `ConvexHull`: [docs](https://docs.scipy.org/doc/scipy-1.5.0/reference/generated/scipy.spatial.Delaunay.convex_hull.html); however, maybe for this 2D case, the numerical stability isn't an issue? (As an aside, `ConvexHull` provides its simplices, so it'd be nice if you could simply doing something like `hull.find_simplex(p)`, as was mentioned above) – Eric Cousineau Jul 03 '20 at 17:49
  • Can someone explain why this method is failing for the person in this question? https://stackoverflow.com/questions/51771248/checking-if-a-point-is-in-convexhull – xuiqzy Nov 11 '20 at 20:58
  • As the answer to the question you mentioned said: it is a numerical issue that happens from almost flat triangle. I didn't check, but that seems quite logical. The answer is also a good solution: use below https://stackoverflow.com/a/42165596/1206998 that provides a numerical precision parameter – Juh_ Nov 12 '20 at 10:10
53

I would not use a convex hull algorithm, because you do not need to compute the convex hull, you just want to check whether your point can be expressed as a convex combination of the set of points of whom a subset defines a convex hull. Moreover, finding the convex hull is computationally expensive, especially in higher dimensions.

In fact, the mere problem of finding out whether a point can be expressed as a convex combination of another set of points can be formulated as a linear programming problem.

import numpy as np
from scipy.optimize import linprog

def in_hull(points, x):
    n_points = len(points)
    n_dim = len(x)
    c = np.zeros(n_points)
    A = np.r_[points.T,np.ones((1,n_points))]
    b = np.r_[x, np.ones(1)]
    lp = linprog(c, A_eq=A, b_eq=b)
    return lp.success

n_points = 10000
n_dim = 10
Z = np.random.rand(n_points,n_dim)
x = np.random.rand(n_dim)
print(in_hull(Z, x))

For the example, I solved the problem for 10000 points in 10 dimensions. The executions time is in the ms range. Would not want to know how long this would take with QHull.

Nils
  • 818
  • 7
  • 16
  • Nice. I also found it is not efficient to compute the full hull (however it is really efficient to code it). Do you have a link to some theorical background on "convex combination of a set of points"? – Juh_ May 22 '18 at 15:17
  • 2
    @Juh_: Denote {x_1,...,x_n} as set of n points, {w_1,...,w_n} as variable weights, and y as the point that you want to describe through a combination of these n points. Then \sum_i w_i x_i = y_i and , then you want to – Nils May 24 '18 at 03:49
  • 1
    @Juh_: ... make sure that \sum_i w_i = 1 and w_i >= 0. I used linear programming to find w_i, but there may be other ways. – Nils May 24 '18 at 03:56
  • 1
    Now, if I understand correctly, you only want to know if the linear problem has a solution, and so there is no real optimization? – Juh_ May 24 '18 at 09:06
  • It's not easy to understand what you do only reading the code. Some comment showing the math behind would be useful. For example: where is the inequality constraint on `w_i`? and why is it necessary to add the "scaling" dimension (the 1s)? – Juh_ May 24 '18 at 09:11
  • 3
    @Juh_ That's tricky. I cannot write math here. Scipy assumes you have the following problem: min_x {c'w | Aw=b, w>=0}, where w are the variables, c are objective coefficients, and Aw=b are the constraints (w>=0 is default in LP). As c is zero, there is no real optimization. The solver simply checks feasibility, i.e., whether there exists a w such that Aw=b is satisfied. Now, in our case b = [y_1,...,y_d,1] and A = [[x_11 w_1,...,x_n1 w_n],...,[x_1d w_1,...,x_nd w_n],[w_1,...,w_n]]. In the code above the query point y is called x and the point set x is called 'points'. – Nils May 25 '18 at 09:47
  • 2
    @Juh_ "Why is it necessary to add the "scaling" dimension (the 1s)?" This is the requirement for having a convex combination, otherwise you'd check if the point lies in a cone, which is not what you want. – Nils May 25 '18 at 09:50
  • I like this method, but for 3D points, the convex hull method was faster for me. Of course I'm sure this method will be faster once n_dim >> 3 – tamtam Oct 11 '18 at 20:20
  • I want to evaluate many 2-D points to identify which are within the convex hull defined by another set of points. To adapt this solution, I looped over the `lp = linprog(...)` line. This worked but took much longer than the answer by @Juh_. For an example looking at 10000 points within a 2-D hull defined by 4 points, this method took ~4000x longer. I expect there is a much better way to apply this to many points than my loop implementation. – Steven C. Howell Oct 29 '18 at 19:52
  • For starter, you can put the code in a class and call, for each point, only the `linprog(...).success`. After that, it is probably possible to implement the linprog algorithm, well only the part that test if a solution exist, and maybe it would be possible to avoid recomputing many other things – Juh_ Oct 30 '18 at 14:39
  • 1
    scipy‘s linprog is based on the simplex algorithm. If the set of points that defines the convex hull does not change but just the query point, you can warm start the simplex by keeping the current basis in memory. I do not think that this is possible with linprog and you might have to look for other implementations of linear programming like CyLP (https://github.com/coin-or/CyLP). – Nils Oct 31 '18 at 08:12
  • I do not understand what Z should represent. Is this only the point cloud or is it the combination of point cloud and point of interest? – S.A Nov 28 '19 at 14:04
  • 1
    @S.A `Z` is the point cloud.T the weights of the points in `Z` that would describe `x` (if any) are stored as an attribute of `lp`. – Nils Nov 29 '19 at 04:41
  • 2
    Good solution. But I find that it is very slow when I check if 10 000 points belong to convex hull –  Dec 30 '19 at 14:02
  • Note that the constraint 'w_u >= 0' does not explicitly appear in the code of this answer but is satisfied as a result of the default parameter value `bounds = (0,None)` in scipy's `linprog` function. Set `bounds = (None, None)` to decide membership of the _affine_ hull rather than convex hull. – Tein van der Lugt Mar 13 '22 at 18:00
28

Hi I am not sure about how to use your program library to achieve this. But there is a simple algorithm to achieve this described in words:

  1. create a point that is definitely outside your hull. Call it Y
  2. produce a line segment connecting your point in question (X) to the new point Y.
  3. loop around all edge segments of your convex hull. check for each of them if the segment intersects with XY.
  4. If the number of intersection you counted is even (including 0), X is outside the hull. Otherwise X is inside the hull.
  5. if so occurs XY pass through one of your vertexes on the hull, or directly overlap with one of your hull's edge, move Y a little bit.
  6. the above works for concave hull as well. You can see in below illustration (Green dot is the X point you are trying to determine. Yellow marks the intersection points. illustration
firemana
  • 487
  • 4
  • 7
  • 6
    +1 Nice approach. It is probably easier, for a convex hull, to find a point that's definitely inside the hull (the average of all the hull vertices) then follow your method with reversed conditions for success. – Jaime Feb 11 '14 at 14:35
  • 1
    Although this is a little nitpicky, there are a couple cases where this will fail: 1) If you pick a point that is colinear with a pair of vertices on the hull and the test point is also colinear with those vertices as well, then you would technically get an infinite number of intersections. 2) if your test point and X and outer point Y are colinear with a vertex on the intersection of an odd number of facets (3d case) then you would erroneously conclude that the test point is actually inside of the hull... at the very least, you may need to check for case 2. E.g. ensure non-colinearity of XYV – wmsmith Oct 06 '17 at 18:34
  • 1
    also, note that some of polygon in the example are not *convex* hulls, for a convex hull you will find at most two intersection. It is also not immediate to me how to select a point that is "definitely outside" the hull. Maybe easier to find a point "definitely inside" (e.g. barycenter) and see if it has one or zero intersections, that also remove colinearity problems (I am assuming the hull is a convex polygon). – Vincenzooo May 30 '18 at 18:17
  • This requires the convex hull (as a polygon) to be found first. But this step is not necessary for the overall task, as Nils's solution shows. – Keenan Pepper Sep 15 '19 at 16:08
  • 2
    @Vincenzooo if you find the minimal point (in a lexicographical ordering) and then subtract by some amount in all dimensions you are definitely outside the hull. In addition, sometimes you might have extra knowledge about what range the points can lie in which makes the task trivial. – Andreas Vinter-Hviid Jan 30 '20 at 08:45
27

First, obtain the convex hull for your point cloud.

Then loop over all of the edges of the convex hull in counter-clockwise order. For each of the edges, check whether your target point lies to the "left" of that edge. When doing this, treat the edges as vectors pointing counter-clockwise around the convex hull. If the target point is to the "left" of all of the vectors, then it is contained by the polygon; otherwise, it lies outside the polygon.

Loop and Check whether point is always to the "left"

This other Stack Overflow topic includes a solution to finding which "side" of a line a point is on: Determine Which Side of a Line a Point Lies


The runtime complexity of this approach (once you already have the convex hull) is O(n) where n is the number of edges that the convex hull has.

Note that this will work only for convex polygons. But you're dealing with a convex hull, so it should suit your needs.

It looks like you already have a way to get the convex hull for your point cloud. But if you find that you have to implement your own, Wikipedia has a nice list of convex hull algorithms here: Convex Hull Algorithms

Community
  • 1
  • 1
Sildoreth
  • 1,883
  • 1
  • 25
  • 38
  • 1
    If someone has already calculated the convex hull of points, then this approach is the simplest one. – CODError Jul 23 '16 at 10:22
19

Use the equations attribute of ConvexHull:

def point_in_hull(point, hull, tolerance=1e-12):
    return all(
        (np.dot(eq[:-1], point) + eq[-1] <= tolerance)
        for eq in hull.equations)

In words, a point is in the hull if and only if for every equation (describing the facets) the dot product between the point and the normal vector (eq[:-1]) plus the offset (eq[-1]) is less than or equal to zero. You may want to compare to a small, positive constant tolerance = 1e-12 rather than to zero because of issues of numerical precision (otherwise, you may find that a vertex of the convex hull is not in the convex hull).

Demonstration:

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

points = np.array([(1, 2), (3, 4), (3, 6), (2, 4.5), (2.5, 5)])
hull = ConvexHull(points)

np.random.seed(1)
random_points = np.random.uniform(0, 6, (100, 2))

for simplex in hull.simplices:
    plt.plot(points[simplex, 0], points[simplex, 1])

plt.scatter(*points.T, alpha=.5, color='k', s=200, marker='v')

for p in random_points:
    point_is_in_hull = point_in_hull(p, hull)
    marker = 'x' if point_is_in_hull else 'd'
    color = 'g' if point_is_in_hull else 'm'
    plt.scatter(p[0], p[1], marker=marker, color=color)

output of demonstration

Charlie Brummitt
  • 651
  • 6
  • 11
  • Can you explain why `a point is in the hull if and only if for every equation (describing the facets) the dot product between the point and the normal vector (eq[:-1]) plus the offset (eq[-1]) is less than or equal to zero`? This is not clear to me. What is the physical meaning of that dot product, for a single equation? I am guessing it would mean "the facet's normal points at the point", but I fail to see why it is so – Gulzar Dec 02 '19 at 13:13
  • 1
    This statement follows from one way of defining the convex hull. From the [documentation of Qhull](http://www.qhull.org/html/index.htm) (the code used by scipy): "The convex hull of a point set P is the smallest convex set that contains P. If P is finite, the convex hull defines a matrix A and a vector b such that for all x in P, Ax+b <= [0,...]" The rows of _A_ are the unit normals; the elements of _b_ are the offsets. – Charlie Brummitt Dec 04 '19 at 02:25
  • it's a good solution. But it's a little slow for a convex hull membership test for 10,000 two-dimensional points –  Dec 30 '19 at 14:04
  • I've shared a vectorized version of the function. – Philippe Miron Jun 03 '22 at 02:01
7

Just for completness, here is a poor's man solution:

import pylab
import numpy
from scipy.spatial import ConvexHull

def is_p_inside_points_hull(points, p):
    global hull, new_points # Remove this line! Just for plotting!
    hull = ConvexHull(points)
    new_points = numpy.append(points, p, axis=0)
    new_hull = ConvexHull(new_points)
    if list(hull.vertices) == list(new_hull.vertices):
        return True
    else:
        return False

# Test:
points = numpy.random.rand(10, 2)   # 30 random points in 2-D
# Note: the number of points must be greater than the dimention.
p = numpy.random.rand(1, 2) # 1 random point in 2-D
print is_p_inside_points_hull(points, p)

# Plot:
pylab.plot(points[:,0], points[:,1], 'o')
for simplex in hull.simplices:
    pylab.plot(points[simplex,0], points[simplex,1], 'k-')
pylab.plot(p[:,0], p[:,1], '^r')
pylab.show()

The idea is simple: the vertices of the convex hull of a set of points P won't change if you add a point p that falls "inside" the hull; the vertices of the convex hull for [P1, P2, ..., Pn] and [P1, P2, ..., Pn, p] are the same. But if p falls "outside", then the vertices must change. This works for n-dimensions, but you have to compute the ConvexHull twice.

Two example plots in 2-D:

False:

New point (red) falls outside the convex hull

True:

New point (red) falls inside the convex hull

Elliot Gorokhovsky
  • 3,610
  • 2
  • 31
  • 56
feqwix
  • 1,362
  • 14
  • 16
  • 1
    I'm digging it! But I will say this: CURSE OF DIMENSIONALITY. Over 8 dimensions and the kernel splits. – Ulf Aslak Apr 28 '16 at 18:39
5

It looks like you are using a 2D point cloud, so I'd like to direct you to the inclusion test for point-in-polygon testing of convex polygons.

Scipy's convex hull algorithm allows for finding convex hulls in 2 or more dimensions which is more complicated than it needs to be for a 2D point cloud. Therefore, I recommend using a different algorithm, such as this one. This is because as you really need for point-in-polygon testing of a convex hull is the list of convex hull points in clockwise order, and a point that is inside of the polygon.

The time performance of this approach is as followed:

  • O(N log N) to construct the convex hull
  • O(h) in preprocessing to calculate (and store) the wedge angles from the interior point
  • O(log h) per point-in-polygon query.

Where N is the number of points in the point cloud and h is the number of points in the point clouds convex hull.

Nuclearman
  • 5,029
  • 1
  • 19
  • 35
5

Building on the work of @Charlie Brummitt, I implemented a more efficient version enabling to check if multiple points are in the convex hull at the same time and replacing any loop by faster linear algebra.

import numpy as np
from scipy.spatial.qhull import _Qhull

def in_hull(points, queries):
    hull = _Qhull(b"i", points,
                  options=b"",
                  furthest_site=False,
                  incremental=False, 
                  interior_point=None)
    equations = hull.get_simplex_facet_array()[2].T
    return np.all(queries @ equations[:-1] < - equations[-1], axis=1)

# ============== Demonstration ================

points = np.random.rand(8, 2)
queries = np.random.rand(3, 2)
print(in_hull(points, queries))

Note that I'm using the lower-level _Qhull class for efficiency.

milembar
  • 919
  • 13
  • 17
  • I got this error: `cannot import name '_Qhull' from 'scipy.spatial.qhull'` – zxdawn Jun 17 '22 at 20:52
  • Yes, this is because your version of `scipy` is very recent. `_Qhull` is no longer exposed. So you have no other choice than using an older version of scipy or the slightly slower python interface. – milembar Jun 17 '22 at 22:10
3

To piggy-back off of this answer, to check all points in a numpy array at once, this worked for me:

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

points = np.array([(1, 2), (3, 4), (3, 6), (2, 4.5), (2.5, 5)])
hull = ConvexHull(points)

np.random.seed(1)
random_points = np.random.uniform(0, 6, (100, 2))

# get array of boolean values indicating in hull if True
in_hull = np.all(np.add(np.dot(random_points, hull.equations[:,:-1].T),
                        hull.equations[:,-1]) <= tolerance, axis=1)

random_points_in_hull = random_points[in_hull]
Viyaleta
  • 51
  • 3
3

For those interested, I made a vectorized version of @charlie-brummit answer:

def points_in_hull(p, hull, tol=1e-12):
    return np.all(hull.equations[:,:-1] @ p.T + np.repeat(hull.equations[:,-1][None,:], len(p), axis=0).T <= tol, 0)

where p is now a [N,2] array. It is ~6 times faster than the recommended solution (@Sildoreth answer), and ~10 times faster than the original.

Adapted demonstration without the for loop: (pasted from below to avoid searching in the thread)

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

points = np.array([(1, 2), (3, 4), (3, 6), (2, 4.5), (2.5, 5)])
hull = ConvexHull(points)

np.random.seed(1)
random_points = np.random.uniform(0, 6, (100, 2))

for simplex in hull.simplices:
    plt.plot(points[simplex, 0], points[simplex, 1])

plt.scatter(*points.T, alpha=.5, color='k', s=200, marker='v')

in_hull = points_in_hull(random_points, hull)
plt.scatter(random_points[in_hull,0], random_points[in_hull,1], marker='x', color='g')
plt.scatter(random_points[~in_hull,0], random_points[~in_hull,1], marker='d', color='m')
Philippe Miron
  • 178
  • 1
  • 9
1

If you want to keep with scipy, you have to convex hull (you did so)

>>> from scipy.spatial import ConvexHull
>>> points = np.random.rand(30, 2)   # 30 random points in 2-D
>>> hull = ConvexHull(points)

then build the list of points on the hull. Here is the code from doc to plot the hull

>>> import matplotlib.pyplot as plt
>>> plt.plot(points[:,0], points[:,1], 'o')
>>> for simplex in hull.simplices:
>>>     plt.plot(points[simplex,0], points[simplex,1], 'k-')

So starting from that, I would propose for computing list of points on the hull

pts_hull = [(points[simplex,0], points[simplex,1]) 
                            for simplex in hull.simplices] 

(although i did not try)

And you can also come with your own code for computing the hull, returning the x,y points.

If you want to know if a point from your original dataset is on the hull, then you are done.

I what you want is to know if a any point is inside the hull or outside, you must do a bit of work more. What you will have to do could be

  • for all edges joining two simplices of your hull: decide whether your point is above or under

  • if point is below all lines, or above all lines, it is outside the hull

As a speed up, as soon as a point has been above one line and below one another, it is inside the hull.

kiriloff
  • 25,609
  • 37
  • 148
  • 229
  • I want to find out, if an arbitrary point is in the convex hull of the point-cloud or outside of it. :) – AME May 25 '13 at 15:54
  • so are you satisfied with answer ? – kiriloff May 25 '13 at 15:56
  • 1
    Your answer for inside or outside the hull isn't correct in that above and below is not a sufficient test. For example, if a point is just outside the hull but, say, midway along a 45deg diagonal, then your test will fail. Instead, sum the angles between the test point and all the points of the convex hull: if it's inside the angles will sum to 2pi, and if it's outside they'll sum to 0 (or I might have some detail of this wrong, but that's the basic idea). – tom10 May 25 '13 at 17:54
  • maybe we are not clear about what is above/under a line. I assume that a line has only two sides, above and under. then the test works if you consider all pairs of points from the hull. – kiriloff May 25 '13 at 18:31
0

Based on this post, here is my quick-and-dirty solution for a convex regions with 4 sides (you can easily extend it to more)

def same_sign(arr): return np.all(arr > 0) if arr[0] > 0 else np.all(arr < 0)

def inside_quad(pts, pt):
    a =  pts - pt
    d = np.zeros((4,2))
    d[0,:] = pts[1,:]-pts[0,:]
    d[1,:] = pts[2,:]-pts[1,:]
    d[2,:] = pts[3,:]-pts[2,:]
    d[3,:] = pts[0,:]-pts[3,:]
    res = np.cross(a,d)
    return same_sign(res), res

points = np.array([(1, 2), (3, 4), (3, 6), (2.5, 5)])

np.random.seed(1)
random_points = np.random.uniform(0, 6, (1000, 2))

print wlk1.inside_quad(points, random_points[0])
res = np.array([inside_quad(points, p)[0] for p in random_points])
print res[:4]
plt.plot(random_points[:,0], random_points[:,1], 'b.')
plt.plot(random_points[res][:,0], random_points[res][:,1], 'r.')

enter image description here

Community
  • 1
  • 1
BBSysDyn
  • 4,389
  • 8
  • 48
  • 63