Of course! Here's a comprehensive guide to calculating the convex hull in Python, covering the concept, popular libraries, and code examples.

What is a Convex Hull?
Imagine you have a set of points on a 2D plane (like thumbtacks on a corkboard). The convex hull is the smallest convex shape (a polygon) that contains all the points.
- Convex Shape: A shape is convex if for any two points inside the shape, the entire line segment connecting them is also inside the shape. Think of a circle or a square. A star shape is not convex.
- Analogy: If you were to stretch a rubber band around all the thumbtacks, the shape it would form when it snaps tight is the convex hull.
Method 1: Using the scipy Library (Recommended)
The SciPy library is the standard for scientific computing in Python and has a highly optimized and easy-to-use function for this.
Step 1: Install SciPy
If you don't have it installed, open your terminal or command prompt and run:
pip install scipy numpy matplotlib
(We include numpy for data handling and matplotlib for visualization)
Step 2: Write the Code
The function scipy.spatial.ConvexHull does all the work.
import numpy as np
from scipy.spatial import ConvexHull
import matplotlib.pyplot as plt
# 1. Generate some random points
# We use a fixed seed for reproducibility
np.random.seed(42)
points = np.random.rand(30, 2) * 10 # 30 points with x,y coordinates between 0 and 10
# 2. Calculate the convex hull
# The ConvexHull object contains the vertices of the hull
hull = ConvexHull(points)
# 3. Extract the vertices of the hull
# The 'vertices' attribute gives the indices of the points forming the hull
hull_vertices = points[hull.vertices]
# 4. Visualize the result
plt.figure(figsize=(8, 8))
# Plot all the original points
plt.plot(points[:, 0], points[:, 1], 'o', markersize=8, label='Original Points')
# Plot the convex hull
# We need to close the polygon by adding the first point at the end
hull_points = np.append(hull_vertices, hull_vertices[0]).reshape(-1, 2)
plt.plot(hull_points[:, 0], hull_points[:, 1], 'r-', linewidth=2, label='Convex Hull')
# Highlight the vertices of the hull
plt.plot(hull_vertices[:, 0], hull_vertices[:, 1], 'ro', markersize=10)
'Convex Hull using SciPy')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.legend()
plt.grid(True)
plt.axis('equal') # Ensures the aspect ratio is equal, so the hull isn't distorted
plt.show()
Explanation of the ConvexHull Object
When you create hull = ConvexHull(points), the object hull contains useful information:
hull.vertices: An array of indices of the points that form the vertices of the convex hull, in counter-clockwise order.hull.points: The original input points.hull.simplices: An array of indices that forms the simplices (in 2D, these are the edges) of the convex hull. This is very useful for plotting.hull.area: The area of the convex hull.hull.volume: The area of the convex hull in 2D (same ashull.area).
Method 2: Using the scikit-image Library
Scikit-image is another excellent library for image processing, and it also has a convex hull function.
Step 1: Install scikit-image
pip install scikit-image numpy matplotlib
Step 2: Write the Code
The function skimage.measure.convex_hull_image is slightly different—it works on a binary image and returns a boolean mask of the hull. To get the vertices, we can use skimage.measure.find_contours.
import numpy as np
import matplotlib.pyplot as plt
from skimage.measure import convex_hull_image, find_contours
# 1. Generate random points
np.random.seed(42)
points = np.random.rand(30, 2) * 10
# 2. Find the bounding box of the points to create a canvas
min_x, min_y = np.min(points, axis=0)
max_x, max_y = np.max(points, axis=0)
# 3. Create a binary image (mask) where points are 'on' (True)
# The image size is determined by the spread of the points
image_shape = (int(max_y - min_y + 2), int(max_x - min_x + 2))
image = np.zeros(image_shape, dtype=bool)
# Shift points to fit the image canvas and mark them as True
shifted_points = points - [min_x - 1, min_y - 1]
for y, x in shifted_points.astype(int):
image[y, x] = True
# 4. Calculate the convex hull of the image
hull_mask = convex_hull_image(image)
# 5. Find the contour (vertices) of the hull mask
# The level=0.5 finds the boundary between True (1) and False (0)
contours = find_contours(hull_mask.astype(np.float64), level=0.5)
# The result is a list of contours; we take the first one
# The coordinates are in (row, col) format, so we swap them and shift back
hull_vertices_contour = contours[0][:, [1, 0]] # Swap x,y
hull_vertices = hull_vertices_contour + [min_x - 1, min_y - 1]
# 6. Visualize the result
plt.figure(figsize=(8, 8))
# Plot original points
plt.plot(points[:, 0], points[:, 1], 'o', markersize=8, label='Original Points')
# Plot the hull vertices
plt.plot(hull_vertices[:, 0], hull_vertices[:, 1], 'go', markersize=10, label='Hull Vertices (skimage)')
# Plot the hull contour
# Close the polygon
hull_points = np.vstack([hull_vertices, hull_vertices[0]])
plt.plot(hull_points[:, 0], hull_points[:, 1], 'g-', linewidth=2, label='Convex Hull (skimage)')
'Convex Hull using scikit-image')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.legend()
plt.grid(True)
plt.axis('equal')
plt.show()
Note: The scikit-image method is more indirect for getting vertices from a point cloud. scipy.spatial.ConvexHull is generally more direct and efficient for this specific task.
Method 3: Implementing the Graham Scan Algorithm (For Learning)
If you want to understand the underlying algorithm, implementing it yourself is a great exercise. The Graham Scan is a classic O(n log n) algorithm.
The Algorithm Steps:
- Find the Pivot: Find the point with the lowest y-coordinate (and the smallest x-coordinate in case of a tie). This point is guaranteed to be on the hull.
- Sort: Sort all other points based on the polar angle they make with the pivot.
- Build the Hull: Use a stack. Iterate through the sorted points. For each point, check if the last three points form a "right turn". If they do, the middle point is not part of the hull, so pop it from the stack. Push the current point onto the stack.
import matplotlib.pyplot as plt
import numpy as np
def cross_product(o, a, b):
"""2D cross product of vectors OA and OB."""
return (a[0] - o[0])*(b[1] - o[1]) - (a[1] - o[1])*(b[0] - o[0])
def graham_scan(points):
"""Computes the convex hull of a set of 2D points.
Input: an iterable sequence of (x, y) pairs representing the points.
Output: a list of vertices of the convex hull in counter-clockwise order,
starting from the vertex with the smallest y-coordinate.
Implements Andrew's monotone chain algorithm. O(n log n) complexity.
"""
# Sort the points lexicographically (by x, then by y)
points = sorted(points)
# Build lower hull
lower = []
for p in points:
while len(lower) >= 2 and cross_product(lower[-2], lower[-1], p) <= 0:
lower.pop()
lower.append(p)
# Build upper hull
upper = []
for p in reversed(points):
while len(upper) >= 2 and cross_product(upper[-2], upper[-1], p) <= 0:
upper.pop()
upper.append(p)
# Concatenate the lower and upper hulls and remove the last point
# of each because it's repeated at the beginning of the other list.
convex_hull = lower[:-1] + upper[:-1]
return convex_hull
# --- Example Usage ---
np.random.seed(42)
points = np.random.rand(30, 2) * 10
# Calculate the hull using our custom function
hull_vertices_custom = graham_scan(points)
# Visualization
plt.figure(figsize=(8, 8))
plt.plot(points[:, 0], points[:, 1], 'o', markersize=8, label='Original Points')
# Plot the hull
if hull_vertices_custom:
hull_vertices_custom.append(hull_vertices_custom[0]) # Close the polygon
hull_vertices_custom = np.array(hull_vertices_custom)
plt.plot(hull_vertices_custom[:, 0], hull_vertices_custom[:, 1], 'b-', linewidth=2, label='Convex Hull (Custom)')
plt.plot(hull_vertices_custom[:, 0], hull_vertices_custom[:, 1], 'bo', markersize=8)
'Convex Hull using Custom Graham Scan')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.legend()
plt.grid(True)
plt.axis('equal')
plt.show()
Summary: Which Method to Use?
| Method | Pros | Cons | Best For |
|---|---|---|---|
scipy.spatial.ConvexHull |
Fast, optimized, easy to use, direct output. | Requires SciPy installation. | Most applications. This is the standard, go-to solution. |
scikit-image |
Good if you're already working with image masks. | Indirect for point clouds, more steps to get vertices. | Image processing workflows where points are represented as a mask. |
| Custom Implementation | Excellent for learning, no external dependencies. | Slower than optimized libraries, more code to write and debug. | Learning and understanding algorithms, coding interviews, or environments where you can't install libraries. |
