Usage#

Installation#

uv add ashp            # or: pip install ashp
uv add "ashp[geo]"     # optional geopandas support

Computing an alpha shape#

from ashp import alphashape

points = [(0., 0.), (0., 1.), (1., 1.), (1., 0.),
          (0.5, 0.25), (0.5, 0.75), (0.25, 0.5), (0.75, 0.5)]

shape = alphashape(points, alpha=2.0)   # -> shapely geometry
hull  = alphashape(points, alpha=0.0)   # alpha = 0 gives the convex hull

alphashape accepts 2-D points (returns a shapely Polygon / LineString / Point), 3-D points (returns a trimesh.Trimesh), a MultiPoint, or a geopandas.GeoDataFrame.

Choosing alpha#

alpha has units of inverse length, so the “right” value scales with point density. ashp provides several data-driven selectors — see the API reference for details:

from ashp import select_alpha, optimizealpha

# Quantile of the circumradius distribution (q in [0, 1]; 1 = convex hull):
shape = alphashape(points, select_alpha(points, q=0.9))

# Automatic cutoff at the knee of the sorted circumradius curve:
shape = alphashape(points, select_alpha(points, method="knee"))

# Centre of the usable band (past the blob, before fragmentation):
shape = alphashape(points, select_alpha(points, method="band"))

# Tightest single polygon that still covers every point:
shape = alphashape(points, optimizealpha(points))

Inspecting the scale#

alpha_sweep reports, as alpha varies, the spread of the kept Delaunay edge lengths and the number of connected components — the metrics behind the band selector and the interactive dashboard:

from ashp import alpha_sweep, usable_band

sweep = alpha_sweep(points)
band = usable_band(sweep)   # (lo, hi, centre) or None when no clean scale

The alpha complex#

Where ashp.alphashape() returns the boundary, ashp.alpha_complex() returns the full simplicial complex — every Delaunay simplex with its filtration value — so you can query any scale and read off topology (it is built with the Edelsbrunner–Mücke / GUDHI algorithm):

from ashp import alpha_complex, alphashape

ac = alpha_complex(points)
ac.simplices_at(alpha=2.0)   # simplices present at this scale, by dimension
ac.betti_chi(alpha=2.0)      # Euler characteristic (2-D: components - holes)

# Holes the boundary reconstruction would fill are preserved with holes=True:
ring = alphashape(points, alpha=6.0, holes=True)

Command-line interface#

ashp input_points.geojson output_shape.geojson --alpha 2.0