Source code for spatialcells.spatial._getBoundary

import shapely
from shapely.geometry import Polygon, MultiPolygon
from shapely.validation import make_valid

from ._utils import *


[docs] def getBoundary(anndata, communitycolumn, communityIndexList, alpha=100, debug=False): """ Get a boundary for the communities of interest as a Shapely MultiPolygon. The boundary is defined based on the alpha shape of points in the communities. The alpha shape is the generalization of the convex hull, and is generated via Delaunay triangulation of all the points of interest. The alpha parameter controls the longest edge that can appear in the alpha shape. Smaller alpha gives more detailed boundary, but may appear more jagged and may leave out some points that are too far away from the rest of the points. :param anndata: the anndata object :param communitycolumn: the column name of the community :param communityIndexList: the list of community indexes :param alpha: the alpha parameter for alpha shape. Smaller alpha gives more detailed boundary, but may appear more jagged. :param debug: whether to return the polygons and edge components :returns: the boundaries of components as a MultiPolygon object """ xy = anndata.obs[anndata.obs[communitycolumn].isin(communityIndexList)][ ["X_centroid", "Y_centroid"] ].to_numpy() edge_components = getAlphaShapes(xy, alpha) polygons = [Polygon(edge[:, 0, :]).buffer(0) for edge in edge_components] boundary = MultiPolygon(polygons) # make sure the boundary is valid and points of interest are inside boundary = make_valid(boundary).buffer(1) if boundary.geom_type == "Polygon": boundary = MultiPolygon([boundary]) new_boundary = [] for poly in boundary.geoms: holes = poly.interiors hole_polygons = [Polygon(hole) for hole in holes] # prevent holes from touching the boundary hole_limit = Polygon(poly.exterior).buffer(-1) hole_multipoly = shapely.unary_union(hole_polygons) & hole_limit if hole_multipoly.geom_type == "Polygon": hole_multipoly = MultiPolygon([hole_multipoly]) new_holes_polygons = [p.exterior.coords for p in hole_multipoly.geoms] new_poly = Polygon(poly.exterior, new_holes_polygons) new_boundary.append(new_poly) boundary = make_valid(MultiPolygon(new_boundary)) if debug: return boundary, polygons, edge_components return boundary